Skip to content

Commit d812f16

Browse files
committed
embedding: provide hook for custom process.exit() behaviour
Embedders may not want to terminate the process when `process.exit()` is called. This provides a hook for more flexible handling of that situation. Refs: #30467 (comment) PR-URL: #32531 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 7e664a5 commit d812f16

File tree

7 files changed

+60
-11
lines changed

7 files changed

+60
-11
lines changed

src/api/environment.cc

+13
Original file line numberDiff line numberDiff line change
@@ -724,4 +724,17 @@ ThreadId AllocateEnvironmentThreadId() {
724724
return ThreadId { next_thread_id++ };
725725
}
726726

727+
void DefaultProcessExitHandler(Environment* env, int exit_code) {
728+
env->set_can_call_into_js(false);
729+
env->stop_sub_worker_contexts();
730+
DisposePlatform();
731+
exit(exit_code);
732+
}
733+
734+
735+
void SetProcessExitHandler(Environment* env,
736+
std::function<void(Environment*, int)>&& handler) {
737+
env->set_process_exit_handler(std::move(handler));
738+
}
739+
727740
} // namespace node

src/env-inl.h

+5
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,11 @@ void Environment::set_main_utf16(std::unique_ptr<v8::String::Value> str) {
12791279
main_utf16_ = std::move(str);
12801280
}
12811281

1282+
void Environment::set_process_exit_handler(
1283+
std::function<void(Environment*, int)>&& handler) {
1284+
process_exit_handler_ = std::move(handler);
1285+
}
1286+
12821287
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
12831288
#define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName)
12841289
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)

src/env.cc

+1-8
Original file line numberDiff line numberDiff line change
@@ -984,14 +984,7 @@ void Environment::Exit(int exit_code) {
984984
StackTrace::CurrentStackTrace(
985985
isolate(), stack_trace_limit(), StackTrace::kDetailed));
986986
}
987-
if (is_main_thread()) {
988-
set_can_call_into_js(false);
989-
stop_sub_worker_contexts();
990-
DisposePlatform();
991-
exit(exit_code);
992-
} else {
993-
worker_context()->Exit(exit_code);
994-
}
987+
process_exit_handler_(this, exit_code);
995988
}
996989

997990
void Environment::stop_sub_worker_contexts() {

src/env.h

+5
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,8 @@ class Environment : public MemoryRetainer {
12571257
#endif // HAVE_INSPECTOR
12581258

12591259
inline void set_main_utf16(std::unique_ptr<v8::String::Value>);
1260+
inline void set_process_exit_handler(
1261+
std::function<void(Environment*, int)>&& handler);
12601262

12611263
private:
12621264
template <typename Fn>
@@ -1459,6 +1461,9 @@ class Environment : public MemoryRetainer {
14591461
int64_t base_object_count_ = 0;
14601462
std::atomic_bool is_stopping_ { false };
14611463

1464+
std::function<void(Environment*, int)> process_exit_handler_ {
1465+
DefaultProcessExitHandler };
1466+
14621467
template <typename T>
14631468
void ForEachBaseObject(T&& iterator);
14641469

src/node.h

+12
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,18 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
473473
std::unique_ptr<InspectorParentHandle> inspector_parent_handle = {});
474474
NODE_EXTERN void FreeEnvironment(Environment* env);
475475

476+
// Set a callback that is called when process.exit() is called from JS,
477+
// overriding the default handler.
478+
// It receives the Environment* instance and the exit code as arguments.
479+
// This could e.g. call Stop(env); in order to terminate execution and stop
480+
// the event loop.
481+
// The default handler disposes of the global V8 platform instance, if one is
482+
// being used, and calls exit().
483+
NODE_EXTERN void SetProcessExitHandler(
484+
Environment* env,
485+
std::function<void(Environment*, int)>&& handler);
486+
NODE_EXTERN void DefaultProcessExitHandler(Environment* env, int exit_code);
487+
476488
// This may return nullptr if context is not associated with a Node instance.
477489
NODE_EXTERN Environment* GetCurrentEnvironment(v8::Local<v8::Context> context);
478490

src/node_worker.cc

+7-3
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ void Worker::Run() {
311311
if (is_stopped()) return;
312312
CHECK_NOT_NULL(env_);
313313
env_->set_env_vars(std::move(env_vars_));
314+
SetProcessExitHandler(env_.get(), [this](Environment*, int exit_code) {
315+
Exit(exit_code);
316+
});
314317
}
315318
{
316319
Mutex::ScopedLock lock(mutex_);
@@ -420,9 +423,10 @@ void Worker::JoinThread() {
420423
MakeCallback(env()->onexit_string(), arraysize(args), args);
421424
}
422425

423-
// We cleared all libuv handles bound to this Worker above,
424-
// the C++ object is no longer needed for anything now.
425-
MakeWeak();
426+
// If we get here, the !thread_joined_ condition at the top of the function
427+
// implies that the thread was running. In that case, its final action will
428+
// be to schedule a callback on the parent thread which will delete this
429+
// object, so there's nothing more to do here.
426430
}
427431

428432
Worker::~Worker() {

test/cctest/test_environment.cc

+17
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,20 @@ TEST_F(EnvironmentTest, InspectorMultipleEmbeddedEnvironments) {
447447
CHECK_EQ(from_inspector->IntegerValue(context).FromJust(), 42);
448448
}
449449
#endif // HAVE_INSPECTOR
450+
451+
TEST_F(EnvironmentTest, ExitHandlerTest) {
452+
const v8::HandleScope handle_scope(isolate_);
453+
const Argv argv;
454+
455+
int callback_calls = 0;
456+
457+
Env env {handle_scope, argv};
458+
SetProcessExitHandler(*env, [&](node::Environment* env_, int exit_code) {
459+
EXPECT_EQ(*env, env_);
460+
EXPECT_EQ(exit_code, 42);
461+
callback_calls++;
462+
node::Stop(*env);
463+
});
464+
node::LoadEnvironment(*env, "process.exit(42)").ToLocalChecked();
465+
EXPECT_EQ(callback_calls, 1);
466+
}

0 commit comments

Comments
 (0)