Skip to content

Commit b35d35c

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

8 files changed

+357
-1
lines changed

lib/async_hooks.js

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

372+
const { AsyncContextFrame } = internalBinding('async_context_frame');
373+
374+
class AsyncLocalStorage2 {
375+
static bind (fn) {
376+
validateFunction(fn, 'fn');
377+
const run = this.snapshot()
378+
return function bound (...args) {
379+
return run.call(this, fn, ...args)
380+
}
381+
}
382+
383+
static snapshot () {
384+
const frame = AsyncContextFrame.current()
385+
return function runSnapshot (fn, ...args) {
386+
const prior = AsyncContextFrame.exchange(frame)
387+
try {
388+
return fn.apply(this, args)
389+
} finally {
390+
AsyncContextFrame.exchange(prior)
391+
}
392+
}
393+
}
394+
395+
disable () {
396+
this.enterWith(undefined)
397+
}
398+
399+
enterWith (data) {
400+
const current = AsyncContextFrame.current()
401+
const frame = new AsyncContextFrame(current, this, data)
402+
AsyncContextFrame.exchange(frame)
403+
}
404+
405+
run (store, fn, ...args) {
406+
return AsyncContextFrame.run(this, store, fn, ...args)
407+
}
408+
409+
exit (fn, ...args) {
410+
const prior = AsyncContextFrame.exchange(undefined)
411+
try {
412+
return Reflect.apply(fn, null, args)
413+
} finally {
414+
AsyncContextFrame.exchange(prior)
415+
}
416+
}
417+
418+
getStore () {
419+
return AsyncContextFrame.current().get(this)
420+
}
421+
}
422+
372423
// Placing all exports down here because the exported classes won't export
373424
// otherwise.
374425
module.exports = {
426+
AsyncContextFrame,
427+
AsyncLocalStorage: AsyncLocalStorage2,
375428
// Public API
376-
AsyncLocalStorage,
429+
// AsyncLocalStorage,
377430
createHook,
378431
executionAsyncId,
379432
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

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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::Exchange(const FunctionCallbackInfo<Value>& info) {
89+
Isolate* isolate = info.GetIsolate();
90+
Local<Context> context = isolate->GetCurrentContext();
91+
info.GetReturnValue().Set(AsyncContextFrame::exchange(context, info[0]));
92+
}
93+
94+
void AsyncContextFrame::Current(const FunctionCallbackInfo<Value>& info) {
95+
Isolate* isolate = info.GetIsolate();
96+
Local<Context> context = isolate->GetCurrentContext();
97+
info.GetReturnValue().Set(AsyncContextFrame::current(context));
98+
}
99+
100+
void AsyncContextFrame::RunStatic(const FunctionCallbackInfo<Value>& args) {
101+
CHECK(args.Length() >= 3);
102+
CHECK(args[2]->IsFunction());
103+
104+
Isolate* isolate = args.GetIsolate();
105+
Environment* env = Environment::GetCurrent(isolate);
106+
Local<Context> context = isolate->GetCurrentContext();
107+
108+
// Extract args
109+
auto key = args[0];
110+
auto value = args[1];
111+
auto fn = args[2].As<Function>();
112+
SlicedArguments call_args(args, 3);
113+
114+
// Create new frame continuing from current frame
115+
auto current = AsyncContextFrame::current(context).As<Object>();
116+
if (current.IsEmpty()) current = Local<Object>();
117+
auto acf = AsyncContextFrame::Create(env, current, key, value);
118+
119+
// Run given function within the frame
120+
Local<Value> ret;
121+
if (acf->run(context, fn, call_args.length(), call_args.out()).ToLocal(&ret)) {
122+
args.GetReturnValue().Set(ret);
123+
}
124+
}
125+
126+
void AsyncContextFrame::Run(const FunctionCallbackInfo<Value>& args) {
127+
Local<Context> context = args.GetIsolate()->GetCurrentContext();
128+
129+
auto fn = args[0].As<Function>();
130+
SlicedArguments call_args(args, 1);
131+
132+
AsyncContextFrame* acf = Unwrap<AsyncContextFrame>(args.This());
133+
134+
Local<Value> ret;
135+
if (acf->run(context, fn, call_args.length(), call_args.out())
136+
.ToLocal(&ret)) {
137+
args.GetReturnValue().Set(ret);
138+
}
139+
}
140+
141+
void AsyncContextFrame::Get(const FunctionCallbackInfo<Value>& args) {
142+
Local<Context> context = args.GetIsolate()->GetCurrentContext();
143+
AsyncContextFrame* acf = Unwrap<AsyncContextFrame>(args.This());
144+
args.GetReturnValue().Set(acf->get(context, args[0]));
145+
}
146+
147+
//
148+
// Class construction infra
149+
//
150+
Local<FunctionTemplate> AsyncContextFrame::GetConstructorTemplate(
151+
Environment* env) {
152+
return GetConstructorTemplate(env->isolate_data());
153+
}
154+
155+
Local<FunctionTemplate> AsyncContextFrame::GetConstructorTemplate(
156+
IsolateData* isolate_data) {
157+
Local<FunctionTemplate> tmpl = isolate_data->async_context_frame_ctor_template();
158+
if (tmpl.IsEmpty()) {
159+
Isolate* isolate = isolate_data->isolate();
160+
Environment* env = Environment::GetCurrent(isolate);
161+
tmpl = BaseObject::MakeLazilyInitializedJSTemplate(env);
162+
tmpl->SetClassName(
163+
FIXED_ONE_BYTE_STRING(isolate, "AsyncContextFrame"));
164+
SetProtoMethodNoSideEffect(isolate, tmpl, "get", Get);
165+
SetProtoMethod(isolate, tmpl, "run", Run);
166+
SetMethod(isolate, tmpl, "run", RunStatic);
167+
SetMethod(isolate, tmpl, "exchange", Exchange);
168+
SetMethod(isolate, tmpl, "current", Current);
169+
isolate_data->set_async_context_frame_ctor_template(tmpl);
170+
}
171+
return tmpl;
172+
}
173+
174+
bool AsyncContextFrame::HasInstance(Environment* env,
175+
v8::Local<v8::Value> object) {
176+
return GetConstructorTemplate(env->isolate_data())->HasInstance(object);
177+
}
178+
179+
BaseObjectPtr<AsyncContextFrame> AsyncContextFrame::Create(
180+
Environment* env,
181+
Local<Object> current,
182+
Local<Value> key,
183+
Local<Value> value) {
184+
Local<Object> obj;
185+
186+
if (UNLIKELY(!GetConstructorTemplate(env)
187+
->InstanceTemplate()
188+
->NewInstance(env->context())
189+
.ToLocal(&obj))) {
190+
return BaseObjectPtr<AsyncContextFrame>();
191+
}
192+
193+
return MakeBaseObject<AsyncContextFrame>(env, obj, current, key, value);
194+
}
195+
196+
void AsyncContextFrame::RegisterExternalReferences(
197+
ExternalReferenceRegistry* registry) {
198+
registry->Register(Get);
199+
registry->Register(RunStatic);
200+
registry->Register(Run);
201+
registry->Register(Current);
202+
registry->Register(Exchange);
203+
}
204+
205+
void AsyncContextFrame::CreatePerContextProperties(Local<Object> target,
206+
Local<Value> unused,
207+
Local<Context> context,
208+
void* priv) {
209+
Environment* env = Environment::GetCurrent(context);
210+
211+
auto t = AsyncContextFrame::GetConstructorTemplate(env);
212+
SetConstructorFunction(context, target, "AsyncContextFrame", t);
213+
}
214+
215+
void AsyncContextFrame::MemoryInfo(MemoryTracker* tracker) const {
216+
tracker->TrackField("parent", parent_);
217+
tracker->TrackField("key", key_);
218+
tracker->TrackField("value", value_);
219+
}
220+
221+
} // namespace node
222+
223+
NODE_BINDING_CONTEXT_AWARE_INTERNAL(async_context_frame,
224+
node::AsyncContextFrame::CreatePerContextProperties)
225+
NODE_BINDING_EXTERNAL_REFERENCE(async_context_frame,
226+
node::AsyncContextFrame::RegisterExternalReferences)

src/async_context_frame.h

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 Get(const v8::FunctionCallbackInfo<v8::Value>& args);
35+
static void RunStatic(const v8::FunctionCallbackInfo<v8::Value>& args);
36+
static void Run(const v8::FunctionCallbackInfo<v8::Value>& args);
37+
static void Current(const v8::FunctionCallbackInfo<v8::Value>& args);
38+
static void Exchange(const v8::FunctionCallbackInfo<v8::Value>& args);
39+
40+
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
41+
IsolateData* isolate_data);
42+
inline static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
43+
Environment* env);
44+
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
45+
static BaseObjectPtr<AsyncContextFrame> Create(Environment* env,
46+
v8::Local<v8::Object> current,
47+
v8::Local<v8::Value> key,
48+
v8::Local<v8::Value> value);
49+
50+
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
51+
static void CreatePerContextProperties(v8::Local<v8::Object> target,
52+
v8::Local<v8::Value> unused,
53+
v8::Local<v8::Context> context,
54+
void* priv);
55+
56+
// If this needs memory info, swap the next two lines
57+
void MemoryInfo(MemoryTracker* tracker) const override;
58+
SET_MEMORY_INFO_NAME(AsyncContextFrame)
59+
SET_SELF_SIZE(AsyncContextFrame)
60+
61+
private:
62+
v8::Global<v8::Object> parent_;
63+
v8::Global<v8::Value> key_;
64+
v8::Global<v8::Value> value_;
65+
};
66+
67+
} // namespace node
68+
69+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
70+
71+
#endif // SRC_ASYNC_CONTEXT_FRAME_H_

src/env_properties.h

+1
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@
331331
V(x_forwarded_string, "x-forwarded-for")
332332

333333
#define PER_ISOLATE_TEMPLATE_PROPERTIES(V) \
334+
V(async_context_frame_ctor_template, v8::FunctionTemplate) \
334335
V(async_wrap_ctor_template, v8::FunctionTemplate) \
335336
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
336337
V(binding_data_default_template, v8::ObjectTemplate) \

src/node_binding.cc

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
// node is built as static library. No need to depend on the
2828
// __attribute__((constructor)) like mechanism in GCC.
2929
#define NODE_BUILTIN_STANDARD_BINDINGS(V) \
30+
V(async_context_frame) \
3031
V(async_wrap) \
3132
V(blob) \
3233
V(block_list) \

0 commit comments

Comments
 (0)