diff --git a/src/async_wrap.cc b/src/async_wrap.cc index bb15bf6adb1262..3748d542c95021 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -827,7 +827,8 @@ void AsyncWrap::EmitDestroy(Environment* env, double async_id) { // interrupt to get this Microtask scheduled as fast as possible. if (env->destroy_async_id_list()->size() == 16384) { env->RequestInterrupt([](Environment* env) { - env->isolate()->EnqueueMicrotask( + env->context()->GetMicrotaskQueue()->EnqueueMicrotask( + env->isolate(), [](void* arg) { DestroyAsyncIdsCallback(static_cast(arg)); }, env); diff --git a/src/node_contextify.cc b/src/node_contextify.cc index a69570400cd897..0accd99c0c4c5e 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -199,7 +199,9 @@ MaybeLocal ContextifyContext::CreateV8Context( object_template, {}, // global object {}, // deserialization callback - microtask_queue() ? microtask_queue().get() : nullptr); + microtask_queue() ? + microtask_queue().get() : + env->isolate()->GetCurrentContext()->GetMicrotaskQueue()); if (ctx.IsEmpty()) return MaybeLocal(); // Only partially initialize the context - the primordials are left out // and only initialized when necessary. diff --git a/src/node_task_queue.cc b/src/node_task_queue.cc index 926b27fcf2a05a..dff59d8dc77b99 100644 --- a/src/node_task_queue.cc +++ b/src/node_task_queue.cc @@ -96,7 +96,8 @@ static void EnqueueMicrotask(const FunctionCallbackInfo& args) { CHECK(args[0]->IsFunction()); - isolate->EnqueueMicrotask(args[0].As()); + isolate->GetCurrentContext()->GetMicrotaskQueue() + ->EnqueueMicrotask(isolate, args[0].As()); } static void RunMicrotasks(const FunctionCallbackInfo& args) { diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index baa03a5899aa25..620f43dc5e2913 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -611,3 +611,58 @@ TEST_F(NodeZeroIsolateTestFixture, CtrlCWithOnlySafeTerminationTest) { isolate->Dispose(); } #endif // _WIN32 + +TEST_F(EnvironmentTest, NestedMicrotaskQueue) { + const v8::HandleScope handle_scope(isolate_); + const Argv argv; + + std::unique_ptr queue = v8::MicrotaskQueue::New(isolate_); + v8::Local context = v8::Context::New( + isolate_, nullptr, {}, {}, {}, queue.get()); + node::InitializeContext(context); + v8::Context::Scope context_scope(context); + + int callback_calls = 0; + v8::Local must_call = v8::Function::New( + context, + [](const v8::FunctionCallbackInfo& info) { + int* callback_calls = + static_cast(info.Data().As()->Value()); + *callback_calls |= info[0].As()->Value(); + }, + v8::External::New(isolate_, static_cast(&callback_calls))) + .ToLocalChecked(); + context->Global()->Set( + context, + v8::String::NewFromUtf8Literal(isolate_, "mustCall"), + must_call).Check(); + + node::IsolateData* isolate_data = node::CreateIsolateData( + isolate_, &NodeTestFixture::current_loop, platform.get()); + CHECK_NE(nullptr, isolate_data); + + node::Environment* env = node::CreateEnvironment( + isolate_data, context, {}, {}); + CHECK_NE(nullptr, env); + + node::LoadEnvironment( + env, + "Promise.resolve().then(() => mustCall(1 << 0));\n" + "require('vm').runInNewContext(" + " 'Promise.resolve().then(() => mustCall(1 << 1))'," + " { mustCall }," + " { microtaskMode: 'afterEvaluate' }" + ");" + "require('vm').runInNewContext(" + " 'Promise.resolve().then(() => mustCall(1 << 2))'," + " { mustCall }" + ");").ToLocalChecked(); + EXPECT_EQ(callback_calls, 1 << 1); + isolate_->PerformMicrotaskCheckpoint(); + EXPECT_EQ(callback_calls, 1 << 1); + queue->PerformCheckpoint(isolate_); + EXPECT_EQ(callback_calls, (1 << 0) | (1 << 1) | (1 << 2)); + + node::FreeEnvironment(env); + node::FreeIsolateData(isolate_data); +}