Description
Version
v24.0.0
Platform
Darwin [computer name] 24.4.0 Darwin Kernel Version 24.4.0: Fri Apr 11 18:33:47 PDT 2025; root:xnu-11417.101.15~117/RELEASE_ARM64_T6000 arm64 arm Darwin
Subsystem
async_hooks
What steps will reproduce the bug?
const { AsyncLocalStorage, AsyncResource } = require('async_hooks')
const { strictEqual } = require('assert')
const storage = new AsyncLocalStorage()
storage.enterWith(1)
const ar = new AsyncResource('test')
ar.runInAsyncScope(() => {
storage.enterWith(2)
ar.runInAsyncScope(() => strictEqual(storage.getStore(), 2))
})
Running this in Node.js 24.0.0 breaks, and it passes on all earlier versions.
How often does it reproduce? Is there a required condition?
This is reproducible as long as --no-async-context-frame
is not set.
What is the expected behavior? Why is that the expected behavior?
This should pass, as it does on previous versions. If a storage is entered with a given store
in a runAsyncContext()
block of an AsyncResource
, then future invocations of getStore()
should return store
as long as no other enterWith()
has been called on that storage.
What do you see instead?
node:assert:95
throw new AssertionError(obj);
^
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
1 !== 2
at /path/to/broken-async.js:10:28
at AsyncResource.runInAsyncScope (node:async_hooks:214:14)
at /path/to/broken-async.js:10:6
at AsyncResource.runInAsyncScope (node:async_hooks:214:14)
at Object.<anonymous> (/path/to/broken-async.js:8:4)
at Module._compile (node:internal/modules/cjs/loader:1734:14)
at Object..js (node:internal/modules/cjs/loader:1899:10)
at Module.load (node:internal/modules/cjs/loader:1469:32)
at Module._load (node:internal/modules/cjs/loader:1286:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 1,
expected: 2,
operator: 'strictEqual'
}
Node.js v24.0.0
It looks like it's reverting to the store
that was active when the AsyncResource
was created.
Additional information
The test case provided is a whittled-down version of something we do in legacy instrumentations in dd-trace-js
that pre-date tracingChannel
. Re-implementing those instrumentations using tracingChannel
solves this, since we're then not doing anything too strange with AsyncResources
like we are here.