src: fix race condition in debug signal on exit
Before this commit, sending a SIGUSR1 at program exit could trigger a hard to reproduce race condition where `v8::Debug::DebugBreak(isolate)` got called when the isolate was in the process of being torn down. A similar race condition is in theory possible when sending signals to two threads simultaneously but I haven't been able to reproduce that myself (and I tried, oh how I tried.) This commit fixes the race condition by turning `node_isolate` into a `std::atomic` and using it as an ad hoc synchronization primitive in places where that is necessary. A bare minimum std::atomic polyfill is added for OS X because Apple wouldn't be Apple if things just worked out of the box. PR-URL: https://github.com/nodejs/node/pull/3528 Reviewed-By: Fedor Indutny <fedor@indutny.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
29da8cf8d7
commit
53e64bb29e
18
src/atomic-polyfill.h
Normal file
18
src/atomic-polyfill.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#ifndef SRC_ATOMIC_POLYFILL_H_
|
||||||
|
#define SRC_ATOMIC_POLYFILL_H_
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nonstd {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct atomic {
|
||||||
|
atomic() = default;
|
||||||
|
T exchange(T value) { return __sync_lock_test_and_set(&value_, value); }
|
||||||
|
T value_ = T();
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(atomic);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nonstd
|
||||||
|
|
||||||
|
#endif // SRC_ATOMIC_POLYFILL_H_
|
55
src/node.cc
55
src/node.cc
@ -86,6 +86,14 @@ typedef int mode_t;
|
|||||||
extern char **environ;
|
extern char **environ;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include "atomic-polyfill.h" // NOLINT(build/include_order)
|
||||||
|
namespace node { template <typename T> using atomic = nonstd::atomic<T>; }
|
||||||
|
#else
|
||||||
|
#include <atomic>
|
||||||
|
namespace node { template <typename T> using atomic = std::atomic<T>; }
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
@ -153,7 +161,7 @@ static double prog_start_time;
|
|||||||
static bool debugger_running;
|
static bool debugger_running;
|
||||||
static uv_async_t dispatch_debug_messages_async;
|
static uv_async_t dispatch_debug_messages_async;
|
||||||
|
|
||||||
static Isolate* node_isolate = nullptr;
|
static node::atomic<Isolate*> node_isolate;
|
||||||
static v8::Platform* default_platform;
|
static v8::Platform* default_platform;
|
||||||
|
|
||||||
|
|
||||||
@ -3410,28 +3418,46 @@ static void EnableDebug(Environment* env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called from an arbitrary thread.
|
||||||
|
static void TryStartDebugger() {
|
||||||
|
// Call only async signal-safe functions here! Don't retry the exchange,
|
||||||
|
// it will deadlock when the thread is interrupted inside a critical section.
|
||||||
|
if (auto isolate = node_isolate.exchange(nullptr)) {
|
||||||
|
v8::Debug::DebugBreak(isolate);
|
||||||
|
uv_async_send(&dispatch_debug_messages_async);
|
||||||
|
CHECK_EQ(nullptr, node_isolate.exchange(isolate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Called from the main thread.
|
// Called from the main thread.
|
||||||
static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) {
|
static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) {
|
||||||
|
// Synchronize with signal handler, see TryStartDebugger.
|
||||||
|
Isolate* isolate;
|
||||||
|
do {
|
||||||
|
isolate = node_isolate.exchange(nullptr);
|
||||||
|
} while (isolate == nullptr);
|
||||||
|
|
||||||
if (debugger_running == false) {
|
if (debugger_running == false) {
|
||||||
fprintf(stderr, "Starting debugger agent.\n");
|
fprintf(stderr, "Starting debugger agent.\n");
|
||||||
|
|
||||||
HandleScope scope(node_isolate);
|
HandleScope scope(isolate);
|
||||||
Environment* env = Environment::GetCurrent(node_isolate);
|
Environment* env = Environment::GetCurrent(isolate);
|
||||||
Context::Scope context_scope(env->context());
|
Context::Scope context_scope(env->context());
|
||||||
|
|
||||||
StartDebug(env, false);
|
StartDebug(env, false);
|
||||||
EnableDebug(env);
|
EnableDebug(env);
|
||||||
}
|
}
|
||||||
Isolate::Scope isolate_scope(node_isolate);
|
|
||||||
|
Isolate::Scope isolate_scope(isolate);
|
||||||
v8::Debug::ProcessDebugMessages();
|
v8::Debug::ProcessDebugMessages();
|
||||||
|
CHECK_EQ(nullptr, node_isolate.exchange(isolate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef __POSIX__
|
#ifdef __POSIX__
|
||||||
static void EnableDebugSignalHandler(int signo) {
|
static void EnableDebugSignalHandler(int signo) {
|
||||||
// Call only async signal-safe functions here!
|
TryStartDebugger();
|
||||||
v8::Debug::DebugBreak(*static_cast<Isolate* volatile*>(&node_isolate));
|
|
||||||
uv_async_send(&dispatch_debug_messages_async);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3485,8 +3511,7 @@ static int RegisterDebugSignalHandler() {
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DWORD WINAPI EnableDebugThreadProc(void* arg) {
|
DWORD WINAPI EnableDebugThreadProc(void* arg) {
|
||||||
v8::Debug::DebugBreak(*static_cast<Isolate* volatile*>(&node_isolate));
|
TryStartDebugger();
|
||||||
uv_async_send(&dispatch_debug_messages_async);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4006,7 +4031,8 @@ static void StartNodeInstance(void* arg) {
|
|||||||
// Fetch a reference to the main isolate, so we have a reference to it
|
// Fetch a reference to the main isolate, so we have a reference to it
|
||||||
// even when we need it to access it from another (debugger) thread.
|
// even when we need it to access it from another (debugger) thread.
|
||||||
if (instance_data->is_main())
|
if (instance_data->is_main())
|
||||||
node_isolate = isolate;
|
CHECK_EQ(nullptr, node_isolate.exchange(isolate));
|
||||||
|
|
||||||
{
|
{
|
||||||
Locker locker(isolate);
|
Locker locker(isolate);
|
||||||
Isolate::Scope isolate_scope(isolate);
|
Isolate::Scope isolate_scope(isolate);
|
||||||
@ -4016,7 +4042,7 @@ static void StartNodeInstance(void* arg) {
|
|||||||
array_buffer_allocator->set_env(env);
|
array_buffer_allocator->set_env(env);
|
||||||
Context::Scope context_scope(context);
|
Context::Scope context_scope(context);
|
||||||
|
|
||||||
node_isolate->SetAbortOnUncaughtExceptionCallback(
|
isolate->SetAbortOnUncaughtExceptionCallback(
|
||||||
ShouldAbortOnUncaughtException);
|
ShouldAbortOnUncaughtException);
|
||||||
|
|
||||||
// Start debug agent when argv has --debug
|
// Start debug agent when argv has --debug
|
||||||
@ -4070,12 +4096,15 @@ static void StartNodeInstance(void* arg) {
|
|||||||
env = nullptr;
|
env = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instance_data->is_main()) {
|
||||||
|
// Synchronize with signal handler, see TryStartDebugger.
|
||||||
|
while (isolate != node_isolate.exchange(nullptr)); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_NE(isolate, nullptr);
|
CHECK_NE(isolate, nullptr);
|
||||||
isolate->Dispose();
|
isolate->Dispose();
|
||||||
isolate = nullptr;
|
isolate = nullptr;
|
||||||
delete array_buffer_allocator;
|
delete array_buffer_allocator;
|
||||||
if (instance_data->is_main())
|
|
||||||
node_isolate = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Start(int argc, char** argv) {
|
int Start(int argc, char** argv) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user