Skip to content

Commit 9ee4b16

Browse files
Stephen Belangertargos
authored andcommitted
lib: rewrite AsyncLocalStorage without async_hooks
PR-URL: #48528 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
1 parent 5dbff81 commit 9ee4b16

29 files changed

Lines changed: 658 additions & 173 deletions

‎benchmark/async_hooks/async-local-storage-getstore-nested-resources.js‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ function runInAsyncScopes(resourceCount, cb, i = 0) {
3838

3939
functionmain({ n, resourceCount }){
4040
conststore=newAsyncLocalStorage();
41-
runInAsyncScopes(resourceCount,()=>{
42-
bench.start();
43-
runBenchmark(store,n);
44-
bench.end(n);
41+
store.run({},()=>{
42+
runInAsyncScopes(resourceCount,()=>{
43+
bench.start();
44+
runBenchmark(store,n);
45+
bench.end(n);
46+
});
4547
});
4648
}

‎benchmark/async_hooks/async-local-storage-getstore-nested-run.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const { AsyncLocalStorage } = require('async_hooks');
1414
* - AsyncLocalStorage1.getStore()
1515
*/
1616
constbench=common.createBenchmark(main,{
17-
sotrageCount: [1,10,100],
17+
storageCount: [1,10,100],
1818
n: [1e4],
1919
});
2020

@@ -34,8 +34,8 @@ function runStores(stores, value, cb, idx = 0) {
3434
}
3535
}
3636

37-
functionmain({ n,sotrageCount}){
38-
conststores=newArray(sotrageCount).fill(0).map(()=>newAsyncLocalStorage());
37+
functionmain({ n,storageCount}){
38+
conststores=newArray(storageCount).fill(0).map(()=>newAsyncLocalStorage());
3939
constcontextValue={};
4040

4141
runStores(stores,contextValue,()=>{

‎doc/api/cli.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,21 @@ and `"` are usable.
876876
It is possible to run code containing inline types by passing
877877
[`--experimental-strip-types`][].
878878

879+
### `--experimental-async-context-frame`
880+
881+
<!-- YAML
882+
added: REPLACEME
883+
-->
884+
885+
> Stability: 1 - Experimental
886+
887+
Enables the use of AsyncLocalStorage backed by AsyncContextFrame rather than
888+
the default implementation which relies on async\_hooks. This new model is
889+
implemented very differently and so could have differences in how context data
890+
flows within the application. As such, it is presently recommended to be sure
891+
your application behaviour is unaffected by this change before using it in
892+
production.
893+
879894
### `--experimental-default-type=type`
880895

881896
<!-- YAML
@@ -2942,6 +2957,7 @@ one is included in the list below.
29422957
*`--enable-network-family-autoselection`
29432958
*`--enable-source-maps`
29442959
*`--experimental-abortcontroller`
2960+
*`--experimental-async-context-frame`
29452961
*`--experimental-default-type`
29462962
*`--experimental-detect-module`
29472963
*`--experimental-eventsource`

‎lib/async_hooks.js‎

Lines changed: 14 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const {
1010
NumberIsSafeInteger,
1111
ObjectDefineProperties,
1212
ObjectFreeze,
13-
ObjectIs,
1413
ReflectApply,
1514
Symbol,
1615
}=primordials;
@@ -30,6 +29,8 @@ const {
3029
}=require('internal/validators');
3130
constinternal_async_hooks=require('internal/async_hooks');
3231

32+
constAsyncContextFrame=require('internal/async_context_frame');
33+
3334
// Get functions
3435
// For userland AsyncResources, make sure to emit a destroy event when the
3536
// resource gets gced.
@@ -158,6 +159,7 @@ function createHook(fns) {
158159
// Embedder API //
159160

160161
constdestroyedSymbol=Symbol('destroyed');
162+
constcontextFrameSymbol=Symbol('context_frame');
161163

162164
classAsyncResource{
163165
constructor(type,opts=kEmptyObject){
@@ -177,6 +179,8 @@ class AsyncResource {
177179
thrownewERR_INVALID_ASYNC_ID('triggerAsyncId',triggerAsyncId);
178180
}
179181

182+
this[contextFrameSymbol]=AsyncContextFrame.current();
183+
180184
constasyncId=newAsyncId();
181185
this[async_id_symbol]=asyncId;
182186
this[trigger_async_id_symbol]=triggerAsyncId;
@@ -201,12 +205,12 @@ class AsyncResource {
201205
constasyncId=this[async_id_symbol];
202206
emitBefore(asyncId,this[trigger_async_id_symbol],this);
203207

208+
constcontextFrame=this[contextFrameSymbol];
209+
constprior=AsyncContextFrame.exchange(contextFrame);
204210
try{
205-
constret=
206-
ReflectApply(fn,thisArg,args);
207-
208-
returnret;
211+
returnReflectApply(fn,thisArg,args);
209212
}finally{
213+
AsyncContextFrame.set(prior);
210214
if(hasAsyncIdStack())
211215
emitAfter(asyncId);
212216
}
@@ -270,110 +274,15 @@ class AsyncResource {
270274
}
271275
}
272276

273-
conststorageList=[];
274-
conststorageHook=createHook({
275-
init(asyncId,type,triggerAsyncId,resource){
276-
constcurrentResource=executionAsyncResource();
277-
// Value of currentResource is always a non null object
278-
for(leti=0;i<storageList.length;++i){
279-
storageList[i]._propagate(resource,currentResource,type);
280-
}
281-
},
282-
});
283-
284-
classAsyncLocalStorage{
285-
constructor(){
286-
this.kResourceStore=Symbol('kResourceStore');
287-
this.enabled=false;
288-
}
289-
290-
staticbind(fn){
291-
returnAsyncResource.bind(fn);
292-
}
293-
294-
staticsnapshot(){
295-
returnAsyncLocalStorage.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-
conststore=triggerResource[this.kResourceStore];
321-
if(this.enabled){
322-
resource[this.kResourceStore]=store;
323-
}
324-
}
325-
326-
enterWith(store){
327-
this._enable();
328-
constresource=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-
returnReflectApply(callback,null,args);
336-
}
337-
338-
this._enable();
339-
340-
constresource=executionAsyncResource();
341-
constoldStore=resource[this.kResourceStore];
342-
343-
resource[this.kResourceStore]=store;
344-
345-
try{
346-
returnReflectApply(callback,null,args);
347-
}finally{
348-
resource[this.kResourceStore]=oldStore;
349-
}
350-
}
351-
352-
exit(callback, ...args){
353-
if(!this.enabled){
354-
returnReflectApply(callback,null,args);
355-
}
356-
this.disable();
357-
try{
358-
returnReflectApply(callback,null,args);
359-
}finally{
360-
this._enable();
361-
}
362-
}
363-
364-
getStore(){
365-
if(this.enabled){
366-
constresource=executionAsyncResource();
367-
returnresource[this.kResourceStore];
368-
}
369-
}
370-
}
371-
372277
// Placing all exports down here because the exported classes won't export
373278
// otherwise.
374279
module.exports={
375280
// Public API
376-
AsyncLocalStorage,
281+
getAsyncLocalStorage(){
282+
returnAsyncContextFrame.enabled ?
283+
require('internal/async_local_storage/native') :
284+
require('internal/async_local_storage/async_hooks');
285+
},
377286
createHook,
378287
executionAsyncId,
379288
triggerAsyncId,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const{
4+
getContinuationPreservedEmbedderData,
5+
setContinuationPreservedEmbedderData,
6+
}=internalBinding('async_context_frame');
7+
8+
letenabled_;
9+
10+
classAsyncContextFrameextendsMap{
11+
constructor(store,data){
12+
super(AsyncContextFrame.current());
13+
this.set(store,data);
14+
}
15+
16+
staticgetenabled(){
17+
enabled_??=require('internal/options')
18+
.getOptionValue('--experimental-async-context-frame');
19+
returnenabled_;
20+
}
21+
22+
staticcurrent(){
23+
if(this.enabled){
24+
returngetContinuationPreservedEmbedderData();
25+
}
26+
}
27+
28+
staticset(frame){
29+
if(this.enabled){
30+
setContinuationPreservedEmbedderData(frame);
31+
}
32+
}
33+
34+
staticexchange(frame){
35+
constprior=this.current();
36+
this.set(frame);
37+
returnprior;
38+
}
39+
40+
staticdisable(store){
41+
constframe=this.current();
42+
frame?.disable(store);
43+
}
44+
45+
disable(store){
46+
this.delete(store);
47+
}
48+
}
49+
50+
module.exports=AsyncContextFrame;

0 commit comments

Comments
 (0)