Skip to content

Commit

Permalink
lib: rewrite AsyncLocalStorage without async_hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen Belanger committed Jun 23, 2023
1 parent 198affc commit 8b30200
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 1 deletion.
54 changes: 53 additions & 1 deletion lib/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,63 @@ class AsyncLocalStorage {
}
}

const { AsyncContextFrame } = internalBinding('async_context_frame');

function runFrame (frame, fn, thisArg, args) {
const prior = AsyncContextFrame.exchange(frame)
try {
return ReflectApply(fn, thisArg, args)
} finally {
AsyncContextFrame.exchange(prior)
}
}

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) {
return runFrame(frame, fn, this, args)
}
}

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) {
return runFrame(undefined, fn, null, args)
}

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,
Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
242 changes: 242 additions & 0 deletions src/async_context_frame.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
#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 <iostream>

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<Object> obj,
Local<Object> current,
Local<Value> key,
Local<Value> value)
: BaseObject(env, obj),
parent_(env->isolate(), current),
key_(env->isolate(), key),
value_(env->isolate(), value) {}

Local<Value> AsyncContextFrame::current(Local<Context> context) {
return context->GetContinuationPreservedEmbedderData();
}

Local<Value> AsyncContextFrame::exchange(Local<Context> context,
Local<Value> value) {
auto prior = current(context);
context->SetContinuationPreservedEmbedderData(value);
return prior;
}

MaybeLocal<Value> AsyncContextFrame::run(Local<Context> context,
Local<Function> fn,
int argc,
Local<Value>* 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<Value> AsyncContextFrame::get(Local<Context> context, Local<Value> 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<AsyncContextFrame>(parent)->get(context, key);
}

//
// JS Static Methods
//
void AsyncContextFrame::New(const FunctionCallbackInfo<Value>& info) {
CHECK(info.IsConstructCall());
CHECK_EQ(info.Length(), 3);

auto current = info[0].As<Object>();
auto key = info[1];
auto value = info[2];

Environment* env = Environment::GetCurrent(info);
new AsyncContextFrame(env, info.This(), current, key, value);
}

void AsyncContextFrame::Exchange(const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
info.GetReturnValue().Set(AsyncContextFrame::exchange(context, info[0]));
}

void AsyncContextFrame::Current(const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
info.GetReturnValue().Set(AsyncContextFrame::current(context));
}

void AsyncContextFrame::RunStatic(const FunctionCallbackInfo<Value>& info) {
CHECK(info.Length() >= 3);
CHECK(info[2]->IsFunction());

Isolate* isolate = info.GetIsolate();
Environment* env = Environment::GetCurrent(isolate);
Local<Context> context = isolate->GetCurrentContext();

// Extract args
auto key = info[0];
auto value = info[1];
auto fn = info[2].As<Function>();
SlicedArguments call_args(info, 3);

// Create new frame continuing from current frame
auto current = AsyncContextFrame::current(context).As<Object>();
if (current.IsEmpty()) current = Local<Object>();
auto acf = AsyncContextFrame::Create(env, current, key, value);

// Run given function within the frame
Local<Value> ret;
if (acf->run(context, fn, call_args.length(), call_args.out()).ToLocal(&ret)) {
info.GetReturnValue().Set(ret);
}
}

void AsyncContextFrame::Run(const FunctionCallbackInfo<Value>& info) {
Local<Context> context = info.GetIsolate()->GetCurrentContext();

auto fn = info[0].As<Function>();
SlicedArguments call_args(info, 1);

AsyncContextFrame* acf = Unwrap<AsyncContextFrame>(info.This());

Local<Value> ret;
if (acf->run(context, fn, call_args.length(), call_args.out())
.ToLocal(&ret)) {
info.GetReturnValue().Set(ret);
}
}

void AsyncContextFrame::Get(const FunctionCallbackInfo<Value>& info) {
Local<Context> context = info.GetIsolate()->GetCurrentContext();
AsyncContextFrame* acf = Unwrap<AsyncContextFrame>(info.This());
info.GetReturnValue().Set(acf->get(context, info[0]));
}

//
// Class construction infra
//
Local<FunctionTemplate> AsyncContextFrame::GetConstructorTemplate(
Environment* env) {
return GetConstructorTemplate(env->isolate_data());
}

Local<FunctionTemplate> AsyncContextFrame::GetConstructorTemplate(
IsolateData* isolate_data) {
Local<FunctionTemplate> 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 = NewFunctionTemplate(isolate, New);
tmpl->InstanceTemplate()->SetInternalFieldCount(
BaseObject::kInternalFieldCount);
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<v8::Value> object) {
return GetConstructorTemplate(env->isolate_data())->HasInstance(object);
}

BaseObjectPtr<AsyncContextFrame> AsyncContextFrame::Create(
Environment* env,
Local<Object> current,
Local<Value> key,
Local<Value> value) {
Local<Object> obj;

if (UNLIKELY(!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context())
.ToLocal(&obj))) {
return BaseObjectPtr<AsyncContextFrame>();
}

return MakeBaseObject<AsyncContextFrame>(env, obj, current, key, value);
}

void AsyncContextFrame::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(New);
registry->Register(Get);
registry->Register(RunStatic);
registry->Register(Run);
registry->Register(Current);
registry->Register(Exchange);
}

void AsyncContextFrame::CreatePerContextProperties(Local<Object> target,
Local<Value> unused,
Local<Context> 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)
72 changes: 72 additions & 0 deletions src/async_context_frame.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#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 <cstdint>

namespace node {

class ExternalReferenceRegistry;

class AsyncContextFrame final : public BaseObject {
public:
AsyncContextFrame(Environment* env,
v8::Local<v8::Object> object,
v8::Local<v8::Object> current,
v8::Local<v8::Value> key,
v8::Local<v8::Value> value);

AsyncContextFrame() = delete;

static v8::Local<v8::Value> current(v8::Local<v8::Context> context);
static v8::Local<v8::Value> exchange(v8::Local<v8::Context> context,
v8::Local<v8::Value> value);
v8::MaybeLocal<v8::Value> run(v8::Local<v8::Context> context,
v8::Local<v8::Function> fn,
int argc,
v8::Local<v8::Value>* args);
v8::Local<v8::Value> get(v8::Local<v8::Context> context, v8::Local<v8::Value> key);

static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RunStatic(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Run(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Current(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Exchange(const v8::FunctionCallbackInfo<v8::Value>& args);

static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
IsolateData* isolate_data);
inline static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
static BaseObjectPtr<AsyncContextFrame> Create(Environment* env,
v8::Local<v8::Object> current,
v8::Local<v8::Value> key,
v8::Local<v8::Value> value);

static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static void CreatePerContextProperties(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> 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<v8::Object> parent_;
v8::Global<v8::Value> key_;
v8::Global<v8::Value> value_;
};

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_ASYNC_CONTEXT_FRAME_H_
Loading

0 comments on commit 8b30200

Please sign in to comment.