Skip to content

Commit 3bafe1a

Browse files
bnoordhuisjasnell
authored andcommitted
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: #3528 Reviewed-By: Fedor Indutny <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 8a2c4ae commit 3bafe1a

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

src/atomic-polyfill.h

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef SRC_ATOMIC_POLYFILL_H_
2+
#define SRC_ATOMIC_POLYFILL_H_
3+
4+
#include "util.h"
5+
6+
namespace nonstd {
7+
8+
template <typename T>
9+
struct atomic {
10+
atomic() = default;
11+
T exchange(T value) { return __sync_lock_test_and_set(&value_, value); }
12+
T value_ = T();
13+
DISALLOW_COPY_AND_ASSIGN(atomic);
14+
};
15+
16+
} // namespace nonstd
17+
18+
#endif // SRC_ATOMIC_POLYFILL_H_

src/node.cc

+42-13
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ typedef int mode_t;
8686
extern char **environ;
8787
#endif
8888

89+
#ifdef __APPLE__
90+
#include "atomic-polyfill.h" // NOLINT(build/include_order)
91+
namespace node { template <typename T> using atomic = nonstd::atomic<T>; }
92+
#else
93+
#include <atomic>
94+
namespace node { template <typename T> using atomic = std::atomic<T>; }
95+
#endif
96+
8997
namespace node {
9098

9199
using v8::Array;
@@ -153,7 +161,7 @@ static double prog_start_time;
153161
static bool debugger_running;
154162
static uv_async_t dispatch_debug_messages_async;
155163

156-
static Isolate* node_isolate = nullptr;
164+
static node::atomic<Isolate*> node_isolate;
157165
static v8::Platform* default_platform;
158166

159167

@@ -3389,28 +3397,46 @@ static void EnableDebug(Environment* env) {
33893397
}
33903398

33913399

3400+
// Called from an arbitrary thread.
3401+
static void TryStartDebugger() {
3402+
// Call only async signal-safe functions here! Don't retry the exchange,
3403+
// it will deadlock when the thread is interrupted inside a critical section.
3404+
if (auto isolate = node_isolate.exchange(nullptr)) {
3405+
v8::Debug::DebugBreak(isolate);
3406+
uv_async_send(&dispatch_debug_messages_async);
3407+
CHECK_EQ(nullptr, node_isolate.exchange(isolate));
3408+
}
3409+
}
3410+
3411+
33923412
// Called from the main thread.
33933413
static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) {
3414+
// Synchronize with signal handler, see TryStartDebugger.
3415+
Isolate* isolate;
3416+
do {
3417+
isolate = node_isolate.exchange(nullptr);
3418+
} while (isolate == nullptr);
3419+
33943420
if (debugger_running == false) {
33953421
fprintf(stderr, "Starting debugger agent.\n");
33963422

3397-
HandleScope scope(node_isolate);
3398-
Environment* env = Environment::GetCurrent(node_isolate);
3423+
HandleScope scope(isolate);
3424+
Environment* env = Environment::GetCurrent(isolate);
33993425
Context::Scope context_scope(env->context());
34003426

34013427
StartDebug(env, false);
34023428
EnableDebug(env);
34033429
}
3404-
Isolate::Scope isolate_scope(node_isolate);
3430+
3431+
Isolate::Scope isolate_scope(isolate);
34053432
v8::Debug::ProcessDebugMessages();
3433+
CHECK_EQ(nullptr, node_isolate.exchange(isolate));
34063434
}
34073435

34083436

34093437
#ifdef __POSIX__
34103438
static void EnableDebugSignalHandler(int signo) {
3411-
// Call only async signal-safe functions here!
3412-
v8::Debug::DebugBreak(*static_cast<Isolate* volatile*>(&node_isolate));
3413-
uv_async_send(&dispatch_debug_messages_async);
3439+
TryStartDebugger();
34143440
}
34153441

34163442

@@ -3464,8 +3490,7 @@ static int RegisterDebugSignalHandler() {
34643490

34653491
#ifdef _WIN32
34663492
DWORD WINAPI EnableDebugThreadProc(void* arg) {
3467-
v8::Debug::DebugBreak(*static_cast<Isolate* volatile*>(&node_isolate));
3468-
uv_async_send(&dispatch_debug_messages_async);
3493+
TryStartDebugger();
34693494
return 0;
34703495
}
34713496

@@ -3986,7 +4011,8 @@ static void StartNodeInstance(void* arg) {
39864011
// Fetch a reference to the main isolate, so we have a reference to it
39874012
// even when we need it to access it from another (debugger) thread.
39884013
if (instance_data->is_main())
3989-
node_isolate = isolate;
4014+
CHECK_EQ(nullptr, node_isolate.exchange(isolate));
4015+
39904016
{
39914017
Locker locker(isolate);
39924018
Isolate::Scope isolate_scope(isolate);
@@ -3996,7 +4022,7 @@ static void StartNodeInstance(void* arg) {
39964022
array_buffer_allocator->set_env(env);
39974023
Context::Scope context_scope(context);
39984024

3999-
node_isolate->SetAbortOnUncaughtExceptionCallback(
4025+
isolate->SetAbortOnUncaughtExceptionCallback(
40004026
ShouldAbortOnUncaughtException);
40014027

40024028
// Start debug agent when argv has --debug
@@ -4047,12 +4073,15 @@ static void StartNodeInstance(void* arg) {
40474073
env = nullptr;
40484074
}
40494075

4076+
if (instance_data->is_main()) {
4077+
// Synchronize with signal handler, see TryStartDebugger.
4078+
while (isolate != node_isolate.exchange(nullptr)); // NOLINT
4079+
}
4080+
40504081
CHECK_NE(isolate, nullptr);
40514082
isolate->Dispose();
40524083
isolate = nullptr;
40534084
delete array_buffer_allocator;
4054-
if (instance_data->is_main())
4055-
node_isolate = nullptr;
40564085
}
40574086

40584087
int Start(int argc, char** argv) {

0 commit comments

Comments
 (0)