mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
vm: fix race condition in watchdog cleanup
Previous code was calling uv_loop_delete() directly on a running loop, which led to race condition aborts/segfaults within libuv. This change changes the watchdog thread to call uv_run() with UV_RUN_ONCE so that the call exits after either the timer times out or uv_async_send() is called from the main thread in Watchdog::Destroy(). The timer/async handles are then closed and uv_run() with UV_RUN_DEFAULT is called so that libuv has a chance to cleanup before the thread exits. The main thread meanwhile calls uv_thread_join() and then uv_loop_delete() to complete the cleanup.
This commit is contained in:
committed by
Ben Noordhuis
parent
7ce5a31061
commit
49e3fcd058
@@ -20,6 +20,7 @@
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "node_watchdog.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
@@ -27,24 +28,23 @@ using v8::V8;
|
||||
|
||||
|
||||
Watchdog::Watchdog(uint64_t ms)
|
||||
: timer_started_(false)
|
||||
, thread_created_(false)
|
||||
: thread_created_(false)
|
||||
, destroyed_(false) {
|
||||
|
||||
loop_ = uv_loop_new();
|
||||
if (!loop_)
|
||||
return;
|
||||
|
||||
int rc = uv_timer_init(loop_, &timer_);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
int rc = uv_async_init(loop_, &async_, &Watchdog::Async);
|
||||
assert(rc == 0);
|
||||
|
||||
rc = uv_timer_init(loop_, &timer_);
|
||||
assert(rc == 0);
|
||||
|
||||
rc = uv_timer_start(&timer_, &Watchdog::Timer, ms, 0);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
timer_started_ = true;
|
||||
|
||||
rc = uv_thread_create(&thread_, &Watchdog::Run, this);
|
||||
if (rc) {
|
||||
@@ -69,28 +69,38 @@ void Watchdog::Destroy() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer_started_) {
|
||||
uv_timer_stop(&timer_);
|
||||
if (thread_created_) {
|
||||
uv_async_send(&async_);
|
||||
uv_thread_join(&thread_);
|
||||
}
|
||||
|
||||
if (loop_) {
|
||||
uv_loop_delete(loop_);
|
||||
}
|
||||
|
||||
if (thread_created_) {
|
||||
uv_thread_join(&thread_);
|
||||
}
|
||||
|
||||
destroyed_ = true;
|
||||
}
|
||||
|
||||
|
||||
void Watchdog::Run(void* arg) {
|
||||
Watchdog* wd = static_cast<Watchdog*>(arg);
|
||||
|
||||
// UV_RUN_ONCE so async_ or timer_ wakeup exits uv_run() call.
|
||||
uv_run(wd->loop_, UV_RUN_ONCE);
|
||||
|
||||
// Loop ref count reaches zero when both handles are closed.
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(&wd->async_), NULL);
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(&wd->timer_), NULL);
|
||||
|
||||
// UV_RUN_DEFAULT so that libuv has a chance to clean up.
|
||||
uv_run(wd->loop_, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
void Watchdog::Async(uv_async_t* async, int status) {
|
||||
}
|
||||
|
||||
|
||||
void Watchdog::Timer(uv_timer_t* timer, int status) {
|
||||
V8::TerminateExecution();
|
||||
}
|
||||
|
||||
@@ -38,12 +38,13 @@ class Watchdog {
|
||||
void Destroy();
|
||||
|
||||
static void Run(void* arg);
|
||||
static void Async(uv_async_t* async, int status);
|
||||
static void Timer(uv_timer_t* timer, int status);
|
||||
|
||||
uv_thread_t thread_;
|
||||
uv_loop_t* loop_;
|
||||
uv_async_t async_;
|
||||
uv_timer_t timer_;
|
||||
bool timer_started_;
|
||||
bool thread_created_;
|
||||
bool destroyed_;
|
||||
};
|
||||
|
||||
@@ -23,15 +23,34 @@ var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var vm = require('vm');
|
||||
|
||||
// Test 1: Timeout of 100ms executing endless loop
|
||||
assert.throws(function() {
|
||||
vm.runInThisContext('while(true) {}', '', 100);
|
||||
});
|
||||
|
||||
// Test 2: Timeout must be >= 0ms
|
||||
assert.throws(function() {
|
||||
vm.runInThisContext('', '', -1);
|
||||
});
|
||||
|
||||
assert.doesNotThrow(function() {
|
||||
vm.runInThisContext('', '', 0);
|
||||
vm.runInThisContext('', '', 100);
|
||||
});
|
||||
// Test 3: Timeout of 0ms
|
||||
vm.runInThisContext('', '', 0);
|
||||
|
||||
// Test 4: Timeout of 1000ms, script finishes first
|
||||
vm.runInThisContext('', '', 1000);
|
||||
|
||||
// Test 5: Nested vm timeouts, inner timeout propagates out
|
||||
try {
|
||||
var context = {
|
||||
log: console.log,
|
||||
runInVM: function(timeout) {
|
||||
vm.runInNewContext('while(true) {}', context, '', timeout);
|
||||
}
|
||||
};
|
||||
vm.runInNewContext('runInVM(10)', context, '', 100);
|
||||
throw new Error('Test 5 failed');
|
||||
} catch (e) {
|
||||
if (-1 === e.message.search(/Script execution timed out./)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user