Skip to content

Commit 7b369d1

Browse files
AndreasMadsenaddaleax
authored andcommitted
async_hooks: fix default nextTick triggerAsyncId
In the case where triggerAsyncId is null it should default to the current executionAsyncId. This worked but as a side-effect the resource object was changed too. This fix also makes the null check more strict. EmitInitS is not a documented API, thus there is no reason to be flexible in its input. Ref: #13548 (comment) PR-URL: #14026 Reviewed-By: Refael Ackermann <[email protected]>
1 parent 86c06c0 commit 7b369d1

File tree

5 files changed

+73
-15
lines changed

5 files changed

+73
-15
lines changed

lib/async_hooks.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,7 @@ function emitInitS(asyncId, type, triggerAsyncId, resource) {
322322

323323
// This can run after the early return check b/c running this function
324324
// manually means that the embedder must have used initTriggerId().
325-
if (!Number.isSafeInteger(triggerAsyncId)) {
326-
if (triggerAsyncId !== undefined)
327-
resource = triggerAsyncId;
325+
if (triggerAsyncId === null) {
328326
triggerAsyncId = initTriggerId();
329327
}
330328

lib/internal/process/next_tick.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function setupNextTick() {
6060
// The needed emit*() functions.
6161
const { emitInit, emitBefore, emitAfter, emitDestroy } = async_hooks;
6262
// Grab the constants necessary for working with internal arrays.
63-
const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr, kInitTriggerId } =
63+
const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr } =
6464
async_wrap.constants;
6565
const { async_id_symbol, trigger_id_symbol } = async_wrap;
6666
var nextTickQueue = new NextTickQueue();
@@ -302,6 +302,10 @@ function setupNextTick() {
302302
if (process._exiting)
303303
return;
304304

305+
if (triggerAsyncId === null) {
306+
triggerAsyncId = async_hooks.initTriggerId();
307+
}
308+
305309
var args;
306310
switch (arguments.length) {
307311
case 2: break;
@@ -320,8 +324,5 @@ function setupNextTick() {
320324
++tickInfo[kLength];
321325
if (async_hook_fields[kInit] > 0)
322326
emitInit(asyncId, 'TickObject', triggerAsyncId, obj);
323-
324-
// The call to initTriggerId() was skipped, so clear kInitTriggerId.
325-
async_uid_fields[kInitTriggerId] = 0;
326327
}
327328
}

test/async-hooks/init-hooks.js

+19-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ class ActivityCollector {
3434
this._logid = logid;
3535
this._logtype = logtype;
3636

37-
// register event handlers if provided
37+
// Register event handlers if provided
3838
this.oninit = typeof oninit === 'function' ? oninit : noop;
3939
this.onbefore = typeof onbefore === 'function' ? onbefore : noop;
4040
this.onafter = typeof onafter === 'function' ? onafter : noop;
4141
this.ondestroy = typeof ondestroy === 'function' ? ondestroy : noop;
4242

43-
// create the hook with which we'll collect activity data
43+
// Create the hook with which we'll collect activity data
4444
this._asyncHook = async_hooks.createHook({
4545
init: this._init.bind(this),
4646
before: this._before.bind(this),
@@ -106,10 +106,15 @@ class ActivityCollector {
106106
'\nExpected "destroy" to be called after "after"');
107107
}
108108
}
109+
if (!a.handleIsObject) {
110+
v('No resource object\n' + activityString(a) +
111+
'\nExpected "init" to be called with a resource object');
112+
}
109113
}
110114
if (violations.length) {
111-
console.error(violations.join('\n'));
112-
assert.fail(violations.length, 0, `Failed sanity checks: ${violations}`);
115+
console.error(violations.join('\n\n') + '\n');
116+
assert.fail(violations.length, 0,
117+
`${violations.length} failed sanity checks`);
113118
}
114119
}
115120

@@ -143,11 +148,11 @@ class ActivityCollector {
143148
_getActivity(uid, hook) {
144149
const h = this._activities.get(uid);
145150
if (!h) {
146-
// if we allowed handles without init we ignore any further life time
151+
// If we allowed handles without init we ignore any further life time
147152
// events this makes sense for a few tests in which we enable some hooks
148153
// later
149154
if (this._allowNoInit) {
150-
const stub = { uid, type: 'Unknown' };
155+
const stub = { uid, type: 'Unknown', handleIsObject: true };
151156
this._activities.set(uid, stub);
152157
return stub;
153158
} else {
@@ -163,7 +168,14 @@ class ActivityCollector {
163168
}
164169

165170
_init(uid, type, triggerAsyncId, handle) {
166-
const activity = { uid, type, triggerAsyncId };
171+
const activity = {
172+
uid,
173+
type,
174+
triggerAsyncId,
175+
// In some cases (e.g. Timeout) the handle is a function, thus the usual
176+
// `typeof handle === 'object' && handle !== null` check can't be used.
177+
handleIsObject: handle instanceof Object
178+
};
167179
this._stamp(activity, 'init');
168180
this._activities.set(uid, activity);
169181
this._maybeLog(uid, type, 'init');

test/async-hooks/test-emit-init.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ initHooks({
4646
})
4747
}).enable();
4848

49-
async_hooks.emitInit(expectedId, expectedType, expectedResource);
49+
async_hooks.emitInit(expectedId, expectedType, null, expectedResource);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
// Flags: --expose-internals
3+
const common = require('../common');
4+
5+
// This tests ensures that the triggerId of both the internal and external
6+
// nexTick function sets the triggerAsyncId correctly.
7+
8+
const assert = require('assert');
9+
const async_hooks = require('async_hooks');
10+
const initHooks = require('./init-hooks');
11+
const { checkInvocations } = require('./hook-checks');
12+
const internal = require('internal/process/next_tick');
13+
14+
const hooks = initHooks();
15+
hooks.enable();
16+
17+
const rootAsyncId = async_hooks.executionAsyncId();
18+
19+
// public
20+
process.nextTick(common.mustCall(function() {
21+
assert.strictEqual(async_hooks.triggerAsyncId(), rootAsyncId);
22+
}));
23+
24+
// internal default
25+
internal.nextTick(null, common.mustCall(function() {
26+
assert.strictEqual(async_hooks.triggerAsyncId(), rootAsyncId);
27+
}));
28+
29+
// internal
30+
internal.nextTick(rootAsyncId + 1, common.mustCall(function() {
31+
assert.strictEqual(async_hooks.triggerAsyncId(), rootAsyncId + 1);
32+
}));
33+
34+
process.on('exit', function() {
35+
hooks.sanityCheck();
36+
37+
const as = hooks.activitiesOfTypes('TickObject');
38+
checkInvocations(as[0], {
39+
init: 1, before: 1, after: 1, destroy: 1
40+
}, 'when process exits');
41+
checkInvocations(as[1], {
42+
init: 1, before: 1, after: 1, destroy: 1
43+
}, 'when process exits');
44+
checkInvocations(as[2], {
45+
init: 1, before: 1, after: 1, destroy: 1
46+
}, 'when process exits');
47+
});

0 commit comments

Comments
 (0)