Skip to content

Commit 0de4fcc

Browse files
committed
src: fix Stop() terminating the next Environment on the isolate
After `Stop(env)`, freeing the Environment and creating another one on the same isolate failed whenever no JavaScript ran in between: the new Environment's first script was terminated before it started. That is the normal case when `Stop()` is called from the process exit handler for an uncaught exception, or by an embedder while the loop is idle. `Stop()` calls `isolate->TerminateExecution()` unless `kDoNotTerminateIsolate` is set, and V8 only clears that request the next time JavaScript runs, so it outlived the Environment it was meant for. Cancel a pending termination when the Environment it was meant for is freed. `Worker::Run()` already did this by hand before freeing its Environment, with a TODO asking why V8 hit a DCHECK without it; this is why, and that call now happens in `FreeEnvironment()`. Refs: #33347 Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65819 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
1 parent 752a12a commit 0de4fcc

3 files changed

Lines changed: 24 additions & 5 deletions

File tree

src/api/environment.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,9 @@ void FreeEnvironment(Environment* env) {
518518
Isolate* isolate = env->isolate();
519519
Isolate::DisallowJavascriptExecutionScope disallow_js(isolate,
520520
Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
521+
// A termination requested by Stop() targets this Environment; if no JS ran
522+
// since, it is still pending and must not hit the isolate's next user.
523+
isolate->CancelTerminateExecution();
521524
{
522525
HandleScope handle_scope(isolate); // For env->context().
523526
Context::Scope context_scope(env->context());

src/node_worker.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,6 @@ void Worker::Run() {
326326

327327
DeleteFnPtr<Environment, FreeEnvironment> env_;
328328
auto cleanup_env = OnScopeLeave([&]() {
329-
// TODO(addaleax): This call is harmless but should not be necessary.
330-
// Figure out why V8 is raising a DCHECK() here without it
331-
// (in test/parallel/test-async-hooks-worker-asyncfn-terminate-4.js).
332-
isolate_->CancelTerminateExecution();
333-
334329
if (!env_) return;
335330
env_->set_can_call_into_js(false);
336331

test/cctest/test_environment.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,27 @@ TEST_F(EnvironmentTest, WorkerInEnvironmentWithoutSnapshot) {
367367
EXPECT_EQ(node::SpinEventLoop(*env).FromJust(), 0);
368368
}
369369

370+
TEST_F(EnvironmentTest, StopFromExitHandlerDoesNotLeakIntoNextEnvironment) {
371+
const v8::HandleScope handle_scope(isolate_);
372+
const Argv argv;
373+
{
374+
Env env{handle_scope, argv};
375+
node::SetProcessExitHandler(
376+
*env, [](node::Environment* env_, int) { node::Stop(env_); });
377+
// The uncaught exception runs the exit handler from C++ and does not
378+
// re-enter JS afterwards, so nothing consumes the termination request.
379+
EXPECT_TRUE(
380+
node::LoadEnvironment(*env, "throw new Error('uncaught')").IsEmpty());
381+
EXPECT_TRUE(node::SpinEventLoop(*env).IsNothing());
382+
}
383+
{
384+
Env env{handle_scope, argv, node::EnvironmentFlags::kNoCreateInspector};
385+
v8::Local<v8::Value> result =
386+
node::LoadEnvironment(*env, "return 42;").ToLocalChecked();
387+
EXPECT_EQ(result->Int32Value(env.context()).FromJust(), 42);
388+
}
389+
}
390+
370391
TEST_F(EnvironmentTest, NoEnvironmentSanity) {
371392
const v8::HandleScope handle_scope(isolate_);
372393
v8::Local<v8::Context> context = v8::Context::New(isolate_);

0 commit comments

Comments
 (0)