Skip to content

Commit 5a7b995

Browse files
author
Stephen Belanger
committed
lib: rewrite AsyncLocalStorage without async_hooks
1 parent 48345d0 commit 5a7b995

25 files changed

+823
-126
lines changed

configure.py

+8
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,12 @@
660660
default=None,
661661
help='do not install the bundled Corepack')
662662

663+
parser.add_argument('--with-native-als',
664+
action='store_true',
665+
dest='with_native_als',
666+
default=None,
667+
help='use native AsyncLocalStorage')
668+
663669
# Dummy option for backwards compatibility
664670
parser.add_argument('--without-report',
665671
action='store_true',
@@ -1237,6 +1243,8 @@ def configure_node(o):
12371243
o['default_configuration'] = 'Debug' if options.debug else 'Release'
12381244
o['variables']['error_on_warn'] = b(options.error_on_warn)
12391245

1246+
o['variables']['node_use_native_als'] = b(options.with_native_als)
1247+
12401248
host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc()
12411249
target_arch = options.dest_cpu or host_arch
12421250
# ia32 is preferred by the build tools (GYP) over x86 even if we prefer the latter

lib/async_hooks.js

+20-104
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const {
3030
} = require('internal/validators');
3131
const internal_async_hooks = require('internal/async_hooks');
3232

33+
const { AsyncContextFrame } = internalBinding('async_context_frame');
34+
const hasAsyncContextFrame = typeof AsyncContextFrame === 'function';
35+
3336
// Get functions
3437
// For userland AsyncResources, make sure to emit a destroy event when the
3538
// resource gets gced.
@@ -158,6 +161,7 @@ function createHook(fns) {
158161
// Embedder API //
159162

160163
const destroyedSymbol = Symbol('destroyed');
164+
const contextFrameSymbol = Symbol('context_frame');
161165

162166
class AsyncResource {
163167
constructor(type, opts = kEmptyObject) {
@@ -177,6 +181,8 @@ class AsyncResource {
177181
throw new ERR_INVALID_ASYNC_ID('triggerAsyncId', triggerAsyncId);
178182
}
179183

184+
this[contextFrameSymbol] = hasAsyncContextFrame && AsyncContextFrame.current();
185+
180186
const asyncId = newAsyncId();
181187
this[async_id_symbol] = asyncId;
182188
this[trigger_async_id_symbol] = triggerAsyncId;
@@ -201,12 +207,17 @@ class AsyncResource {
201207
const asyncId = this[async_id_symbol];
202208
emitBefore(asyncId, this[trigger_async_id_symbol], this);
203209

210+
const contextFrame = this[contextFrameSymbol];
211+
let prior;
212+
if (hasAsyncContextFrame) {
213+
prior = AsyncContextFrame.exchange(contextFrame);
214+
}
204215
try {
205-
const ret =
206-
ReflectApply(fn, thisArg, args);
207-
208-
return ret;
216+
return ReflectApply(fn, thisArg, args);
209217
} finally {
218+
if (hasAsyncContextFrame) {
219+
AsyncContextFrame.exchange(prior);
220+
}
210221
if (hasAsyncIdStack())
211222
emitAfter(asyncId);
212223
}
@@ -270,110 +281,15 @@ class AsyncResource {
270281
}
271282
}
272283

273-
const storageList = [];
274-
const storageHook = createHook({
275-
init(asyncId, type, triggerAsyncId, resource) {
276-
const currentResource = executionAsyncResource();
277-
// Value of currentResource is always a non null object
278-
for (let i = 0; i < storageList.length; ++i) {
279-
storageList[i]._propagate(resource, currentResource, type);
280-
}
281-
},
282-
});
283-
284-
class AsyncLocalStorage {
285-
constructor() {
286-
this.kResourceStore = Symbol('kResourceStore');
287-
this.enabled = false;
288-
}
289-
290-
static bind(fn) {
291-
return AsyncResource.bind(fn);
292-
}
293-
294-
static snapshot() {
295-
return AsyncLocalStorage.bind((cb, ...args) => cb(...args));
296-
}
297-
298-
disable() {
299-
if (this.enabled) {
300-
this.enabled = false;
301-
// If this.enabled, the instance must be in storageList
302-
ArrayPrototypeSplice(storageList,
303-
ArrayPrototypeIndexOf(storageList, this), 1);
304-
if (storageList.length === 0) {
305-
storageHook.disable();
306-
}
307-
}
308-
}
309-
310-
_enable() {
311-
if (!this.enabled) {
312-
this.enabled = true;
313-
ArrayPrototypePush(storageList, this);
314-
storageHook.enable();
315-
}
316-
}
317-
318-
// Propagate the context from a parent resource to a child one
319-
_propagate(resource, triggerResource, type) {
320-
const store = triggerResource[this.kResourceStore];
321-
if (this.enabled) {
322-
resource[this.kResourceStore] = store;
323-
}
324-
}
325-
326-
enterWith(store) {
327-
this._enable();
328-
const resource = executionAsyncResource();
329-
resource[this.kResourceStore] = store;
330-
}
331-
332-
run(store, callback, ...args) {
333-
// Avoid creation of an AsyncResource if store is already active
334-
if (ObjectIs(store, this.getStore())) {
335-
return ReflectApply(callback, null, args);
336-
}
337-
338-
this._enable();
339-
340-
const resource = executionAsyncResource();
341-
const oldStore = resource[this.kResourceStore];
342-
343-
resource[this.kResourceStore] = store;
344-
345-
try {
346-
return ReflectApply(callback, null, args);
347-
} finally {
348-
resource[this.kResourceStore] = oldStore;
349-
}
350-
}
351-
352-
exit(callback, ...args) {
353-
if (!this.enabled) {
354-
return ReflectApply(callback, null, args);
355-
}
356-
this.disable();
357-
try {
358-
return ReflectApply(callback, null, args);
359-
} finally {
360-
this._enable();
361-
}
362-
}
363-
364-
getStore() {
365-
if (this.enabled) {
366-
const resource = executionAsyncResource();
367-
return resource[this.kResourceStore];
368-
}
369-
}
370-
}
371-
372284
// Placing all exports down here because the exported classes won't export
373285
// otherwise.
374286
module.exports = {
375287
// Public API
376-
AsyncLocalStorage,
288+
get AsyncLocalStorage () {
289+
return hasAsyncContextFrame
290+
? require('internal/async_local_storage/native')
291+
: require('internal/async_local_storage/async_hooks');
292+
},
377293
createHook,
378294
executionAsyncId,
379295
triggerAsyncId,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use strict';
2+
3+
const {
4+
ArrayPrototypeIndexOf,
5+
ArrayPrototypePush,
6+
ArrayPrototypeSplice,
7+
ObjectIs,
8+
ReflectApply
9+
} = primordials;
10+
11+
const {
12+
AsyncResource,
13+
createHook,
14+
executionAsyncResource
15+
} = require('async_hooks');
16+
17+
const storageList = [];
18+
const storageHook = createHook({
19+
init (asyncId, type, triggerAsyncId, resource) {
20+
const currentResource = executionAsyncResource();
21+
// Value of currentResource is always a non null object
22+
for (let i = 0; i < storageList.length; ++i) {
23+
storageList[i]._propagate(resource, currentResource, type);
24+
}
25+
},
26+
});
27+
28+
class AsyncLocalStorage {
29+
constructor () {
30+
this.kResourceStore = Symbol('kResourceStore');
31+
this.enabled = false;
32+
}
33+
34+
static bind (fn) {
35+
return AsyncResource.bind(fn);
36+
}
37+
38+
static snapshot () {
39+
return AsyncLocalStorage.bind((cb, ...args) => cb(...args));
40+
}
41+
42+
disable () {
43+
if (this.enabled) {
44+
this.enabled = false;
45+
// If this.enabled, the instance must be in storageList
46+
ArrayPrototypeSplice(storageList,
47+
ArrayPrototypeIndexOf(storageList, this), 1);
48+
if (storageList.length === 0) {
49+
storageHook.disable();
50+
}
51+
}
52+
}
53+
54+
_enable () {
55+
if (!this.enabled) {
56+
this.enabled = true;
57+
ArrayPrototypePush(storageList, this);
58+
storageHook.enable();
59+
}
60+
}
61+
62+
// Propagate the context from a parent resource to a child one
63+
_propagate (resource, triggerResource, type) {
64+
const store = triggerResource[this.kResourceStore];
65+
if (this.enabled) {
66+
resource[this.kResourceStore] = store;
67+
}
68+
}
69+
70+
enterWith (store) {
71+
this._enable();
72+
const resource = executionAsyncResource();
73+
resource[this.kResourceStore] = store;
74+
}
75+
76+
run (store, callback, ...args) {
77+
// Avoid creation of an AsyncResource if store is already active
78+
if (ObjectIs(store, this.getStore())) {
79+
return ReflectApply(callback, null, args);
80+
}
81+
82+
this._enable();
83+
84+
const resource = executionAsyncResource();
85+
const oldStore = resource[this.kResourceStore];
86+
87+
resource[this.kResourceStore] = store;
88+
89+
try {
90+
return ReflectApply(callback, null, args);
91+
} finally {
92+
resource[this.kResourceStore] = oldStore;
93+
}
94+
}
95+
96+
exit (callback, ...args) {
97+
if (!this.enabled) {
98+
return ReflectApply(callback, null, args);
99+
}
100+
this.disable();
101+
try {
102+
return ReflectApply(callback, null, args);
103+
} finally {
104+
this._enable();
105+
}
106+
}
107+
108+
getStore () {
109+
if (this.enabled) {
110+
const resource = executionAsyncResource();
111+
return resource[this.kResourceStore];
112+
}
113+
}
114+
}
115+
116+
module.exports = AsyncLocalStorage;
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use strict';
2+
3+
const {
4+
FunctionPrototypeBind,
5+
ReflectApply
6+
} = primordials;
7+
8+
const { validateFunction } = require('internal/validators');
9+
10+
const { AsyncContextFrame } = internalBinding('async_context_frame');
11+
12+
class AsyncLocalStorage {
13+
static bind (fn) {
14+
validateFunction(fn, 'fn');
15+
const run = this.snapshot()
16+
return function bound (...args) {
17+
return run.call(this, fn, ...args);
18+
};
19+
}
20+
21+
static snapshot () {
22+
const frame = AsyncContextFrame.current();
23+
return function runSnapshot (fn, ...args) {
24+
const bound = FunctionPrototypeBind(fn, this);
25+
const prior = AsyncContextFrame.exchange(frame);
26+
try {
27+
return ReflectApply(bound, undefined, args);
28+
} finally {
29+
AsyncContextFrame.exchange(prior);
30+
}
31+
};
32+
}
33+
34+
disable () {
35+
AsyncContextFrame.disable(this);
36+
}
37+
38+
enterWith (data) {
39+
const frame = new AsyncContextFrame(this, data);
40+
AsyncContextFrame.exchange(frame);
41+
}
42+
43+
run (data, fn, ...args) {
44+
const frame = new AsyncContextFrame(this, data);
45+
const prior = AsyncContextFrame.exchange(frame);
46+
47+
try {
48+
return ReflectApply(fn, undefined, args);
49+
} finally {
50+
AsyncContextFrame.exchange(prior);
51+
}
52+
}
53+
54+
exit (fn, ...args) {
55+
return ReflectApply(this.run, this, [undefined, fn, ...args]);
56+
}
57+
58+
getStore () {
59+
return AsyncContextFrame.current()?.get(this);
60+
}
61+
}
62+
63+
module.exports = AsyncLocalStorage;

0 commit comments

Comments
 (0)