Skip to content

Commit 6c90b7f

Browse files
gireeshpunathiltargos
authored andcommitted
src: shutdown node in-flight
This commit introduces a `node::Stop()` API. An identified use case for embedders is their ability to tear down Node while it is still running (event loop contain pending events) Here the assumptions are that (i) embedders do not wish to resort to JS routines to initiate shutdown (ii) embedders have the Environment handle handy. (iii) embedders stop Node through a second thread. Fixes: #19365 Refs: nodejs/user-feedback#51 PR-URL: #21283 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Michael Dawson <Michael_Dawson@ca.ibm.com>
1 parent cfa152b commit 6c90b7f

11 files changed

+154
-109
lines changed

src/api/callback.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ void InternalCallbackScope::Close() {
8282
HandleScope handle_scope(env_->isolate());
8383

8484
if (!env_->can_call_into_js()) return;
85-
if (failed_ && !env_->is_main_thread() && env_->is_stopping_worker()) {
85+
if (failed_ && !env_->is_main_thread() && env_->is_stopping()) {
8686
env_->async_hooks()->clear_async_id_stack();
8787
}
8888

src/api/environment.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static bool ShouldAbortOnUncaughtException(Isolate* isolate) {
3737
DebugSealHandleScope scope(isolate);
3838
Environment* env = Environment::GetCurrent(isolate);
3939
return env != nullptr &&
40-
(env->is_main_thread() || !env->is_stopping_worker()) &&
40+
(env->is_main_thread() || !env->is_stopping()) &&
4141
env->should_abort_on_uncaught_toggle()[0] &&
4242
!env->inside_should_not_abort_on_uncaught_scope();
4343
}

src/env-inl.h

+3-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
#include "v8.h"
3333
#include "node_perf_common.h"
3434
#include "node_context_data.h"
35-
#include "node_worker.h"
3635

3736
#include <cstddef>
3837
#include <cstdint>
@@ -661,7 +660,7 @@ void Environment::SetUnrefImmediate(native_immediate_callback cb,
661660
}
662661

663662
inline bool Environment::can_call_into_js() const {
664-
return can_call_into_js_ && (is_main_thread() || !is_stopping_worker());
663+
return can_call_into_js_ && !is_stopping();
665664
}
666665

667666
inline void Environment::set_can_call_into_js(bool can_call_into_js) {
@@ -709,9 +708,8 @@ inline void Environment::remove_sub_worker_context(worker::Worker* context) {
709708
sub_worker_contexts_.erase(context);
710709
}
711710

712-
inline bool Environment::is_stopping_worker() const {
713-
CHECK(!is_main_thread());
714-
return worker_context_->is_stopped();
711+
inline bool Environment::is_stopping() const {
712+
return thread_stopper_.IsStopped();
715713
}
716714

717715
inline performance::performance_state* Environment::performance_state() {

src/env.cc

+62
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,14 @@ void Environment::InitializeLibuv(bool start_profiler_idle_notifier) {
350350
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
351351
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
352352

353+
GetAsyncRequest()->Install(
354+
this, static_cast<void*>(this), [](uv_async_t* handle) {
355+
Environment* env = static_cast<Environment*>(handle->data);
356+
uv_stop(env->event_loop());
357+
});
358+
GetAsyncRequest()->SetStopped(false);
359+
uv_unref(reinterpret_cast<uv_handle_t*>(GetAsyncRequest()->GetHandle()));
360+
353361
// Register clean-up cb to be called to clean up the handles
354362
// when the environment is freed, note that they are not cleaned in
355363
// the one environment per process setup, but will be called in
@@ -365,6 +373,12 @@ void Environment::InitializeLibuv(bool start_profiler_idle_notifier) {
365373
uv_key_set(&thread_local_env, this);
366374
}
367375

376+
void Environment::ExitEnv() {
377+
set_can_call_into_js(false);
378+
GetAsyncRequest()->Stop();
379+
isolate_->TerminateExecution();
380+
}
381+
368382
MaybeLocal<Object> Environment::ProcessCliArgs(
369383
const std::vector<std::string>& args,
370384
const std::vector<std::string>& exec_args) {
@@ -529,6 +543,7 @@ void Environment::RunCleanup() {
529543
started_cleanup_ = true;
530544
TraceEventScope trace_scope(TRACING_CATEGORY_NODE1(environment),
531545
"RunCleanup", this);
546+
GetAsyncRequest()->Uninstall();
532547
CleanupHandles();
533548

534549
while (!cleanup_hooks_.empty()) {
@@ -961,6 +976,53 @@ char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
961976
return new_data;
962977
}
963978

979+
void AsyncRequest::Install(Environment* env, void* data, uv_async_cb target) {
980+
Mutex::ScopedLock lock(mutex_);
981+
env_ = env;
982+
async_ = new uv_async_t;
983+
async_->data = data;
984+
CHECK_EQ(uv_async_init(env_->event_loop(), async_, target), 0);
985+
}
986+
987+
void AsyncRequest::Uninstall() {
988+
Mutex::ScopedLock lock(mutex_);
989+
if (async_ != nullptr) {
990+
env_->CloseHandle(async_, [](uv_async_t* async) { delete async; });
991+
async_ = nullptr;
992+
}
993+
}
994+
995+
void AsyncRequest::Stop() {
996+
Mutex::ScopedLock lock(mutex_);
997+
stop_ = true;
998+
if (async_ != nullptr) uv_async_send(async_);
999+
}
1000+
1001+
void AsyncRequest::SetStopped(bool flag) {
1002+
Mutex::ScopedLock lock(mutex_);
1003+
stop_ = flag;
1004+
}
1005+
1006+
bool AsyncRequest::IsStopped() const {
1007+
Mutex::ScopedLock lock(mutex_);
1008+
return stop_;
1009+
}
1010+
1011+
uv_async_t* AsyncRequest::GetHandle() {
1012+
Mutex::ScopedLock lock(mutex_);
1013+
return async_;
1014+
}
1015+
1016+
void AsyncRequest::MemoryInfo(MemoryTracker* tracker) const {
1017+
Mutex::ScopedLock lock(mutex_);
1018+
if (async_ != nullptr) tracker->TrackField("async_request", *async_);
1019+
}
1020+
1021+
AsyncRequest::~AsyncRequest() {
1022+
Mutex::ScopedLock lock(mutex_);
1023+
CHECK_NULL(async_);
1024+
}
1025+
9641026
// Not really any better place than env.cc at this moment.
9651027
void BaseObject::DeleteMe(void* data) {
9661028
BaseObject* self = static_cast<BaseObject*>(data);

src/env.h

+28-1
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,27 @@ struct AllocatedBuffer {
508508
friend class Environment;
509509
};
510510

511+
class AsyncRequest : public MemoryRetainer {
512+
public:
513+
AsyncRequest() {}
514+
~AsyncRequest();
515+
void Install(Environment* env, void* data, uv_async_cb target);
516+
void Uninstall();
517+
void Stop();
518+
void SetStopped(bool flag);
519+
bool IsStopped() const;
520+
uv_async_t* GetHandle();
521+
void MemoryInfo(MemoryTracker* tracker) const override;
522+
SET_MEMORY_INFO_NAME(AsyncRequest)
523+
SET_SELF_SIZE(AsyncRequest)
524+
525+
private:
526+
Environment* env_;
527+
uv_async_t* async_ = nullptr;
528+
mutable Mutex mutex_;
529+
bool stop_ = true;
530+
};
531+
511532
class Environment {
512533
public:
513534
class AsyncHooks {
@@ -692,6 +713,7 @@ class Environment {
692713
void RegisterHandleCleanups();
693714
void CleanupHandles();
694715
void Exit(int code);
716+
void ExitEnv();
695717

696718
// Register clean-up cb to be called on environment destruction.
697719
inline void RegisterHandleCleanup(uv_handle_t* handle,
@@ -847,7 +869,7 @@ class Environment {
847869
inline void add_sub_worker_context(worker::Worker* context);
848870
inline void remove_sub_worker_context(worker::Worker* context);
849871
void stop_sub_worker_contexts();
850-
inline bool is_stopping_worker() const;
872+
inline bool is_stopping() const;
851873

852874
inline void ThrowError(const char* errmsg);
853875
inline void ThrowTypeError(const char* errmsg);
@@ -1021,6 +1043,7 @@ class Environment {
10211043
inline ExecutionMode execution_mode() { return execution_mode_; }
10221044

10231045
inline void set_execution_mode(ExecutionMode mode) { execution_mode_ = mode; }
1046+
inline AsyncRequest* GetAsyncRequest() { return &thread_stopper_; }
10241047

10251048
private:
10261049
inline void CreateImmediate(native_immediate_callback cb,
@@ -1177,6 +1200,10 @@ class Environment {
11771200
uint64_t cleanup_hook_counter_ = 0;
11781201
bool started_cleanup_ = false;
11791202

1203+
// A custom async abstraction (a pair of async handle and a state variable)
1204+
// Used by embedders to shutdown running Node instance.
1205+
AsyncRequest thread_stopper_;
1206+
11801207
static void EnvPromiseHook(v8::PromiseHookType type,
11811208
v8::Local<v8::Promise> promise,
11821209
v8::Local<v8::Value> parent);

src/module_wrap.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
302302

303303
// Convert the termination exception into a regular exception.
304304
if (timed_out || received_signal) {
305-
if (!env->is_main_thread() && env->is_stopping_worker())
305+
if (!env->is_main_thread() && env->is_stopping())
306306
return;
307307
env->isolate()->CancelTerminateExecution();
308308
// It is possible that execution was terminated by another timeout in

src/node.cc

+7-3
Original file line numberDiff line numberDiff line change
@@ -824,15 +824,14 @@ inline int StartNodeWithIsolate(Isolate* isolate,
824824
per_process::v8_platform.DrainVMTasks(isolate);
825825

826826
more = uv_loop_alive(env.event_loop());
827-
if (more)
828-
continue;
827+
if (more && !env.GetAsyncRequest()->IsStopped()) continue;
829828

830829
RunBeforeExit(&env);
831830

832831
// Emit `beforeExit` if the loop became alive either after emitting
833832
// event, or after running some callbacks.
834833
more = uv_loop_alive(env.event_loop());
835-
} while (more == true);
834+
} while (more == true && !env.GetAsyncRequest()->IsStopped());
836835
env.performance_state()->Mark(
837836
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
838837
}
@@ -969,6 +968,11 @@ int Start(int argc, char** argv) {
969968
return exit_code;
970969
}
971970

971+
int Stop(Environment* env) {
972+
env->ExitEnv();
973+
return 0;
974+
}
975+
972976
} // namespace node
973977

974978
#if !HAVE_INSPECTOR

src/node.h

+7-3
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,17 @@ typedef intptr_t ssize_t;
199199

200200
namespace node {
201201

202+
class IsolateData;
203+
class Environment;
204+
202205
// TODO(addaleax): Officially deprecate this and replace it with something
203206
// better suited for a public embedder API.
204207
NODE_EXTERN int Start(int argc, char* argv[]);
205208

209+
// Tear down Node.js while it is running (there are active handles
210+
// in the loop and / or actively executing JavaScript code).
211+
NODE_EXTERN int Stop(Environment* env);
212+
206213
// TODO(addaleax): Officially deprecate this and replace it with something
207214
// better suited for a public embedder API.
208215
NODE_EXTERN void Init(int* argc,
@@ -215,9 +222,6 @@ class ArrayBufferAllocator;
215222
NODE_EXTERN ArrayBufferAllocator* CreateArrayBufferAllocator();
216223
NODE_EXTERN void FreeArrayBufferAllocator(ArrayBufferAllocator* allocator);
217224

218-
class IsolateData;
219-
class Environment;
220-
221225
class NODE_EXTERN MultiIsolatePlatform : public v8::Platform {
222226
public:
223227
~MultiIsolatePlatform() override { }

src/node_contextify.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ bool ContextifyScript::EvalMachine(Environment* env,
924924

925925
// Convert the termination exception into a regular exception.
926926
if (timed_out || received_signal) {
927-
if (!env->is_main_thread() && env->is_stopping_worker())
927+
if (!env->is_main_thread() && env->is_stopping())
928928
return false;
929929
env->isolate()->CancelTerminateExecution();
930930
// It is possible that execution was terminated by another timeout in

0 commit comments

Comments
 (0)