From b35d35c9b58fb95dfdd197ef3b0de59ba8b0b893 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Thu, 22 Jun 2023 16:41:29 -0700 Subject: [PATCH] lib: rewrite AsyncLocalStorage without async_hooks --- lib/async_hooks.js | 55 +++++- node.gyp | 2 + src/async_context_frame.cc | 226 ++++++++++++++++++++++++ src/async_context_frame.h | 71 ++++++++ src/env_properties.h | 1 + src/node_binding.cc | 1 + src/node_external_reference.h | 1 + test/parallel/test-bootstrap-modules.js | 1 + 8 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 src/async_context_frame.cc create mode 100644 src/async_context_frame.h diff --git a/lib/async_hooks.js b/lib/async_hooks.js index b4dd54022d55b1..5482dfcbf11385 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -369,11 +369,64 @@ class AsyncLocalStorage { } } +const { AsyncContextFrame } = internalBinding('async_context_frame'); + +class AsyncLocalStorage2 { + static bind (fn) { + validateFunction(fn, 'fn'); + const run = this.snapshot() + return function bound (...args) { + return run.call(this, fn, ...args) + } + } + + static snapshot () { + const frame = AsyncContextFrame.current() + return function runSnapshot (fn, ...args) { + const prior = AsyncContextFrame.exchange(frame) + try { + return fn.apply(this, args) + } finally { + AsyncContextFrame.exchange(prior) + } + } + } + + disable () { + this.enterWith(undefined) + } + + enterWith (data) { + const current = AsyncContextFrame.current() + const frame = new AsyncContextFrame(current, this, data) + AsyncContextFrame.exchange(frame) + } + + run (store, fn, ...args) { + return AsyncContextFrame.run(this, store, fn, ...args) + } + + exit (fn, ...args) { + const prior = AsyncContextFrame.exchange(undefined) + try { + return Reflect.apply(fn, null, args) + } finally { + AsyncContextFrame.exchange(prior) + } + } + + getStore () { + return AsyncContextFrame.current().get(this) + } +} + // Placing all exports down here because the exported classes won't export // otherwise. module.exports = { + AsyncContextFrame, + AsyncLocalStorage: AsyncLocalStorage2, // Public API - AsyncLocalStorage, + // AsyncLocalStorage, createHook, executionAsyncId, triggerAsyncId, diff --git a/node.gyp b/node.gyp index 218f1b834b108d..af7235bf1bef35 100644 --- a/node.gyp +++ b/node.gyp @@ -61,6 +61,7 @@ 'src/api/exceptions.cc', 'src/api/hooks.cc', 'src/api/utils.cc', + 'src/async_context_frame.cc', 'src/async_wrap.cc', 'src/base_object.cc', 'src/cares_wrap.cc', @@ -168,6 +169,7 @@ 'src/aliased_buffer-inl.h', 'src/aliased_struct.h', 'src/aliased_struct-inl.h', + 'src/async_context_frame.h', 'src/async_wrap.h', 'src/async_wrap-inl.h', 'src/base_object.h', diff --git a/src/async_context_frame.cc b/src/async_context_frame.cc new file mode 100644 index 00000000000000..3dd27562533115 --- /dev/null +++ b/src/async_context_frame.cc @@ -0,0 +1,226 @@ +#include "async_context_frame.h" // NOLINT(build/include_inline) +#include "env-inl.h" +#include "node_errors.h" +#include "node_external_reference.h" +#include "tracing/traced_value.h" +#include "util-inl.h" + +#include "debug_utils-inl.h" + +#include "v8.h" + +#include + +using v8::ArrayBufferView; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::ObjectTemplate; +using v8::Value; + +namespace node { + +// +// Constructor +// +AsyncContextFrame::AsyncContextFrame(Environment* env, + Local obj, + Local current, + Local key, + Local value) + : BaseObject(env, obj), + parent_(env->isolate(), current), + key_(env->isolate(), key), + value_(env->isolate(), value) {} + +Local AsyncContextFrame::current(Local context) { + return context->GetContinuationPreservedEmbedderData(); +} + +Local AsyncContextFrame::exchange(Local context, + Local value) { + auto prior = current(context); + context->SetContinuationPreservedEmbedderData(value); + return prior; +} + +MaybeLocal AsyncContextFrame::run(Local context, + Local fn, + int argc, + Local* args) { + auto prior = AsyncContextFrame::exchange(context, this->object()); + + auto ret = fn->Call(context, v8::Undefined(context->GetIsolate()), argc, args); + + AsyncContextFrame::exchange(context, prior); + + return ret; +} + +Local AsyncContextFrame::get(Local context, Local key) { + Environment* env = Environment::GetCurrent(context); + Isolate* isolate = context->GetIsolate(); + + if (key_ == key) { + return value_.Get(isolate); + } + + auto parent = parent_.Get(isolate); + if (parent.IsEmpty()) { + return v8::Undefined(isolate); + } + + if (!AsyncContextFrame::HasInstance(env, parent)) { + return v8::Undefined(isolate); + } + + return Unwrap(parent)->get(context, key); +} + +// +// JS Static Methods +// +void AsyncContextFrame::Exchange(const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + info.GetReturnValue().Set(AsyncContextFrame::exchange(context, info[0])); +} + +void AsyncContextFrame::Current(const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + Local context = isolate->GetCurrentContext(); + info.GetReturnValue().Set(AsyncContextFrame::current(context)); +} + +void AsyncContextFrame::RunStatic(const FunctionCallbackInfo& args) { + CHECK(args.Length() >= 3); + CHECK(args[2]->IsFunction()); + + Isolate* isolate = args.GetIsolate(); + Environment* env = Environment::GetCurrent(isolate); + Local context = isolate->GetCurrentContext(); + + // Extract args + auto key = args[0]; + auto value = args[1]; + auto fn = args[2].As(); + SlicedArguments call_args(args, 3); + + // Create new frame continuing from current frame + auto current = AsyncContextFrame::current(context).As(); + if (current.IsEmpty()) current = Local(); + auto acf = AsyncContextFrame::Create(env, current, key, value); + + // Run given function within the frame + Local ret; + if (acf->run(context, fn, call_args.length(), call_args.out()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } +} + +void AsyncContextFrame::Run(const FunctionCallbackInfo& args) { + Local context = args.GetIsolate()->GetCurrentContext(); + + auto fn = args[0].As(); + SlicedArguments call_args(args, 1); + + AsyncContextFrame* acf = Unwrap(args.This()); + + Local ret; + if (acf->run(context, fn, call_args.length(), call_args.out()) + .ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } +} + +void AsyncContextFrame::Get(const FunctionCallbackInfo& args) { + Local context = args.GetIsolate()->GetCurrentContext(); + AsyncContextFrame* acf = Unwrap(args.This()); + args.GetReturnValue().Set(acf->get(context, args[0])); +} + +// +// Class construction infra +// +Local AsyncContextFrame::GetConstructorTemplate( + Environment* env) { + return GetConstructorTemplate(env->isolate_data()); +} + +Local AsyncContextFrame::GetConstructorTemplate( + IsolateData* isolate_data) { + Local tmpl = isolate_data->async_context_frame_ctor_template(); + if (tmpl.IsEmpty()) { + Isolate* isolate = isolate_data->isolate(); + Environment* env = Environment::GetCurrent(isolate); + tmpl = BaseObject::MakeLazilyInitializedJSTemplate(env); + tmpl->SetClassName( + FIXED_ONE_BYTE_STRING(isolate, "AsyncContextFrame")); + SetProtoMethodNoSideEffect(isolate, tmpl, "get", Get); + SetProtoMethod(isolate, tmpl, "run", Run); + SetMethod(isolate, tmpl, "run", RunStatic); + SetMethod(isolate, tmpl, "exchange", Exchange); + SetMethod(isolate, tmpl, "current", Current); + isolate_data->set_async_context_frame_ctor_template(tmpl); + } + return tmpl; +} + +bool AsyncContextFrame::HasInstance(Environment* env, + v8::Local object) { + return GetConstructorTemplate(env->isolate_data())->HasInstance(object); +} + +BaseObjectPtr AsyncContextFrame::Create( + Environment* env, + Local current, + Local key, + Local value) { + Local obj; + + if (UNLIKELY(!GetConstructorTemplate(env) + ->InstanceTemplate() + ->NewInstance(env->context()) + .ToLocal(&obj))) { + return BaseObjectPtr(); + } + + return MakeBaseObject(env, obj, current, key, value); +} + +void AsyncContextFrame::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(Get); + registry->Register(RunStatic); + registry->Register(Run); + registry->Register(Current); + registry->Register(Exchange); +} + +void AsyncContextFrame::CreatePerContextProperties(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + + auto t = AsyncContextFrame::GetConstructorTemplate(env); + SetConstructorFunction(context, target, "AsyncContextFrame", t); +} + +void AsyncContextFrame::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("parent", parent_); + tracker->TrackField("key", key_); + tracker->TrackField("value", value_); +} + +} // namespace node + +NODE_BINDING_CONTEXT_AWARE_INTERNAL(async_context_frame, + node::AsyncContextFrame::CreatePerContextProperties) +NODE_BINDING_EXTERNAL_REFERENCE(async_context_frame, + node::AsyncContextFrame::RegisterExternalReferences) diff --git a/src/async_context_frame.h b/src/async_context_frame.h new file mode 100644 index 00000000000000..b882999b602f0d --- /dev/null +++ b/src/async_context_frame.h @@ -0,0 +1,71 @@ +#ifndef SRC_ASYNC_CONTEXT_FRAME_H_ +#define SRC_ASYNC_CONTEXT_FRAME_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "base_object.h" +#include "v8.h" + +#include + +namespace node { + +class ExternalReferenceRegistry; + +class AsyncContextFrame final : public BaseObject { + public: + AsyncContextFrame(Environment* env, + v8::Local object, + v8::Local current, + v8::Local key, + v8::Local value); + + AsyncContextFrame() = delete; + + static v8::Local current(v8::Local context); + static v8::Local exchange(v8::Local context, + v8::Local value); + v8::MaybeLocal run(v8::Local context, + v8::Local fn, + int argc, + v8::Local* args); + v8::Local get(v8::Local context, v8::Local key); + + static void Get(const v8::FunctionCallbackInfo& args); + static void RunStatic(const v8::FunctionCallbackInfo& args); + static void Run(const v8::FunctionCallbackInfo& args); + static void Current(const v8::FunctionCallbackInfo& args); + static void Exchange(const v8::FunctionCallbackInfo& args); + + static v8::Local GetConstructorTemplate( + IsolateData* isolate_data); + inline static v8::Local GetConstructorTemplate( + Environment* env); + static bool HasInstance(Environment* env, v8::Local value); + static BaseObjectPtr Create(Environment* env, + v8::Local current, + v8::Local key, + v8::Local value); + + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); + static void CreatePerContextProperties(v8::Local target, + v8::Local unused, + v8::Local context, + void* priv); + + // If this needs memory info, swap the next two lines + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(AsyncContextFrame) + SET_SELF_SIZE(AsyncContextFrame) + + private: + v8::Global parent_; + v8::Global key_; + v8::Global value_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_ASYNC_CONTEXT_FRAME_H_ diff --git a/src/env_properties.h b/src/env_properties.h index 77b143dbf78931..6fe80ace60864a 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -331,6 +331,7 @@ V(x_forwarded_string, "x-forwarded-for") #define PER_ISOLATE_TEMPLATE_PROPERTIES(V) \ + V(async_context_frame_ctor_template, v8::FunctionTemplate) \ V(async_wrap_ctor_template, v8::FunctionTemplate) \ V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ V(binding_data_default_template, v8::ObjectTemplate) \ diff --git a/src/node_binding.cc b/src/node_binding.cc index 97257d47c61738..fe6c0796792ece 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -27,6 +27,7 @@ // node is built as static library. No need to depend on the // __attribute__((constructor)) like mechanism in GCC. #define NODE_BUILTIN_STANDARD_BINDINGS(V) \ + V(async_context_frame) \ V(async_wrap) \ V(blob) \ V(block_list) \ diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 1586b15553d6a2..3c4909de955987 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -76,6 +76,7 @@ class ExternalReferenceRegistry { }; #define EXTERNAL_REFERENCE_BINDING_LIST_BASE(V) \ + V(async_context_frame) \ V(async_wrap) \ V(binding) \ V(blob) \ diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 0a671eb95eb6d4..61d1f5f01c246c 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -16,6 +16,7 @@ const expectedModules = new Set([ 'NativeModule internal/errors', 'Internal Binding config', 'Internal Binding timers', + 'Internal Binding async_context_frame', 'Internal Binding async_wrap', 'Internal Binding task_queue', 'Internal Binding symbols',