summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Paprocki <andrew@ishiboo.com>2013-05-28 13:16:16 -0400
committerBen Noordhuis <info@bnoordhuis.nl>2013-05-30 15:57:25 +0200
commit49e3fcd058524826ecbdf57ae7dcb9edd2551af9 (patch)
tree911f21dee10da1173ce2345f6114c248ae95b870
parent7ce5a310612bfcfc153836e718fe3c6309369fb4 (diff)
downloadandroid-node-v8-49e3fcd058524826ecbdf57ae7dcb9edd2551af9.tar.gz
android-node-v8-49e3fcd058524826ecbdf57ae7dcb9edd2551af9.tar.bz2
android-node-v8-49e3fcd058524826ecbdf57ae7dcb9edd2551af9.zip
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.
-rw-r--r--src/node_watchdog.cc36
-rw-r--r--src/node_watchdog.h3
-rw-r--r--test/simple/test-vm-run-timeout.js27
3 files changed, 48 insertions, 18 deletions
diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc
index a8ec719348..0922a2b482 100644
--- a/src/node_watchdog.cc
+++ b/src/node_watchdog.cc
@@ -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();
}
diff --git a/src/node_watchdog.h b/src/node_watchdog.h
index 1bb4317e33..92a8081047 100644
--- a/src/node_watchdog.h
+++ b/src/node_watchdog.h
@@ -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_;
};
diff --git a/test/simple/test-vm-run-timeout.js b/test/simple/test-vm-run-timeout.js
index ccc95f9ddb..26833c61ce 100644
--- a/test/simple/test-vm-run-timeout.js
+++ b/test/simple/test-vm-run-timeout.js
@@ -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;
+ }
+}