Skip to content

Commit 43c8f1c

Browse files
committed
async_hooks: discourage AsyncLocalStorage.disable with --async-context-frame
When `--async-context-frame` is enabled (and by default in v24), there should be no need to manually call `asyncLocalStorage.disable()`.
1 parent 067a779 commit 43c8f1c

File tree

3 files changed

+46
-10
lines changed

3 files changed

+46
-10
lines changed

doc/api/async_context.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,14 @@ to `asyncLocalStorage.getStore()` will return `undefined` until
224224
When calling `asyncLocalStorage.disable()`, all current contexts linked to the
225225
instance will be exited.
226226

227-
Calling `asyncLocalStorage.disable()` is required before the
228-
`asyncLocalStorage` can be garbage collected. This does not apply to stores
229-
provided by the `asyncLocalStorage`, as those objects are garbage collected
230-
along with the corresponding async resources.
227+
There is no need to call this method in order to get an `asyncLocalStorage`
228+
instance from being garbage-collected.
231229

232-
Use this method when the `asyncLocalStorage` is not in use anymore
233-
in the current process.
230+
When running Node.js with CLI flag [`--no-async-context-frame`][], calling
231+
`asyncLocalStorage.disable()` is required before the `asyncLocalStorage` itself
232+
can be garbage collected. However, this does not apply to stores provided by
233+
the `asyncLocalStorage`, as those objects are garbage collected along with the
234+
corresponding async resources.
234235

235236
### `asyncLocalStorage.getStore()`
236237

@@ -905,6 +906,7 @@ const server = createServer((req, res) => {
905906
}).listen(3000);
906907
```
907908
909+
[`--no-async-context-frame`]: cli.md#--no-async-context-frame
908910
[`AsyncResource`]: #class-asyncresource
909911
[`EventEmitter`]: events.md#class-eventemitter
910912
[`Stream`]: stream.md#stream

lib/internal/async_local_storage/async_context_frame.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ReflectApply,
5+
Symbol,
56
} = primordials;
67

78
const {
@@ -12,6 +13,7 @@ const AsyncContextFrame = require('internal/async_context_frame');
1213
const { AsyncResource } = require('async_hooks');
1314

1415
class AsyncLocalStorage {
16+
#frameKey;
1517
#defaultValue = undefined;
1618
#name = undefined;
1719

@@ -30,6 +32,8 @@ class AsyncLocalStorage {
3032
if (options.name !== undefined) {
3133
this.#name = `${options.name}`;
3234
}
35+
36+
this.#frameKey = Symbol(this.#name ? `AsyncLocalStorage ${this.#name}` : 'AsyncLocalStorage');
3337
}
3438

3539
/** @type {string} */
@@ -44,11 +48,13 @@ class AsyncLocalStorage {
4448
}
4549

4650
disable() {
47-
AsyncContextFrame.disable(this);
51+
// TODO(legendecas): deprecate this method once `--async-context-frame` is
52+
// the only way to use AsyncLocalStorage.
53+
AsyncContextFrame.disable(this.#frameKey);
4854
}
4955

5056
enterWith(data) {
51-
const frame = new AsyncContextFrame(this, data);
57+
const frame = new AsyncContextFrame(this.#frameKey, data);
5258
AsyncContextFrame.set(frame);
5359
}
5460

@@ -68,10 +74,10 @@ class AsyncLocalStorage {
6874

6975
getStore() {
7076
const frame = AsyncContextFrame.current();
71-
if (!frame?.has(this)) {
77+
if (!frame?.has(this.#frameKey)) {
7278
return this.#defaultValue;
7379
}
74-
return frame?.get(this);
80+
return frame?.get(this.#frameKey);
7581
}
7682
}
7783

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Flags: --async-context-frame --expose-gc
2+
3+
'use strict';
4+
require('../common');
5+
const { gcUntil } = require('../common/gc');
6+
const { AsyncLocalStorage } = require('async_hooks');
7+
8+
// Verify that the AsyncLocalStorage object can be garbage collected even without
9+
// `asyncLocalStorage.disable()` being called, when `--async-context-frame` is enabled.
10+
11+
let weakRef = null;
12+
{
13+
const alsValue = {};
14+
let als = new AsyncLocalStorage();
15+
als.run(alsValue, () => {
16+
setInterval(() => {
17+
/**
18+
* Empty interval to keep the als value alive.
19+
*/
20+
}, 1000).unref();
21+
});
22+
weakRef = new WeakRef(als);
23+
als = null;
24+
}
25+
26+
gcUntil('AsyncLocalStorage object can be garbage collected', () => {
27+
return weakRef.deref() === undefined;
28+
});

0 commit comments

Comments
 (0)