Skip to content

Commit 3c814e8

Browse files
codebytereaduh95
authored andcommitted
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 0b6d945 commit 3c814e8

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
@@ -514,6 +514,9 @@ void FreeEnvironment(Environment* env) {
514514
Isolate* isolate = env->isolate();
515515
Isolate::DisallowJavascriptExecutionScope disallow_js(isolate,
516516
Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE);
517+
// A termination requested by Stop() targets this Environment; if no JS ran
518+
// since, it is still pending and must not hit the isolate's next user.
519+
isolate->CancelTerminateExecution();
517520
{
518521
HandleScope handle_scope(isolate); // For env->context().
519522
Context::Scope context_scope(env->context());

‎src/node_worker.cc‎

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

315315
DeleteFnPtr<Environment, FreeEnvironment> env_;
316316
auto cleanup_env = OnScopeLeave([&]() {
317-
// TODO(addaleax): This call is harmless but should not be necessary.
318-
// Figure out why V8 is raising a DCHECK() here without it
319-
// (in test/parallel/test-async-hooks-worker-asyncfn-terminate-4.js).
320-
isolate_->CancelTerminateExecution();
321-
322317
if (!env_) return;
323318
env_->set_can_call_into_js(false);
324319

‎test/cctest/test_environment.cc‎

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

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

0 commit comments

Comments
 (0)