Skip to content

Commit d35af56

Browse files
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: nodejs#19365 Refs: nodejs/user-feedback#51 PR-URL: nodejs#21283 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Richard Lau <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent 22de2cf commit d35af56

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
@@ -40,7 +40,7 @@ static bool ShouldAbortOnUncaughtException(Isolate* isolate) {
4040
DebugSealHandleScope scope(isolate);
4141
Environment* env = Environment::GetCurrent(isolate);
4242
return env != nullptr &&
43-
(env->is_main_thread() || !env->is_stopping_worker()) &&
43+
(env->is_main_thread() || !env->is_stopping()) &&
4444
env->should_abort_on_uncaught_toggle()[0] &&
4545
!env->inside_should_not_abort_on_uncaught_scope();
4646
}

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
@@ -340,6 +340,14 @@ void Environment::InitializeLibuv(bool start_profiler_idle_notifier) {
340340
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
341341
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
342342

343+
GetAsyncRequest()->Install(
344+
this, static_cast<void*>(this), [](uv_async_t* handle) {
345+
Environment* env = static_cast<Environment*>(handle->data);
346+
uv_stop(env->event_loop());
347+
});
348+
GetAsyncRequest()->SetStopped(false);
349+
uv_unref(reinterpret_cast<uv_handle_t*>(GetAsyncRequest()->GetHandle()));
350+
343351
// Register clean-up cb to be called to clean up the handles
344352
// when the environment is freed, note that they are not cleaned in
345353
// the one environment per process setup, but will be called in
@@ -355,6 +363,12 @@ void Environment::InitializeLibuv(bool start_profiler_idle_notifier) {
355363
uv_key_set(&thread_local_env, this);
356364
}
357365

366+
void Environment::ExitEnv() {
367+
set_can_call_into_js(false);
368+
GetAsyncRequest()->Stop();
369+
isolate_->TerminateExecution();
370+
}
371+
358372
MaybeLocal<Object> Environment::ProcessCliArgs(
359373
const std::vector<std::string>& args,
360374
const std::vector<std::string>& exec_args) {
@@ -519,6 +533,7 @@ void Environment::RunCleanup() {
519533
started_cleanup_ = true;
520534
TraceEventScope trace_scope(TRACING_CATEGORY_NODE1(environment),
521535
"RunCleanup", this);
536+
GetAsyncRequest()->Uninstall();
522537
CleanupHandles();
523538

524539
while (!cleanup_hooks_.empty()) {
@@ -932,6 +947,53 @@ char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
932947
return new_data;
933948
}
934949

950+
void AsyncRequest::Install(Environment* env, void* data, uv_async_cb target) {
951+
Mutex::ScopedLock lock(mutex_);
952+
env_ = env;
953+
async_ = new uv_async_t;
954+
async_->data = data;
955+
CHECK_EQ(uv_async_init(env_->event_loop(), async_, target), 0);
956+
}
957+
958+
void AsyncRequest::Uninstall() {
959+
Mutex::ScopedLock lock(mutex_);
960+
if (async_ != nullptr) {
961+
env_->CloseHandle(async_, [](uv_async_t* async) { delete async; });
962+
async_ = nullptr;
963+
}
964+
}
965+
966+
void AsyncRequest::Stop() {
967+
Mutex::ScopedLock lock(mutex_);
968+
stop_ = true;
969+
if (async_ != nullptr) uv_async_send(async_);
970+
}
971+
972+
void AsyncRequest::SetStopped(bool flag) {
973+
Mutex::ScopedLock lock(mutex_);
974+
stop_ = flag;
975+
}
976+
977+
bool AsyncRequest::IsStopped() const {
978+
Mutex::ScopedLock lock(mutex_);
979+
return stop_;
980+
}
981+
982+
uv_async_t* AsyncRequest::GetHandle() {
983+
Mutex::ScopedLock lock(mutex_);
984+
return async_;
985+
}
986+
987+
void AsyncRequest::MemoryInfo(MemoryTracker* tracker) const {
988+
Mutex::ScopedLock lock(mutex_);
989+
if (async_ != nullptr) tracker->TrackField("async_request", *async_);
990+
}
991+
992+
AsyncRequest::~AsyncRequest() {
993+
Mutex::ScopedLock lock(mutex_);
994+
CHECK_NULL(async_);
995+
}
996+
935997
// Not really any better place than env.cc at this moment.
936998
void BaseObject::DeleteMe(void* data) {
937999
BaseObject* self = static_cast<BaseObject*>(data);

src/env.h

+28-1
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,27 @@ struct AllocatedBuffer {
511511
friend class Environment;
512512
};
513513

514+
class AsyncRequest : public MemoryRetainer {
515+
public:
516+
AsyncRequest() {}
517+
~AsyncRequest();
518+
void Install(Environment* env, void* data, uv_async_cb target);
519+
void Uninstall();
520+
void Stop();
521+
void SetStopped(bool flag);
522+
bool IsStopped() const;
523+
uv_async_t* GetHandle();
524+
void MemoryInfo(MemoryTracker* tracker) const override;
525+
SET_MEMORY_INFO_NAME(AsyncRequest)
526+
SET_SELF_SIZE(AsyncRequest)
527+
528+
private:
529+
Environment* env_;
530+
uv_async_t* async_ = nullptr;
531+
mutable Mutex mutex_;
532+
bool stop_ = true;
533+
};
534+
514535
class Environment {
515536
public:
516537
class AsyncHooks {
@@ -695,6 +716,7 @@ class Environment {
695716
void RegisterHandleCleanups();
696717
void CleanupHandles();
697718
void Exit(int code);
719+
void ExitEnv();
698720

699721
// Register clean-up cb to be called on environment destruction.
700722
inline void RegisterHandleCleanup(uv_handle_t* handle,
@@ -844,7 +866,7 @@ class Environment {
844866
inline void add_sub_worker_context(worker::Worker* context);
845867
inline void remove_sub_worker_context(worker::Worker* context);
846868
void stop_sub_worker_contexts();
847-
inline bool is_stopping_worker() const;
869+
inline bool is_stopping() const;
848870

849871
inline void ThrowError(const char* errmsg);
850872
inline void ThrowTypeError(const char* errmsg);
@@ -1018,6 +1040,7 @@ class Environment {
10181040
inline ExecutionMode execution_mode() { return execution_mode_; }
10191041

10201042
inline void set_execution_mode(ExecutionMode mode) { execution_mode_ = mode; }
1043+
inline AsyncRequest* GetAsyncRequest() { return &thread_stopper_; }
10211044

10221045
private:
10231046
inline void CreateImmediate(native_immediate_callback cb,
@@ -1174,6 +1197,10 @@ class Environment {
11741197
uint64_t cleanup_hook_counter_ = 0;
11751198
bool started_cleanup_ = false;
11761199

1200+
// A custom async abstraction (a pair of async handle and a state variable)
1201+
// Used by embedders to shutdown running Node instance.
1202+
AsyncRequest thread_stopper_;
1203+
11771204
static void EnvPromiseHook(v8::PromiseHookType type,
11781205
v8::Local<v8::Promise> promise,
11791206
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
@@ -832,15 +832,14 @@ inline int StartNodeWithIsolate(Isolate* isolate,
832832
per_process::v8_platform.DrainVMTasks(isolate);
833833

834834
more = uv_loop_alive(env.event_loop());
835-
if (more)
836-
continue;
835+
if (more && !env.GetAsyncRequest()->IsStopped()) continue;
837836

838837
RunBeforeExit(&env);
839838

840839
// Emit `beforeExit` if the loop became alive either after emitting
841840
// event, or after running some callbacks.
842841
more = uv_loop_alive(env.event_loop());
843-
} while (more == true);
842+
} while (more == true && !env.GetAsyncRequest()->IsStopped());
844843
env.performance_state()->Mark(
845844
node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT);
846845
}
@@ -977,6 +976,11 @@ int Start(int argc, char** argv) {
977976
return exit_code;
978977
}
979978

979+
int Stop(Environment* env) {
980+
env->ExitEnv();
981+
return 0;
982+
}
983+
980984
} // namespace node
981985

982986
#if !HAVE_INSPECTOR

src/node.h

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

202202
namespace node {
203203

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

211+
// Tear down Node.js while it is running (there are active handles
212+
// in the loop and / or actively executing JavaScript code).
213+
NODE_EXTERN int Stop(Environment* env);
214+
208215
// TODO(addaleax): Officially deprecate this and replace it with something
209216
// better suited for a public embedder API.
210217
NODE_EXTERN void Init(int* argc,
@@ -239,9 +246,6 @@ class NODE_EXTERN ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
239246
NODE_EXTERN ArrayBufferAllocator* CreateArrayBufferAllocator();
240247
NODE_EXTERN void FreeArrayBufferAllocator(ArrayBufferAllocator* allocator);
241248

242-
class IsolateData;
243-
class Environment;
244-
245249
class NODE_EXTERN MultiIsolatePlatform : public v8::Platform {
246250
public:
247251
~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)