diff --git a/src/api/environment.cc b/src/api/environment.cc index 27eb47328c510c..cf10aa398f0744 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -713,4 +713,17 @@ ThreadId AllocateEnvironmentThreadId() { return ret; } +void DefaultProcessExitHandler(Environment* env, int exit_code) { + env->set_can_call_into_js(false); + env->stop_sub_worker_contexts(); + DisposePlatform(); + exit(exit_code); +} + + +void SetProcessExitHandler(Environment* env, + std::function&& handler) { + env->set_process_exit_handler(std::move(handler)); +} + } // namespace node diff --git a/src/env-inl.h b/src/env-inl.h index cb10b4d9554e23..0e3b2842e4d1ad 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -1221,6 +1221,11 @@ void Environment::set_main_utf16(std::unique_ptr str) { main_utf16_ = std::move(str); } +void Environment::set_process_exit_handler( + std::function&& handler) { + process_exit_handler_ = std::move(handler); +} + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) diff --git a/src/env.cc b/src/env.cc index f0b8b521f70363..b2e4721b96db1b 100644 --- a/src/env.cc +++ b/src/env.cc @@ -987,14 +987,7 @@ void Environment::Exit(int exit_code) { StackTrace::CurrentStackTrace( isolate(), stack_trace_limit(), StackTrace::kDetailed)); } - if (is_main_thread()) { - set_can_call_into_js(false); - stop_sub_worker_contexts(); - DisposePlatform(); - exit(exit_code); - } else { - worker_context()->Exit(exit_code); - } + process_exit_handler_(this, exit_code); } void Environment::stop_sub_worker_contexts() { diff --git a/src/env.h b/src/env.h index 0566c2d26dc4f2..13c562133ce44a 100644 --- a/src/env.h +++ b/src/env.h @@ -1270,6 +1270,8 @@ class Environment : public MemoryRetainer { std::shared_ptr); inline void set_main_utf16(std::unique_ptr); + inline void set_process_exit_handler( + std::function&& handler); private: inline void ThrowError(v8::Local (*fun)(v8::Local), @@ -1428,6 +1430,9 @@ class Environment : public MemoryRetainer { ArrayBufferAllocatorList; ArrayBufferAllocatorList* keep_alive_allocators_ = nullptr; + std::function process_exit_handler_ { + DefaultProcessExitHandler }; + template void ForEachBaseObject(T&& iterator); diff --git a/src/node.h b/src/node.h index cb3ff139278ecd..74fe59097ff673 100644 --- a/src/node.h +++ b/src/node.h @@ -452,6 +452,18 @@ NODE_EXTERN v8::MaybeLocal LoadEnvironment( std::unique_ptr inspector_parent_handle = {}); NODE_EXTERN void FreeEnvironment(Environment* env); +// Set a callback that is called when process.exit() is called from JS, +// overriding the default handler. +// It receives the Environment* instance and the exit code as arguments. +// This could e.g. call Stop(env); in order to terminate execution and stop +// the event loop. +// The default handler disposes of the global V8 platform instance, if one is +// being used, and calls exit(). +NODE_EXTERN void SetProcessExitHandler( + Environment* env, + std::function&& handler); +NODE_EXTERN void DefaultProcessExitHandler(Environment* env, int exit_code); + // This may return nullptr if context is not associated with a Node instance. NODE_EXTERN Environment* GetCurrentEnvironment(v8::Local context); diff --git a/src/node_worker.cc b/src/node_worker.cc index a61bf51f1c9a02..afa4a9a52d0192 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -321,6 +321,9 @@ void Worker::Run() { if (is_stopped()) return; CHECK_NOT_NULL(env_); env_->set_env_vars(std::move(env_vars_)); + SetProcessExitHandler(env_.get(), [this](Environment*, int exit_code) { + Exit(exit_code); + }); } { Mutex::ScopedLock lock(mutex_); @@ -430,9 +433,10 @@ void Worker::JoinThread() { MakeCallback(env()->onexit_string(), arraysize(args), args); } - // We cleared all libuv handles bound to this Worker above, - // the C++ object is no longer needed for anything now. - MakeWeak(); + // If we get here, the !thread_joined_ condition at the top of the function + // implies that the thread was running. In that case, its final action will + // be to schedule a callback on the parent thread which will delete this + // object, so there's nothing more to do here. } Worker::~Worker() { diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 88c1c2ab953608..33361c12280d9d 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -448,3 +448,20 @@ TEST_F(EnvironmentTest, InspectorMultipleEmbeddedEnvironments) { CHECK_EQ(from_inspector->IntegerValue(context).FromJust(), 42); } #endif // HAVE_INSPECTOR + +TEST_F(EnvironmentTest, ExitHandlerTest) { + const v8::HandleScope handle_scope(isolate_); + const Argv argv; + + int callback_calls = 0; + + Env env {handle_scope, argv}; + SetProcessExitHandler(*env, [&](node::Environment* env_, int exit_code) { + EXPECT_EQ(*env, env_); + EXPECT_EQ(exit_code, 42); + callback_calls++; + node::Stop(*env); + }); + node::LoadEnvironment(*env, "process.exit(42)").ToLocalChecked(); + EXPECT_EQ(callback_calls, 1); +}