Skip to content

Commit 00add61

Browse files
codebytereaduh95
authored andcommitted
node-api: enter env context for async callbacks
`uvimpl::Work::AfterThreadPoolWork()` and the thread-safe function's `DispatchOne()` and `Finalize()` opened an `AsyncResource::CallbackScope` with only a `HandleScope`. `InternalCallbackScope` expects the resource's environment context to be entered and otherwise asserts that `Environment::GetCurrent(isolate)` is that environment, so with two environments on one isolate an addon's async work completion or thread-safe function call aborted the process whenever the other environment's context was current when the loop ran the callback. Enter the node-api env's context first, as `CallFinalizer()` and the zlib and WebCrypto thread pool callbacks already do. In `Finalize()` the scope covers only the finalizer call, since `MaybeDelete()` can free the env whose persistent handle `context()` returns. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65406 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 211ec5a commit 00add61

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

src/node_api.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ class ThreadSafeFunction {
451451

452452
if (popped_value) {
453453
v8::HandleScope scope(env->isolate);
454+
v8::Context::Scope context_scope(env->context());
454455
AsyncResource::CallbackScope cb_scope(&*async_resource);
455456
napi_value js_callback = nullptr;
456457
if (!ref.IsEmpty()) {
@@ -469,6 +470,7 @@ class ThreadSafeFunction {
469470
v8::HandleScope scope(env->isolate);
470471
EmptyQueue();
471472
if (finalize_cb) {
473+
v8::Context::Scope context_scope(env->context());
472474
AsyncResource::CallbackScope cb_scope(&*async_resource);
473475
env->CallFinalizer<false>(finalize_cb, finalize_data, context);
474476
}
@@ -1236,6 +1238,7 @@ class Work : public node::AsyncResource, public node::ThreadPoolWork {
12361238
// Establish a handle scope here so that every callback doesn't have to.
12371239
// Also it is needed for the exception-handling below.
12381240
v8::HandleScope scope(_env->isolate);
1241+
v8::Context::Scope context_scope(_env->context());
12391242

12401243
CallbackScope callback_scope(this);
12411244

test/cctest/test_node_api.cc

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,109 @@ TEST_F(NodeApiTest, CreateNodeApiEnv) {
4040
node_napi_env internal_env = reinterpret_cast<node_napi_env>(addon_env);
4141
EXPECT_EQ(internal_env->node_env(), env);
4242
}
43+
44+
namespace {
45+
46+
struct ContextCheckState {
47+
napi_async_work work = nullptr;
48+
bool complete_called = false;
49+
bool complete_in_own_context = false;
50+
bool call_js_called = false;
51+
bool call_js_in_own_context = false;
52+
bool tsfn_finalize_called = false;
53+
bool tsfn_finalize_in_own_context = false;
54+
};
55+
56+
bool InOwnContext(napi_env env) {
57+
node_napi_env internal_env = reinterpret_cast<node_napi_env>(env);
58+
return internal_env->isolate->GetCurrentContext() == internal_env->context();
59+
}
60+
61+
} // namespace
62+
63+
TEST_F(NodeApiTest, AsyncCallbacksEnterOwnContext) {
64+
const v8::HandleScope handle_scope(isolate_);
65+
Argv argv;
66+
67+
Env env1{handle_scope, argv};
68+
node::LoadEnvironment(*env1, "");
69+
Env env2{handle_scope, argv, node::EnvironmentFlags::kNoFlags};
70+
node::LoadEnvironment(*env2, "");
71+
ASSERT_EQ(isolate_->GetCurrentContext(), env2.context());
72+
73+
ContextCheckState state;
74+
{
75+
v8::Context::Scope context_scope(env1.context());
76+
napi_addon_register_func init = [](napi_env env, napi_value exports) {
77+
addon_env = env;
78+
return exports;
79+
};
80+
addon_env = nullptr;
81+
napi_module_register_by_symbol(Object::New(isolate_),
82+
Object::New(isolate_),
83+
env1.context(),
84+
init,
85+
NAPI_VERSION);
86+
ASSERT_NE(addon_env, nullptr);
87+
88+
napi_value resource_name;
89+
ASSERT_EQ(napi_create_string_utf8(
90+
addon_env, "cctest", NAPI_AUTO_LENGTH, &resource_name),
91+
napi_ok);
92+
93+
ASSERT_EQ(napi_create_async_work(
94+
addon_env,
95+
nullptr,
96+
resource_name,
97+
[](napi_env env, void* data) {},
98+
[](napi_env env, napi_status status, void* data) {
99+
auto* state = static_cast<ContextCheckState*>(data);
100+
state->complete_called = true;
101+
state->complete_in_own_context = InOwnContext(env);
102+
napi_delete_async_work(env, state->work);
103+
},
104+
&state,
105+
&state.work),
106+
napi_ok);
107+
ASSERT_EQ(napi_queue_async_work(addon_env, state.work), napi_ok);
108+
109+
napi_threadsafe_function tsfn;
110+
ASSERT_EQ(napi_create_threadsafe_function(
111+
addon_env,
112+
nullptr,
113+
nullptr,
114+
resource_name,
115+
0,
116+
1,
117+
&state,
118+
[](napi_env env, void* finalize_data, void* hint) {
119+
auto* state =
120+
static_cast<ContextCheckState*>(finalize_data);
121+
state->tsfn_finalize_called = true;
122+
state->tsfn_finalize_in_own_context = InOwnContext(env);
123+
},
124+
&state,
125+
[](napi_env env, napi_value cb, void* context, void* data) {
126+
auto* state = static_cast<ContextCheckState*>(context);
127+
state->call_js_called = true;
128+
state->call_js_in_own_context = InOwnContext(env);
129+
},
130+
&tsfn),
131+
napi_ok);
132+
ASSERT_EQ(napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_blocking),
133+
napi_ok);
134+
ASSERT_EQ(napi_release_threadsafe_function(tsfn, napi_tsfn_release),
135+
napi_ok);
136+
}
137+
138+
ASSERT_EQ(isolate_->GetCurrentContext(), env2.context());
139+
uv_run(&current_loop, UV_RUN_DEFAULT);
140+
141+
EXPECT_TRUE(state.complete_called);
142+
EXPECT_TRUE(state.complete_in_own_context);
143+
EXPECT_TRUE(state.call_js_called);
144+
EXPECT_TRUE(state.call_js_in_own_context);
145+
EXPECT_TRUE(state.tsfn_finalize_called);
146+
EXPECT_TRUE(state.tsfn_finalize_in_own_context);
147+
EXPECT_EQ(isolate_->GetCurrentContext(), env2.context());
148+
}

0 commit comments

Comments
 (0)