Skip to content

Commit

Permalink
embedding: provide hook for custom process.exit() behaviour
Browse files Browse the repository at this point in the history
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)

Backport-PR-URL: #35241
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>
  • Loading branch information
addaleax committed Sep 23, 2020
1 parent 61eec0c commit ff0a036
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 11 deletions.
13 changes: 13 additions & 0 deletions src/api/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(Environment*, int)>&& handler) {
env->set_process_exit_handler(std::move(handler));
}

} // namespace node
5 changes: 5 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,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)
Expand Down
9 changes: 1 addition & 8 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
5 changes: 5 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,8 @@ class Environment : public MemoryRetainer {
std::shared_ptr<v8::ArrayBuffer::Allocator>);

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

private:
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
Expand Down Expand Up @@ -1428,6 +1430,9 @@ class Environment : public MemoryRetainer {
ArrayBufferAllocatorList;
ArrayBufferAllocatorList* keep_alive_allocators_ = nullptr;

std::function<void(Environment*, int)> process_exit_handler_ {
DefaultProcessExitHandler };

template <typename T>
void ForEachBaseObject(T&& iterator);

Expand Down
12 changes: 12 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,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);

Expand Down
10 changes: 7 additions & 3 deletions src/node_worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
Expand Down Expand Up @@ -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() {
Expand Down
17 changes: 17 additions & 0 deletions test/cctest/test_environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit ff0a036

Please sign in to comment.