async_hooks: enabledHooksExist shall return if hooks are enabled

Correct the implementaton of enabledHooksExist to return true if
there are enabled hooks.

Adapt callsites which used getHooksArrays() as workaround.

PR-URL: https://github.com/nodejs/node/pull/61054
Fixes: https://github.com/nodejs/node/issues/61019
Refs: https://github.com/nodejs/node/pull/59873
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
This commit is contained in:
Gerhard Stöbich
2025-12-31 15:45:20 +01:00
committed by GitHub
parent 7b7f693a98
commit 926162a2ea
9 changed files with 34 additions and 20 deletions

View File

@@ -52,7 +52,6 @@ const {
emitBefore, emitBefore,
emitAfter, emitAfter,
emitDestroy, emitDestroy,
enabledHooksExist,
initHooksExist, initHooksExist,
destroyHooksExist, destroyHooksExist,
} = internal_async_hooks; } = internal_async_hooks;
@@ -188,7 +187,7 @@ class AsyncResource {
this[trigger_async_id_symbol] = triggerAsyncId; this[trigger_async_id_symbol] = triggerAsyncId;
if (initHooksExist()) { if (initHooksExist()) {
if (enabledHooksExist() && type.length === 0) { if (type.length === 0) {
throw new ERR_ASYNC_TYPE(type); throw new ERR_ASYNC_TYPE(type);
} }

View File

@@ -481,7 +481,7 @@ function hasHooks(key) {
} }
function enabledHooksExist() { function enabledHooksExist() {
return hasHooks(kCheck); return active_hooks.array.length > 0;
} }
function initHooksExist() { function initHooksExist() {
@@ -563,7 +563,7 @@ function popAsyncContext(asyncId) {
const stackLength = async_hook_fields[kStackLength]; const stackLength = async_hook_fields[kStackLength];
if (stackLength === 0) return false; if (stackLength === 0) return false;
if (enabledHooksExist() && async_id_fields[kExecutionAsyncId] !== asyncId) { if (async_hook_fields[kCheck] > 0 && async_id_fields[kExecutionAsyncId] !== asyncId) {
// Do the same thing as the native code (i.e. crash hard). // Do the same thing as the native code (i.e. crash hard).
return popAsyncContext_(asyncId); return popAsyncContext_(asyncId);
} }

View File

@@ -25,7 +25,7 @@ const {
const { const {
getDefaultTriggerAsyncId, getDefaultTriggerAsyncId,
getHookArrays, enabledHooksExist,
newAsyncId, newAsyncId,
initHooksExist, initHooksExist,
emitInit, emitInit,
@@ -160,7 +160,7 @@ function queueMicrotask(callback) {
validateFunction(callback, 'callback'); validateFunction(callback, 'callback');
const contextFrame = AsyncContextFrame.current(); const contextFrame = AsyncContextFrame.current();
if (contextFrame || getHookArrays()[0].length > 0) { if (contextFrame || enabledHooksExist()) {
const asyncResource = new AsyncResource( const asyncResource = new AsyncResource(
'Microtask', 'Microtask',
defaultMicrotaskResourceOpts, defaultMicrotaskResourceOpts,

View File

@@ -45,7 +45,7 @@ const {
kIsClosedPromise, kIsClosedPromise,
} = require('internal/streams/utils'); } = require('internal/streams/utils');
const { getHookArrays } = require('internal/async_hooks'); const { enabledHooksExist } = require('internal/async_hooks');
const AsyncContextFrame = require('internal/async_context_frame'); const AsyncContextFrame = require('internal/async_context_frame');
// Lazy load // Lazy load
@@ -78,8 +78,7 @@ function eos(stream, options, callback) {
validateFunction(callback, 'callback'); validateFunction(callback, 'callback');
validateAbortSignal(options.signal, 'options.signal'); validateAbortSignal(options.signal, 'options.signal');
if (AsyncContextFrame.current() || if (AsyncContextFrame.current() || enabledHooksExist()) {
getHookArrays()[0].length > 0) {
// Avoid AsyncResource.bind() because it calls ObjectDefineProperties which // Avoid AsyncResource.bind() because it calls ObjectDefineProperties which
// is a bottleneck here. // is a bottleneck here.
callback = once(bindAsyncResource(callback, 'STREAM_END_OF_STREAM')); callback = once(bindAsyncResource(callback, 'STREAM_END_OF_STREAM'));

View File

@@ -127,8 +127,7 @@ void AsyncHooks::push_async_context(
std::variant<Local<Object>*, Global<Object>*> resource) { std::variant<Local<Object>*, Global<Object>*> resource) {
std::visit([](auto* ptr) { CHECK_IMPLIES(ptr != nullptr, !ptr->IsEmpty()); }, std::visit([](auto* ptr) { CHECK_IMPLIES(ptr != nullptr, !ptr->IsEmpty()); },
resource); resource);
// Since async_hooks is experimental, do only perform the check
// when async_hooks is enabled.
if (fields_[kCheck] > 0) { if (fields_[kCheck] > 0) {
CHECK_GE(async_id, -1); CHECK_GE(async_id, -1);
CHECK_GE(trigger_async_id, -1); CHECK_GE(trigger_async_id, -1);
@@ -1756,7 +1755,7 @@ AsyncHooks::AsyncHooks(Isolate* isolate, const SerializeInfo* info)
clear_async_id_stack(); clear_async_id_stack();
// Always perform async_hooks checks, not just when async_hooks is enabled. // Always perform async_hooks checks, not just when async_hooks is enabled.
// TODO(AndreasMadsen): Consider removing this for LTS releases. // Can be disabled via CLI option --no-force-async-hooks-checks
// See discussion in https://github.com/nodejs/node/pull/15454 // See discussion in https://github.com/nodejs/node/pull/15454
// When removing this, do it by reverting the commit. Otherwise the test // When removing this, do it by reverting the commit. Otherwise the test
// and flag changes won't be included. // and flag changes won't be included.

View File

@@ -0,0 +1,18 @@
// Flags: --expose-internals
'use strict';
require('../common');
const assert = require('assert');
const { createHook } = require('async_hooks');
const { enabledHooksExist } = require('internal/async_hooks');
assert.strictEqual(enabledHooksExist(), false);
const ah = createHook({});
assert.strictEqual(enabledHooksExist(), false);
ah.enable();
assert.strictEqual(enabledHooksExist(), true);
ah.disable();
assert.strictEqual(enabledHooksExist(), false);

View File

@@ -6,7 +6,7 @@ const { Readable, finished } = require('stream');
const { AsyncLocalStorage } = require('async_hooks'); const { AsyncLocalStorage } = require('async_hooks');
const assert = require('assert'); const assert = require('assert');
const AsyncContextFrame = require('internal/async_context_frame'); const AsyncContextFrame = require('internal/async_context_frame');
const internalAsyncHooks = require('internal/async_hooks'); const { enabledHooksExist } = require('internal/async_hooks');
// This test verifies that ALS context is preserved when using stream.finished() // This test verifies that ALS context is preserved when using stream.finished()
@@ -15,8 +15,7 @@ const readable = new Readable();
als.run('test-context-1', common.mustCall(() => { als.run('test-context-1', common.mustCall(() => {
finished(readable, common.mustCall(() => { finished(readable, common.mustCall(() => {
assert.strictEqual(AsyncContextFrame.enabled || internalAsyncHooks.getHookArrays()[0].length > 0, assert.strictEqual(AsyncContextFrame.enabled || enabledHooksExist(), true);
true);
assert.strictEqual(als.getStore(), 'test-context-1'); assert.strictEqual(als.getStore(), 'test-context-1');
})); }));
})); }));

View File

@@ -5,7 +5,7 @@ const common = require('../common');
const { Readable, finished } = require('stream'); const { Readable, finished } = require('stream');
const { createHook, executionAsyncId } = require('async_hooks'); const { createHook, executionAsyncId } = require('async_hooks');
const assert = require('assert'); const assert = require('assert');
const internalAsyncHooks = require('internal/async_hooks'); const { enabledHooksExist } = require('internal/async_hooks');
// This test verifies that when there are active async hooks, stream.finished() uses // This test verifies that when there are active async hooks, stream.finished() uses
// the bindAsyncResource path // the bindAsyncResource path
@@ -27,7 +27,7 @@ const readable = new Readable();
finished(readable, common.mustCall(() => { finished(readable, common.mustCall(() => {
const currentAsyncId = executionAsyncId(); const currentAsyncId = executionAsyncId();
const ctx = contextMap.get(currentAsyncId); const ctx = contextMap.get(currentAsyncId);
assert.strictEqual(internalAsyncHooks.getHookArrays()[0].length > 0, true); assert.ok(enabledHooksExist());
assert.strictEqual(ctx, 'abc-123'); assert.strictEqual(ctx, 'abc-123');
})); }));

View File

@@ -5,16 +5,16 @@ const common = require('../common');
const { Readable, finished } = require('stream'); const { Readable, finished } = require('stream');
const assert = require('assert'); const assert = require('assert');
const AsyncContextFrame = require('internal/async_context_frame'); const AsyncContextFrame = require('internal/async_context_frame');
const internalAsyncHooks = require('internal/async_hooks'); const { enabledHooksExist } = require('internal/async_hooks');
// This test verifies that when there are no active async hooks, stream.finished() uses the default callback path // This test verifies that when there are no active async hooks, stream.finished() uses the default callback path
const readable = new Readable(); const readable = new Readable();
finished(readable, common.mustCall(() => { finished(readable, common.mustCall(() => {
assert.strictEqual(internalAsyncHooks.getHookArrays()[0].length === 0, true); assert.strictEqual(enabledHooksExist(), false);
assert.strictEqual( assert.strictEqual(
AsyncContextFrame.current() || internalAsyncHooks.getHookArrays()[0].length > 0, AsyncContextFrame.current() || enabledHooksExist(),
false, false,
); );
})); }));