Skip to content
This repository was archived by the owner on Aug 31, 2018. It is now read-only.

Commit 844200f

Browse files
AndreasMadsenaddaleax
authored andcommitted
async_hooks: skip runtime checks when disabled
PR-URL: nodejs/node#15454 Ref: nodejs/node#14387 Ref: nodejs/node#14722 Ref: nodejs/node#14717 Ref: nodejs/node#15448 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent f1cdc59 commit 844200f

15 files changed

+192
-87
lines changed

doc/api/cli.md

+8
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,14 @@ added: v2.1.0
208208
Prints a stack trace whenever synchronous I/O is detected after the first turn
209209
of the event loop.
210210

211+
### `--force-async-hooks-checks`
212+
<!-- YAML
213+
added: REPLACEME
214+
-->
215+
216+
Enables runtime checks for `async_hooks`. These can also be enabled dynamically
217+
by enabling one of the `async_hooks` hooks.
218+
211219
### `--trace-events-enabled`
212220
<!-- YAML
213221
added: v7.7.0

doc/ayo.1

+5
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ Write process warnings to the given file instead of printing to stderr.
152152
Print a stack trace whenever synchronous I/O is detected after the first turn
153153
of the event loop.
154154

155+
.TP
156+
.BR \-\-force\-async\-hooks\-checks
157+
Enables runtime checks for `async_hooks`. These can also be enabled dynamically
158+
by enabling one of the `async_hooks` hooks.
159+
155160
.TP
156161
.BR \-\-trace\-events\-enabled
157162
Enables the collection of trace event tracing information.

lib/_http_outgoing.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,15 @@ function _writeRaw(data, encoding, callback) {
262262
this._flushOutput(conn);
263263
} else if (!data.length) {
264264
if (typeof callback === 'function') {
265-
nextTick(this.socket[async_id_symbol], callback);
265+
let socketAsyncId = this.socket[async_id_symbol];
266+
// If the socket was set directly it won't be correctly initialized
267+
// with an async_id_symbol.
268+
// TODO(AndreasMadsen): @trevnorris suggested some more correct
269+
// solutions in:
270+
// https://github.com/nodejs/node/pull/14389/files#r128522202
271+
if (socketAsyncId === undefined) socketAsyncId = null;
272+
273+
nextTick(socketAsyncId, callback);
266274
}
267275
return true;
268276
}

lib/async_hooks.js

+28-34
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const active_hooks = {
6060
// async execution. These are tracked so if the user didn't include callbacks
6161
// for a given step, that step can bail out early.
6262
const { kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
63-
kExecutionAsyncId, kTriggerAsyncId, kAsyncIdCounter,
63+
kCheck, kExecutionAsyncId, kTriggerAsyncId, kAsyncIdCounter,
6464
kInitTriggerAsyncId } = async_wrap.constants;
6565

6666
// Symbols used to store the respective ids on both AsyncResource instances and
@@ -156,8 +156,10 @@ class AsyncHook {
156156
hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
157157
hooks_array.push(this);
158158

159-
if (prev_kTotals === 0 && hook_fields[kTotals] > 0)
159+
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
160160
enablePromiseHook();
161+
hook_fields[kCheck] += 1;
162+
}
161163

162164
return this;
163165
}
@@ -180,8 +182,10 @@ class AsyncHook {
180182
hook_fields[kPromiseResolve] -= +!!this[promise_resolve_symbol];
181183
hooks_array.splice(index, 1);
182184

183-
if (prev_kTotals > 0 && hook_fields[kTotals] === 0)
185+
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
184186
disablePromiseHook();
187+
hook_fields[kCheck] -= 1;
188+
}
185189

186190
return this;
187191
}
@@ -243,6 +247,15 @@ function triggerAsyncId() {
243247
return async_id_fields[kTriggerAsyncId];
244248
}
245249

250+
function validateAsyncId(asyncId, type) {
251+
// Skip validation when async_hooks is disabled
252+
if (async_hook_fields[kCheck] <= 0) return;
253+
254+
if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
255+
fatalError(new errors.RangeError('ERR_INVALID_ASYNC_ID', type, asyncId));
256+
}
257+
}
258+
246259

247260
// Embedder API //
248261

@@ -337,10 +350,16 @@ function setInitTriggerId(triggerAsyncId) {
337350

338351

339352
function emitInitScript(asyncId, type, triggerAsyncId, resource) {
353+
validateAsyncId(asyncId, 'asyncId');
354+
if (triggerAsyncId !== null)
355+
validateAsyncId(triggerAsyncId, 'triggerAsyncId');
356+
if (async_hook_fields[kCheck] > 0 &&
357+
(typeof type !== 'string' || type.length <= 0)) {
358+
throw new errors.TypeError('ERR_ASYNC_TYPE', type);
359+
}
360+
340361
// Short circuit all checks for the common case. Which is that no hooks have
341362
// been set. Do this to remove performance impact for embedders (and core).
342-
// Even though it bypasses all the argument checks. The performance savings
343-
// here is critical.
344363
if (async_hook_fields[kInit] === 0)
345364
return;
346365

@@ -354,18 +373,6 @@ function emitInitScript(asyncId, type, triggerAsyncId, resource) {
354373
async_id_fields[kInitTriggerAsyncId] = 0;
355374
}
356375

357-
if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
358-
throw new errors.RangeError('ERR_INVALID_ASYNC_ID', 'asyncId', asyncId);
359-
}
360-
if (!Number.isSafeInteger(triggerAsyncId) || triggerAsyncId < -1) {
361-
throw new errors.RangeError('ERR_INVALID_ASYNC_ID',
362-
'triggerAsyncId',
363-
triggerAsyncId);
364-
}
365-
if (typeof type !== 'string' || type.length <= 0) {
366-
throw new errors.TypeError('ERR_ASYNC_TYPE', type);
367-
}
368-
369376
emitInitNative(asyncId, type, triggerAsyncId, resource);
370377
}
371378

@@ -411,15 +418,8 @@ function emitBeforeScript(asyncId, triggerAsyncId) {
411418
// Validate the ids. An id of -1 means it was never set and is visible on the
412419
// call graph. An id < -1 should never happen in any circumstance. Throw
413420
// on user calls because async state should still be recoverable.
414-
if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
415-
fatalError(
416-
new errors.RangeError('ERR_INVALID_ASYNC_ID', 'asyncId', asyncId));
417-
}
418-
if (!Number.isSafeInteger(triggerAsyncId) || triggerAsyncId < -1) {
419-
fatalError(new errors.RangeError('ERR_INVALID_ASYNC_ID',
420-
'triggerAsyncId',
421-
triggerAsyncId));
422-
}
421+
validateAsyncId(asyncId, 'asyncId');
422+
validateAsyncId(triggerAsyncId, 'triggerAsyncId');
423423

424424
pushAsyncIds(asyncId, triggerAsyncId);
425425

@@ -429,10 +429,7 @@ function emitBeforeScript(asyncId, triggerAsyncId) {
429429

430430

431431
function emitAfterScript(asyncId) {
432-
if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
433-
fatalError(
434-
new errors.RangeError('ERR_INVALID_ASYNC_ID', 'asyncId', asyncId));
435-
}
432+
validateAsyncId(asyncId, 'asyncId');
436433

437434
if (async_hook_fields[kAfter] > 0)
438435
emitAfterNative(asyncId);
@@ -442,10 +439,7 @@ function emitAfterScript(asyncId) {
442439

443440

444441
function emitDestroyScript(asyncId) {
445-
if (!Number.isSafeInteger(asyncId) || asyncId < -1) {
446-
fatalError(
447-
new errors.RangeError('ERR_INVALID_ASYNC_ID', 'asyncId', asyncId));
448-
}
442+
validateAsyncId(asyncId, 'asyncId');
449443

450444
// Return early if there are no destroy callbacks, or invalid asyncId.
451445
if (async_hook_fields[kDestroy] === 0 || asyncId <= 0)

lib/internal/process/next_tick.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ function setupNextTick() {
281281
if (process._exiting)
282282
return;
283283

284-
if (!Number.isSafeInteger(triggerAsyncId) || triggerAsyncId <= 0) {
284+
if (triggerAsyncId === null) {
285285
triggerAsyncId = async_hooks.initTriggerId();
286286
}
287287

src/async-wrap.cc

+1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ void AsyncWrap::Initialize(Local<Object> target,
530530
SET_HOOKS_CONSTANT(kDestroy);
531531
SET_HOOKS_CONSTANT(kPromiseResolve);
532532
SET_HOOKS_CONSTANT(kTotals);
533+
SET_HOOKS_CONSTANT(kCheck);
533534
SET_HOOKS_CONSTANT(kExecutionAsyncId);
534535
SET_HOOKS_CONSTANT(kTriggerAsyncId);
535536
SET_HOOKS_CONSTANT(kAsyncIdCounter);

src/env-inl.h

+18-5
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,19 @@ inline v8::Local<v8::String> Environment::AsyncHooks::provider_string(int idx) {
101101
return providers_[idx].Get(isolate_);
102102
}
103103

104+
inline void Environment::AsyncHooks::force_checks() {
105+
// fields_ does not have the += operator defined
106+
fields_[kCheck] = fields_[kCheck] + 1;
107+
}
108+
104109
inline void Environment::AsyncHooks::push_async_ids(double async_id,
105110
double trigger_async_id) {
106-
CHECK_GE(async_id, -1);
107-
CHECK_GE(trigger_async_id, -1);
111+
// Since async_hooks is experimental, do only perform the check
112+
// when async_hooks is enabled.
113+
if (fields_[kCheck] > 0) {
114+
CHECK_GE(async_id, -1);
115+
CHECK_GE(trigger_async_id, -1);
116+
}
108117

109118
async_ids_stack_.push({ async_id_fields_[kExecutionAsyncId],
110119
async_id_fields_[kTriggerAsyncId] });
@@ -117,9 +126,11 @@ inline bool Environment::AsyncHooks::pop_async_id(double async_id) {
117126
// stack was multiple MakeCallback()'s deep.
118127
if (async_ids_stack_.empty()) return false;
119128

120-
// Ask for the async_id to be restored as a sanity check that the stack
129+
// Ask for the async_id to be restored as a check that the stack
121130
// hasn't been corrupted.
122-
if (async_id_fields_[kExecutionAsyncId] != async_id) {
131+
// Since async_hooks is experimental, do only perform the check
132+
// when async_hooks is enabled.
133+
if (fields_[kCheck] > 0 && async_id_fields_[kExecutionAsyncId] != async_id) {
123134
fprintf(stderr,
124135
"Error: async hook stack has become corrupted ("
125136
"actual: %.f, expected: %.f)\n",
@@ -157,7 +168,9 @@ inline Environment::AsyncHooks::InitScope::InitScope(
157168
Environment* env, double init_trigger_async_id)
158169
: env_(env),
159170
async_id_fields_ref_(env->async_hooks()->async_id_fields()) {
160-
CHECK_GE(init_trigger_async_id, -1);
171+
if (env_->async_hooks()->fields()[AsyncHooks::kCheck] > 0) {
172+
CHECK_GE(init_trigger_async_id, -1);
173+
}
161174
env->async_hooks()->push_async_ids(
162175
async_id_fields_ref_[AsyncHooks::kExecutionAsyncId],
163176
init_trigger_async_id);

src/env.h

+3
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ class Environment {
413413
kDestroy,
414414
kPromiseResolve,
415415
kTotals,
416+
kCheck,
416417
kFieldsCount,
417418
};
418419

@@ -434,6 +435,8 @@ class Environment {
434435

435436
inline v8::Local<v8::String> provider_string(int idx);
436437

438+
inline void force_checks();
439+
437440
inline void push_async_ids(double async_id, double trigger_async_id);
438441
inline bool pop_async_id(double async_id);
439442
inline size_t stack_size();

src/node.cc

+18-4
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ static bool syntax_check_only = false;
176176
static bool trace_deprecation = false;
177177
static bool throw_deprecation = false;
178178
static bool trace_sync_io = false;
179+
static bool force_async_hooks_checks = false;
179180
static bool track_heap_objects = false;
180181
static const char* eval_string = nullptr;
181182
static std::vector<std::string> preload_modules;
@@ -1478,8 +1479,10 @@ void InternalCallbackScope::Close() {
14781479

14791480
// Make sure the stack unwound properly. If there are nested MakeCallback's
14801481
// then it should return early and not reach this code.
1481-
CHECK_EQ(env_->execution_async_id(), 0);
1482-
CHECK_EQ(env_->trigger_async_id(), 0);
1482+
if (env_->async_hooks()->fields()[AsyncHooks::kTotals]) {
1483+
CHECK_EQ(env_->execution_async_id(), 0);
1484+
CHECK_EQ(env_->trigger_async_id(), 0);
1485+
}
14831486

14841487
Local<Object> process = env_->process_object();
14851488

@@ -1488,8 +1491,10 @@ void InternalCallbackScope::Close() {
14881491
return;
14891492
}
14901493

1491-
CHECK_EQ(env_->execution_async_id(), 0);
1492-
CHECK_EQ(env_->trigger_async_id(), 0);
1494+
if (env_->async_hooks()->fields()[AsyncHooks::kTotals]) {
1495+
CHECK_EQ(env_->execution_async_id(), 0);
1496+
CHECK_EQ(env_->trigger_async_id(), 0);
1497+
}
14931498

14941499
if (!env_->can_call_into_js()) return;
14951500

@@ -4055,6 +4060,8 @@ static void PrintHelp() {
40554060
" stderr\n"
40564061
" --trace-sync-io show stack trace when use of sync IO\n"
40574062
" is detected after the first tick\n"
4063+
" --force-async-hooks-checks\n"
4064+
" enables checks for async_hooks\n"
40584065
" --trace-events-enabled track trace events\n"
40594066
" --trace-event-categories comma separated list of trace event\n"
40604067
" categories to record\n"
@@ -4180,6 +4187,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
41804187
"--trace-warnings",
41814188
"--redirect-warnings",
41824189
"--trace-sync-io",
4190+
"--force-async-hooks-checks",
41834191
"--trace-events-enabled",
41844192
"--trace-events-categories",
41854193
"--track-heap-objects",
@@ -4318,6 +4326,8 @@ static void ParseArgs(int* argc,
43184326
trace_deprecation = true;
43194327
} else if (strcmp(arg, "--trace-sync-io") == 0) {
43204328
trace_sync_io = true;
4329+
} else if (strcmp(arg, "--force-async-hooks-checks") == 0) {
4330+
force_async_hooks_checks = true;
43214331
} else if (strcmp(arg, "--trace-events-enabled") == 0) {
43224332
trace_enabled = true;
43234333
} else if (strcmp(arg, "--trace-event-categories") == 0) {
@@ -4973,6 +4983,10 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
49734983

49744984
env.set_abort_on_uncaught_exception(abort_on_uncaught_exception);
49754985

4986+
if (force_async_hooks_checks) {
4987+
env.async_hooks()->force_checks();
4988+
}
4989+
49764990
{
49774991
Environment::AsyncCallbackScope callback_scope(&env);
49784992
env.async_hooks()->push_async_ids(1, 0);

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

+35-22
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22

33
const common = require('../common');
44
const assert = require('assert');
5+
const spawnSync = require('child_process').spawnSync;
56
const async_hooks = require('async_hooks');
67
const initHooks = require('./init-hooks');
78

8-
// Verify that if there is no registered hook, then those invalid parameters
9-
// won't be checked.
10-
assert.doesNotThrow(() => async_hooks.emitInit());
11-
129
const expectedId = async_hooks.newUid();
1310
const expectedTriggerId = async_hooks.newUid();
1411
const expectedType = 'test_emit_init_type';
@@ -25,24 +22,40 @@ const hooks1 = initHooks({
2522

2623
hooks1.enable();
2724

28-
assert.throws(() => {
29-
async_hooks.emitInit();
30-
}, common.expectsError({
31-
code: 'ERR_INVALID_ASYNC_ID',
32-
type: RangeError,
33-
}));
34-
assert.throws(() => {
35-
async_hooks.emitInit(expectedId);
36-
}, common.expectsError({
37-
code: 'ERR_INVALID_ASYNC_ID',
38-
type: RangeError,
39-
}));
40-
assert.throws(() => {
41-
async_hooks.emitInit(expectedId, expectedType, -2);
42-
}, common.expectsError({
43-
code: 'ERR_INVALID_ASYNC_ID',
44-
type: RangeError,
45-
}));
25+
switch (process.argv[2]) {
26+
case 'test_invalid_async_id':
27+
async_hooks.emitInit();
28+
return;
29+
case 'test_invalid_trigger_id':
30+
async_hooks.emitInit(expectedId);
31+
return;
32+
case 'test_invalid_trigger_id_negative':
33+
async_hooks.emitInit(expectedId, expectedType, -2);
34+
return;
35+
}
36+
assert.ok(!process.argv[2]);
37+
38+
39+
const c1 = spawnSync(process.execPath, [__filename, 'test_invalid_async_id']);
40+
assert.strictEqual(
41+
c1.stderr.toString().split(/[\r\n]+/g)[0],
42+
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: undefined');
43+
assert.strictEqual(c1.status, 1);
44+
45+
const c2 = spawnSync(process.execPath, [__filename, 'test_invalid_trigger_id']);
46+
assert.strictEqual(
47+
c2.stderr.toString().split(/[\r\n]+/g)[0],
48+
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: undefined');
49+
assert.strictEqual(c2.status, 1);
50+
51+
const c3 = spawnSync(process.execPath, [
52+
__filename, 'test_invalid_trigger_id_negative'
53+
]);
54+
assert.strictEqual(
55+
c3.stderr.toString().split(/[\r\n]+/g)[0],
56+
'RangeError [ERR_INVALID_ASYNC_ID]: Invalid triggerAsyncId value: -2');
57+
assert.strictEqual(c3.status, 1);
58+
4659

4760
async_hooks.emitInit(expectedId, expectedType, expectedTriggerId,
4861
expectedResource);

0 commit comments

Comments
 (0)