Skip to content

Commit 8b30200

Browse files
author
Stephen Belanger
committed
lib: rewrite AsyncLocalStorage without async_hooks
1 parent 198affc commit 8b30200

8 files changed

+373
-1
lines changed

lib/async_hooks.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,63 @@ class AsyncLocalStorage {
369369
}
370370
}
371371

372+
const { AsyncContextFrame } = internalBinding('async_context_frame');
373+
374+
function runFrame (frame, fn, thisArg, args) {
375+
const prior = AsyncContextFrame.exchange(frame)
376+
try {
377+
return ReflectApply(fn, thisArg, args)
378+
} finally {
379+
AsyncContextFrame.exchange(prior)
380+
}
381+
}
382+
383+
class AsyncLocalStorage2 {
384+
static bind (fn) {
385+
validateFunction(fn, 'fn');
386+
const run = this.snapshot()
387+
return function bound (...args) {
388+
return run.call(this, fn, ...args)
389+
}
390+
}
391+
392+
static snapshot () {
393+
const frame = AsyncContextFrame.current()
394+
return function runSnapshot (fn, ...args) {
395+
return runFrame(frame, fn, this, args)
396+
}
397+
}
398+
399+
disable () {
400+
this.enterWith(undefined)
401+
}
402+
403+
enterWith (data) {
404+
const current = AsyncContextFrame.current()
405+
const frame = new AsyncContextFrame(current, this, data)
406+
AsyncContextFrame.exchange(frame)
407+
}
408+
409+
run (store, fn, ...args) {
410+
return AsyncContextFrame.run(this, store, fn, ...args)
411+
}
412+
413+
exit (fn, ...args) {
414+
return runFrame(undefined, fn, null, args)
415+
}
416+
417+
getStore () {
418+
return AsyncContextFrame.current()?.get(this)
419+
}
420+
}
421+
372422
// Placing all exports down here because the exported classes won't export
373423
// otherwise.
374424
module.exports = {
425+
AsyncContextFrame,
426+
AsyncLocalStorage: AsyncLocalStorage2,
375427
// Public API
376-
AsyncLocalStorage,
428+
// AsyncLocalStorage,
377429
createHook,
378430
executionAsyncId,
379431
triggerAsyncId,

node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
'src/api/exceptions.cc',
6262
'src/api/hooks.cc',
6363
'src/api/utils.cc',
64+
'src/async_context_frame.cc',
6465
'src/async_wrap.cc',
6566
'src/base_object.cc',
6667
'src/cares_wrap.cc',
@@ -168,6 +169,7 @@
168169
'src/aliased_buffer-inl.h',
169170
'src/aliased_struct.h',
170171
'src/aliased_struct-inl.h',
172+
'src/async_context_frame.h',
171173
'src/async_wrap.h',
172174
'src/async_wrap-inl.h',
173175
'src/base_object.h',

src/async_context_frame.cc

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#include "async_context_frame.h" // NOLINT(build/include_inline)
2+
#include "env-inl.h"
3+
#include "node_errors.h"
4+
#include "node_external_reference.h"
5+
#include "tracing/traced_value.h"
6+
#include "util-inl.h"
7+
8+
#include "debug_utils-inl.h"
9+
10+
#include "v8.h"
11+
12+
#include <iostream>
13+
14+
using v8::ArrayBufferView;
15+
using v8::Context;
16+
using v8::Function;
17+
using v8::FunctionCallbackInfo;
18+
using v8::FunctionTemplate;
19+
using v8::Isolate;
20+
using v8::Local;
21+
using v8::MaybeLocal;
22+
using v8::Object;
23+
using v8::ObjectTemplate;
24+
using v8::Value;
25+
26+
namespace node {
27+
28+
//
29+
// Constructor
30+
//
31+
AsyncContextFrame::AsyncContextFrame(Environment* env,
32+
Local<Object> obj,
33+
Local<Object> current,
34+
Local<Value> key,
35+
Local<Value> value)
36+
: BaseObject(env, obj),
37+
parent_(env->isolate(), current),
38+
key_(env->isolate(), key),
39+
value_(env->isolate(), value) {}
40+
41+
Local<Value> AsyncContextFrame::current(Local<Context> context) {
42+
return context->GetContinuationPreservedEmbedderData();
43+
}
44+
45+
Local<Value> AsyncContextFrame::exchange(Local<Context> context,
46+
Local<Value> value) {
47+
auto prior = current(context);
48+
context->SetContinuationPreservedEmbedderData(value);
49+
return prior;
50+
}
51+
52+
MaybeLocal<Value> AsyncContextFrame::run(Local<Context> context,
53+
Local<Function> fn,
54+
int argc,
55+
Local<Value>* args) {
56+
auto prior = AsyncContextFrame::exchange(context, this->object());
57+
58+
auto ret = fn->Call(context, v8::Undefined(context->GetIsolate()), argc, args);
59+
60+
AsyncContextFrame::exchange(context, prior);
61+
62+
return ret;
63+
}
64+
65+
Local<Value> AsyncContextFrame::get(Local<Context> context, Local<Value> key) {
66+
Environment* env = Environment::GetCurrent(context);
67+
Isolate* isolate = context->GetIsolate();
68+
69+
if (key_ == key) {
70+
return value_.Get(isolate);
71+
}
72+
73+
auto parent = parent_.Get(isolate);
74+
if (parent.IsEmpty()) {
75+
return v8::Undefined(isolate);
76+
}
77+
78+
if (!AsyncContextFrame::HasInstance(env, parent)) {
79+
return v8::Undefined(isolate);
80+
}
81+
82+
return Unwrap<AsyncContextFrame>(parent)->get(context, key);
83+
}
84+
85+
//
86+
// JS Static Methods
87+
//
88+
void AsyncContextFrame::New(const FunctionCallbackInfo<Value>& info) {
89+
CHECK(info.IsConstructCall());
90+
CHECK_EQ(info.Length(), 3);
91+
92+
auto current = info[0].As<Object>();
93+
auto key = info[1];
94+
auto value = info[2];
95+
96+
Environment* env = Environment::GetCurrent(info);
97+
new AsyncContextFrame(env, info.This(), current, key, value);
98+
}
99+
100+
void AsyncContextFrame::Exchange(const FunctionCallbackInfo<Value>& info) {
101+
Isolate* isolate = info.GetIsolate();
102+
Local<Context> context = isolate->GetCurrentContext();
103+
info.GetReturnValue().Set(AsyncContextFrame::exchange(context, info[0]));
104+
}
105+
106+
void AsyncContextFrame::Current(const FunctionCallbackInfo<Value>& info) {
107+
Isolate* isolate = info.GetIsolate();
108+
Local<Context> context = isolate->GetCurrentContext();
109+
info.GetReturnValue().Set(AsyncContextFrame::current(context));
110+
}
111+
112+
void AsyncContextFrame::RunStatic(const FunctionCallbackInfo<Value>& info) {
113+
CHECK(info.Length() >= 3);
114+
CHECK(info[2]->IsFunction());
115+
116+
Isolate* isolate = info.GetIsolate();
117+
Environment* env = Environment::GetCurrent(isolate);
118+
Local<Context> context = isolate->GetCurrentContext();
119+
120+
// Extract args
121+
auto key = info[0];
122+
auto value = info[1];
123+
auto fn = info[2].As<Function>();
124+
SlicedArguments call_args(info, 3);
125+
126+
// Create new frame continuing from current frame
127+
auto current = AsyncContextFrame::current(context).As<Object>();
128+
if (current.IsEmpty()) current = Local<Object>();
129+
auto acf = AsyncContextFrame::Create(env, current, key, value);
130+
131+
// Run given function within the frame
132+
Local<Value> ret;
133+
if (acf->run(context, fn, call_args.length(), call_args.out()).ToLocal(&ret)) {
134+
info.GetReturnValue().Set(ret);
135+
}
136+
}
137+
138+
void AsyncContextFrame::Run(const FunctionCallbackInfo<Value>& info) {
139+
Local<Context> context = info.GetIsolate()->GetCurrentContext();
140+
141+
auto fn = info[0].As<Function>();
142+
SlicedArguments call_args(info, 1);
143+
144+
AsyncContextFrame* acf = Unwrap<AsyncContextFrame>(info.This());
145+
146+
Local<Value> ret;
147+
if (acf->run(context, fn, call_args.length(), call_args.out())
148+
.ToLocal(&ret)) {
149+
info.GetReturnValue().Set(ret);
150+
}
151+
}
152+
153+
void AsyncContextFrame::Get(const FunctionCallbackInfo<Value>& info) {
154+
Local<Context> context = info.GetIsolate()->GetCurrentContext();
155+
AsyncContextFrame* acf = Unwrap<AsyncContextFrame>(info.This());
156+
info.GetReturnValue().Set(acf->get(context, info[0]));
157+
}
158+
159+
//
160+
// Class construction infra
161+
//
162+
Local<FunctionTemplate> AsyncContextFrame::GetConstructorTemplate(
163+
Environment* env) {
164+
return GetConstructorTemplate(env->isolate_data());
165+
}
166+
167+
Local<FunctionTemplate> AsyncContextFrame::GetConstructorTemplate(
168+
IsolateData* isolate_data) {
169+
Local<FunctionTemplate> tmpl = isolate_data->async_context_frame_ctor_template();
170+
if (tmpl.IsEmpty()) {
171+
Isolate* isolate = isolate_data->isolate();
172+
Environment* env = Environment::GetCurrent(isolate);
173+
// tmpl = BaseObject::MakeLazilyInitializedJSTemplate(env);
174+
tmpl = NewFunctionTemplate(isolate, New);
175+
tmpl->InstanceTemplate()->SetInternalFieldCount(
176+
BaseObject::kInternalFieldCount);
177+
tmpl->SetClassName(
178+
FIXED_ONE_BYTE_STRING(isolate, "AsyncContextFrame"));
179+
SetProtoMethodNoSideEffect(isolate, tmpl, "get", Get);
180+
SetProtoMethod(isolate, tmpl, "run", Run);
181+
SetMethod(isolate, tmpl, "run", RunStatic);
182+
SetMethod(isolate, tmpl, "exchange", Exchange);
183+
SetMethod(isolate, tmpl, "current", Current);
184+
isolate_data->set_async_context_frame_ctor_template(tmpl);
185+
}
186+
return tmpl;
187+
}
188+
189+
bool AsyncContextFrame::HasInstance(Environment* env,
190+
v8::Local<v8::Value> object) {
191+
return GetConstructorTemplate(env->isolate_data())->HasInstance(object);
192+
}
193+
194+
BaseObjectPtr<AsyncContextFrame> AsyncContextFrame::Create(
195+
Environment* env,
196+
Local<Object> current,
197+
Local<Value> key,
198+
Local<Value> value) {
199+
Local<Object> obj;
200+
201+
if (UNLIKELY(!GetConstructorTemplate(env)
202+
->InstanceTemplate()
203+
->NewInstance(env->context())
204+
.ToLocal(&obj))) {
205+
return BaseObjectPtr<AsyncContextFrame>();
206+
}
207+
208+
return MakeBaseObject<AsyncContextFrame>(env, obj, current, key, value);
209+
}
210+
211+
void AsyncContextFrame::RegisterExternalReferences(
212+
ExternalReferenceRegistry* registry) {
213+
registry->Register(New);
214+
registry->Register(Get);
215+
registry->Register(RunStatic);
216+
registry->Register(Run);
217+
registry->Register(Current);
218+
registry->Register(Exchange);
219+
}
220+
221+
void AsyncContextFrame::CreatePerContextProperties(Local<Object> target,
222+
Local<Value> unused,
223+
Local<Context> context,
224+
void* priv) {
225+
Environment* env = Environment::GetCurrent(context);
226+
227+
auto t = AsyncContextFrame::GetConstructorTemplate(env);
228+
SetConstructorFunction(context, target, "AsyncContextFrame", t);
229+
}
230+
231+
void AsyncContextFrame::MemoryInfo(MemoryTracker* tracker) const {
232+
tracker->TrackField("parent", parent_);
233+
tracker->TrackField("key", key_);
234+
tracker->TrackField("value", value_);
235+
}
236+
237+
} // namespace node
238+
239+
NODE_BINDING_CONTEXT_AWARE_INTERNAL(async_context_frame,
240+
node::AsyncContextFrame::CreatePerContextProperties)
241+
NODE_BINDING_EXTERNAL_REFERENCE(async_context_frame,
242+
node::AsyncContextFrame::RegisterExternalReferences)

src/async_context_frame.h

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#ifndef SRC_ASYNC_CONTEXT_FRAME_H_
2+
#define SRC_ASYNC_CONTEXT_FRAME_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include "base_object.h"
7+
#include "v8.h"
8+
9+
#include <cstdint>
10+
11+
namespace node {
12+
13+
class ExternalReferenceRegistry;
14+
15+
class AsyncContextFrame final : public BaseObject {
16+
public:
17+
AsyncContextFrame(Environment* env,
18+
v8::Local<v8::Object> object,
19+
v8::Local<v8::Object> current,
20+
v8::Local<v8::Value> key,
21+
v8::Local<v8::Value> value);
22+
23+
AsyncContextFrame() = delete;
24+
25+
static v8::Local<v8::Value> current(v8::Local<v8::Context> context);
26+
static v8::Local<v8::Value> exchange(v8::Local<v8::Context> context,
27+
v8::Local<v8::Value> value);
28+
v8::MaybeLocal<v8::Value> run(v8::Local<v8::Context> context,
29+
v8::Local<v8::Function> fn,
30+
int argc,
31+
v8::Local<v8::Value>* args);
32+
v8::Local<v8::Value> get(v8::Local<v8::Context> context, v8::Local<v8::Value> key);
33+
34+
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
35+
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
36+
static void RunStatic(const v8::FunctionCallbackInfo<v8::Value>& args);
37+
static void Run(const v8::FunctionCallbackInfo<v8::Value>& args);
38+
static void Current(const v8::FunctionCallbackInfo<v8::Value>& args);
39+
static void Exchange(const v8::FunctionCallbackInfo<v8::Value>& args);
40+
41+
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
42+
IsolateData* isolate_data);
43+
inline static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
44+
Environment* env);
45+
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
46+
static BaseObjectPtr<AsyncContextFrame> Create(Environment* env,
47+
v8::Local<v8::Object> current,
48+
v8::Local<v8::Value> key,
49+
v8::Local<v8::Value> value);
50+
51+
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
52+
static void CreatePerContextProperties(v8::Local<v8::Object> target,
53+
v8::Local<v8::Value> unused,
54+
v8::Local<v8::Context> context,
55+
void* priv);
56+
57+
// If this needs memory info, swap the next two lines
58+
void MemoryInfo(MemoryTracker* tracker) const override;
59+
SET_MEMORY_INFO_NAME(AsyncContextFrame)
60+
SET_SELF_SIZE(AsyncContextFrame)
61+
62+
private:
63+
v8::Global<v8::Object> parent_;
64+
v8::Global<v8::Value> key_;
65+
v8::Global<v8::Value> value_;
66+
};
67+
68+
} // namespace node
69+
70+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
71+
72+
#endif // SRC_ASYNC_CONTEXT_FRAME_H_

0 commit comments

Comments
 (0)