diff --git a/src/env.cc b/src/env.cc index f618fca2e2d157..141991a9a5736a 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1411,9 +1411,19 @@ void Environment::RunAndClearInterrupts() { } } +bool Environment::HasNativeImmediates() const { + return native_immediates_.size() > 0 || + native_immediates_threadsafe_.size() > 0 || + native_immediates_interrupts_.size() > 0; +} + void Environment::RunAndClearNativeImmediates(bool only_refed) { TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "RunAndClearNativeImmediates"); + if (!HasNativeImmediates()) { + return; + } + HandleScope handle_scope(isolate_); // In case the Isolate is no longer accessible just use an empty Local. This // is not an issue for InternalCallbackScope as this case is already handled @@ -1586,6 +1596,9 @@ void Environment::CheckImmediate(uv_check_t* handle) { Environment* env = Environment::from_immediate_check_handle(handle); TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "CheckImmediate"); + if (env->immediate_info()->count() == 0 && !env->HasNativeImmediates()) + return; + HandleScope scope(env->isolate()); Context::Scope context_scope(env->context()); diff --git a/src/env.h b/src/env.h index 5fabc366c6e68b..2e644983ea1d0a 100644 --- a/src/env.h +++ b/src/env.h @@ -1034,6 +1034,7 @@ class Environment final : public MemoryRetainer { void RunAndClearNativeImmediates(bool only_refed = false); void RunAndClearInterrupts(); + bool HasNativeImmediates() const; uv_buf_t allocate_managed_buffer(const size_t suggested_size); std::unique_ptr release_managed_buffer(const uv_buf_t& buf); diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index ee9052a1dad4e4..59c71835499e41 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -401,6 +401,69 @@ TEST_F(EnvironmentTest, SetImmediateCleanup) { EXPECT_EQ(called_unref, 0); } +TEST_F(EnvironmentTest, RunAndClearNativeImmediatesSkipsEmptyScope) { + const v8::HandleScope handle_scope(isolate_); + const Argv argv; + Env env{handle_scope, argv}; + v8::Local context = env.context(); + + using IntVec = std::vector; + IntVec callback_calls; + v8::Local must_call = + v8::Function::New( + context, + [](const v8::FunctionCallbackInfo& info) { + IntVec* callback_calls = + static_cast(info.Data().As()->Value( + v8::kExternalPointerTypeTagDefault)); + callback_calls->push_back(info[0].As()->Value()); + }, + v8::External::New(isolate_, + static_cast(&callback_calls), + v8::kExternalPointerTypeTagDefault)) + .ToLocalChecked(); + context->Global() + ->Set(context, + v8::String::NewFromUtf8Literal(isolate_, "mustCall"), + must_call) + .Check(); + + v8::Local eval_in_env = + node::LoadEnvironment(*env, "return eval;") + .ToLocalChecked() + .As(); + + v8::Local queue_microtask = v8::String::NewFromUtf8Literal( + isolate_, "Promise.resolve().then(() => mustCall(1));"); + eval_in_env->Call(context, v8::Null(isolate_), 1, &queue_microtask) + .ToLocalChecked(); + EXPECT_TRUE(callback_calls.empty()); + (*env)->RunAndClearNativeImmediates(); + EXPECT_TRUE(callback_calls.empty()); + + context->GetMicrotaskQueue()->PerformCheckpoint(isolate_); + EXPECT_EQ(callback_calls, (IntVec{1})); + callback_calls.clear(); + + queue_microtask = v8::String::NewFromUtf8Literal( + isolate_, "Promise.resolve().then(() => mustCall(2));"); + eval_in_env->Call(context, v8::Null(isolate_), 1, &queue_microtask) + .ToLocalChecked(); + EXPECT_TRUE(callback_calls.empty()); + + bool native_immediate_called = false; + (*env)->SetImmediate( + [&](node::Environment* env_arg) { + EXPECT_EQ(env_arg, *env); + native_immediate_called = true; + }, + node::CallbackFlags::kRefed); + + (*env)->RunAndClearNativeImmediates(); + EXPECT_TRUE(native_immediate_called); + EXPECT_EQ(callback_calls, (IntVec{2})); +} + static char hello[] = "hello"; TEST_F(EnvironmentTest, BufferWithFreeCallbackIsDetached) {