Skip to content

Commit dc6cba4

Browse files
committed
env: skip empty native immediate callback scope
Signed-off-by: Gürgün Dayıoğlu <[email protected]>
1 parent fcff458 commit dc6cba4

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

src/env.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,15 @@ void Environment::RunAndClearInterrupts() {
14141414
void Environment::RunAndClearNativeImmediates(bool only_refed) {
14151415
TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment),
14161416
"RunAndClearNativeImmediates");
1417+
// Skip the callback scope on the common empty path. These are the only
1418+
// queues drained by this function. If a threadsafe immediate is queued
1419+
// concurrently after this check, uv_async_send() schedules another drain.
1420+
if (native_immediates_.size() == 0 &&
1421+
native_immediates_threadsafe_.size() == 0 &&
1422+
native_immediates_interrupts_.size() == 0) {
1423+
return;
1424+
}
1425+
14171426
HandleScope handle_scope(isolate_);
14181427
// In case the Isolate is no longer accessible just use an empty Local. This
14191428
// is not an issue for InternalCallbackScope as this case is already handled

test/cctest/test_environment.cc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,72 @@ TEST_F(EnvironmentTest, SetImmediateCleanup) {
401401
EXPECT_EQ(called_unref, 0);
402402
}
403403

404+
TEST_F(EnvironmentTest, RunAndClearNativeImmediatesSkipsEmptyScope) {
405+
const v8::HandleScope handle_scope(isolate_);
406+
const Argv argv;
407+
Env env {handle_scope, argv};
408+
v8::Local<v8::Context> context = env.context();
409+
410+
using IntVec = std::vector<int>;
411+
IntVec callback_calls;
412+
v8::Local<v8::Function> must_call =
413+
v8::Function::New(
414+
context,
415+
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
416+
IntVec* callback_calls =
417+
static_cast<IntVec*>(info.Data().As<v8::External>()->Value(
418+
v8::kExternalPointerTypeTagDefault));
419+
callback_calls->push_back(info[0].As<v8::Int32>()->Value());
420+
},
421+
v8::External::New(isolate_,
422+
static_cast<void*>(&callback_calls),
423+
v8::kExternalPointerTypeTagDefault))
424+
.ToLocalChecked();
425+
context->Global()->Set(
426+
context,
427+
v8::String::NewFromUtf8Literal(isolate_, "mustCall"),
428+
must_call).Check();
429+
430+
v8::Local<v8::Function> eval_in_env =
431+
node::LoadEnvironment(*env, "return eval;")
432+
.ToLocalChecked()
433+
.As<v8::Function>();
434+
435+
v8::Local<v8::Value> queue_microtask =
436+
v8::String::NewFromUtf8Literal(
437+
isolate_, "Promise.resolve().then(() => mustCall(1));");
438+
eval_in_env->Call(context,
439+
v8::Null(isolate_),
440+
1,
441+
&queue_microtask).ToLocalChecked();
442+
EXPECT_TRUE(callback_calls.empty());
443+
(*env)->RunAndClearNativeImmediates();
444+
EXPECT_TRUE(callback_calls.empty());
445+
446+
context->GetMicrotaskQueue()->PerformCheckpoint(isolate_);
447+
EXPECT_EQ(callback_calls, (IntVec { 1 }));
448+
callback_calls.clear();
449+
450+
queue_microtask =
451+
v8::String::NewFromUtf8Literal(
452+
isolate_, "Promise.resolve().then(() => mustCall(2));");
453+
eval_in_env->Call(context,
454+
v8::Null(isolate_),
455+
1,
456+
&queue_microtask).ToLocalChecked();
457+
EXPECT_TRUE(callback_calls.empty());
458+
459+
bool native_immediate_called = false;
460+
(*env)->SetImmediate([&](node::Environment* env_arg) {
461+
EXPECT_EQ(env_arg, *env);
462+
native_immediate_called = true;
463+
}, node::CallbackFlags::kRefed);
464+
465+
(*env)->RunAndClearNativeImmediates();
466+
EXPECT_TRUE(native_immediate_called);
467+
EXPECT_EQ(callback_calls, (IntVec { 2 }));
468+
}
469+
404470
static char hello[] = "hello";
405471

406472
TEST_F(EnvironmentTest, BufferWithFreeCallbackIsDetached) {

0 commit comments

Comments
 (0)