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: https://github.com/nodejs/node/pull/30467#issuecomment-603689644

PR-URL: https://github.com/nodejs/node/pull/32531
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anna Henningsen
2020-03-28 02:46:41 +01:00
parent 7e664a554a
commit d812f16234
7 changed files with 60 additions and 11 deletions

View File

@@ -724,4 +724,17 @@ ThreadId AllocateEnvironmentThreadId() {
return ThreadId { next_thread_id++ };
}
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<void(Environment*, int)>&& handler) {
env->set_process_exit_handler(std::move(handler));
}
} // namespace node

View File

@@ -1279,6 +1279,11 @@ void Environment::set_main_utf16(std::unique_ptr<v8::String::Value> str) {
main_utf16_ = std::move(str);
}
void Environment::set_process_exit_handler(
std::function<void(Environment*, int)>&& 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)

View File

@@ -984,14 +984,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() {

View File

@@ -1257,6 +1257,8 @@ class Environment : public MemoryRetainer {
#endif // HAVE_INSPECTOR
inline void set_main_utf16(std::unique_ptr<v8::String::Value>);
inline void set_process_exit_handler(
std::function<void(Environment*, int)>&& handler);
private:
template <typename Fn>
@@ -1459,6 +1461,9 @@ class Environment : public MemoryRetainer {
int64_t base_object_count_ = 0;
std::atomic_bool is_stopping_ { false };
std::function<void(Environment*, int)> process_exit_handler_ {
DefaultProcessExitHandler };
template <typename T>
void ForEachBaseObject(T&& iterator);

View File

@@ -473,6 +473,18 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
std::unique_ptr<InspectorParentHandle> 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<void(Environment*, int)>&& 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<v8::Context> context);

View File

@@ -311,6 +311,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_);
@@ -420,9 +423,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() {

View File

@@ -447,3 +447,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);
}