Skip to content

Commit b061655

Browse files
Qardcodebytere
authored andcommitted
async_hooks: move PromiseHook handler to JS
This avoids the need to wrap every promise in an AsyncWrap and also makes it easier to skip the machinery to track destroy events when there's no destroy listener. Co-authored-by: Andrey Pechkurov <[email protected]> PR-URL: #32891 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent cc5c8e0 commit b061655

File tree

10 files changed

+306
-65
lines changed

10 files changed

+306
-65
lines changed

benchmark/async_hooks/promises.js

+23-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22
const common = require('../common.js');
33
const { createHook } = require('async_hooks');
44

5+
let hook;
6+
const tests = {
7+
disabled() {
8+
hook = createHook({
9+
promiseResolve() {}
10+
});
11+
},
12+
enabled() {
13+
hook = createHook({
14+
promiseResolve() {}
15+
}).enable();
16+
},
17+
enabledWithDestroy() {
18+
hook = createHook({
19+
promiseResolve() {},
20+
destroy() {}
21+
}).enable();
22+
}
23+
};
24+
525
const bench = common.createBenchmark(main, {
626
n: [1e6],
727
asyncHooks: [
828
'enabled',
29+
'enabledWithDestroy',
930
'disabled',
1031
]
1132
});
@@ -19,10 +40,8 @@ async function run(n) {
1940
}
2041

2142
function main({ n, asyncHooks }) {
22-
const hook = createHook({ promiseResolve() {} });
23-
if (asyncHooks !== 'disabled') {
24-
hook.enable();
25-
}
43+
if (hook) hook.disable();
44+
tests[asyncHooks]();
2645
bench.start();
2746
run(n).then(() => {
2847
bench.end(n);

doc/api/async_hooks.md

-5
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,6 @@ currently not considered public, but using the Embedder API, users can provide
306306
and document their own resource objects. For example, such a resource object
307307
could contain the SQL query being executed.
308308

309-
In the case of Promises, the `resource` object will have an
310-
`isChainedPromise` property, set to `true` if the promise has a parent promise,
311-
and `false` otherwise. For example, in the case of `b = a.then(handler)`, `a` is
312-
considered a parent `Promise` of `b`. Here, `b` is considered a chained promise.
313-
314309
In some cases the resource object is reused for performance reasons, it is
315310
thus not safe to use it as a key in a `WeakMap` or add properties to it.
316311

lib/async_hooks.js

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
getHookArrays,
2727
enableHooks,
2828
disableHooks,
29+
updatePromiseHookMode,
2930
executionAsyncResource,
3031
// Internal Embedder API
3132
newAsyncId,
@@ -101,6 +102,8 @@ class AsyncHook {
101102
enableHooks();
102103
}
103104

105+
updatePromiseHookMode();
106+
104107
return this;
105108
}
106109

lib/internal/async_hooks.js

+71-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
const {
44
Error,
55
FunctionPrototypeBind,
6+
ObjectPrototypeHasOwnProperty,
67
ObjectDefineProperty,
8+
Promise,
79
Symbol,
810
} = primordials;
911

@@ -86,9 +88,10 @@ const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
8688
kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId,
8789
kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;
8890

91+
const { async_id_symbol,
92+
trigger_async_id_symbol } = internalBinding('symbols');
93+
8994
// Used in AsyncHook and AsyncResource.
90-
const async_id_symbol = Symbol('asyncId');
91-
const trigger_async_id_symbol = Symbol('triggerAsyncId');
9295
const init_symbol = Symbol('init');
9396
const before_symbol = Symbol('before');
9497
const after_symbol = Symbol('after');
@@ -243,27 +246,89 @@ function restoreActiveHooks() {
243246
active_hooks.tmp_fields = null;
244247
}
245248

249+
function trackPromise(promise, parent, silent) {
250+
const asyncId = getOrSetAsyncId(promise);
251+
252+
promise[trigger_async_id_symbol] = parent ? getOrSetAsyncId(parent) :
253+
getDefaultTriggerAsyncId();
254+
255+
if (!silent && initHooksExist()) {
256+
const triggerId = promise[trigger_async_id_symbol];
257+
emitInitScript(asyncId, 'PROMISE', triggerId, promise);
258+
}
259+
}
260+
261+
function fastPromiseHook(type, promise, parent) {
262+
if (type === kInit || !promise[async_id_symbol]) {
263+
const silent = type !== kInit;
264+
if (parent instanceof Promise) {
265+
trackPromise(promise, parent, silent);
266+
} else {
267+
trackPromise(promise, null, silent);
268+
}
269+
270+
if (!silent) return;
271+
}
272+
273+
const asyncId = promise[async_id_symbol];
274+
switch (type) {
275+
case kBefore:
276+
const triggerId = promise[trigger_async_id_symbol];
277+
emitBeforeScript(asyncId, triggerId, promise);
278+
break;
279+
case kAfter:
280+
if (hasHooks(kAfter)) {
281+
emitAfterNative(asyncId);
282+
}
283+
if (asyncId === executionAsyncId()) {
284+
// This condition might not be true if async_hooks was enabled during
285+
// the promise callback execution.
286+
// Popping it off the stack can be skipped in that case, because it is
287+
// known that it would correspond to exactly one call with
288+
// PromiseHookType::kBefore that was not witnessed by the PromiseHook.
289+
popAsyncContext(asyncId);
290+
}
291+
break;
292+
case kPromiseResolve:
293+
emitPromiseResolveNative(asyncId);
294+
break;
295+
}
296+
}
246297

247298
let wantPromiseHook = false;
248299
function enableHooks() {
249300
async_hook_fields[kCheck] += 1;
301+
}
250302

303+
let promiseHookMode = -1;
304+
function updatePromiseHookMode() {
251305
wantPromiseHook = true;
252-
enablePromiseHook();
306+
if (destroyHooksExist()) {
307+
if (promiseHookMode !== 1) {
308+
promiseHookMode = 1;
309+
enablePromiseHook();
310+
}
311+
} else if (promiseHookMode !== 0) {
312+
promiseHookMode = 0;
313+
enablePromiseHook(fastPromiseHook);
314+
}
253315
}
254316

255317
function disableHooks() {
256318
async_hook_fields[kCheck] -= 1;
257319

258320
wantPromiseHook = false;
321+
259322
// Delay the call to `disablePromiseHook()` because we might currently be
260323
// between the `before` and `after` calls of a Promise.
261324
enqueueMicrotask(disablePromiseHookIfNecessary);
262325
}
263326

264327
function disablePromiseHookIfNecessary() {
265-
if (!wantPromiseHook)
328+
if (!wantPromiseHook) {
329+
promiseHookMode = -1;
266330
disablePromiseHook();
331+
}
267332
}
268333

269334
// Internal Embedder API //
@@ -276,7 +341,7 @@ function newAsyncId() {
276341
}
277342

278343
function getOrSetAsyncId(object) {
279-
if (object.hasOwnProperty(async_id_symbol)) {
344+
if (ObjectPrototypeHasOwnProperty(object, async_id_symbol)) {
280345
return object[async_id_symbol];
281346
}
282347

@@ -447,6 +512,7 @@ module.exports = {
447512
},
448513
enableHooks,
449514
disableHooks,
515+
updatePromiseHookMode,
450516
clearDefaultTriggerAsyncId,
451517
clearAsyncIdStack,
452518
hasAsyncIdStack,

0 commit comments

Comments
 (0)