mirror of https://github.com/nodejs/node.git
vm: add support for timeout argument
Add a watchdog class which executes a timer in a separate event loop in a separate thread that will terminate v8 execution if it expires. Add timeout argument to functions in vm module which use the watchdog if a non-zero timeout is specified.
This commit is contained in:
parent
b5c172138c
commit
c081809344
|
@ -79,12 +79,14 @@ In case of syntax error in `code`, `vm.runInThisContext` emits the syntax error
|
|||
and throws an exception.
|
||||
|
||||
|
||||
## vm.runInNewContext(code, [sandbox], [filename])
|
||||
## vm.runInNewContext(code, [sandbox], [filename], [timeout])
|
||||
|
||||
`vm.runInNewContext` compiles `code`, then runs it in `sandbox` and returns the
|
||||
result. Running code does not have access to local scope. The object `sandbox`
|
||||
will be used as the global object for `code`.
|
||||
`sandbox` and `filename` are optional, `filename` is only used in stack traces.
|
||||
`timeout` specifies an optional number of milliseconds to execute `code` before
|
||||
terminating execution. If execution is terminated, `null` will be thrown.
|
||||
|
||||
Example: compile and execute code that increments a global variable and sets a new one.
|
||||
These globals are contained in the sandbox.
|
||||
|
@ -108,7 +110,7 @@ requires a separate process.
|
|||
In case of syntax error in `code`, `vm.runInNewContext` emits the syntax error to stderr
|
||||
and throws an exception.
|
||||
|
||||
## vm.runInContext(code, context, [filename])
|
||||
## vm.runInContext(code, context, [filename], [timeout])
|
||||
|
||||
`vm.runInContext` compiles `code`, then runs it in `context` and returns the
|
||||
result. A (V8) context comprises a global object, together with a set of
|
||||
|
@ -116,6 +118,8 @@ built-in objects and functions. Running code does not have access to local scope
|
|||
and the global object held within `context` will be used as the global object
|
||||
for `code`.
|
||||
`filename` is optional, it's used only in stack traces.
|
||||
`timeout` specifies an optional number of milliseconds to execute `code` before
|
||||
terminating execution. If execution is terminated, `null` will be thrown.
|
||||
|
||||
Example: compile and execute code in a existing context.
|
||||
|
||||
|
@ -165,12 +169,14 @@ and throws an exception.
|
|||
|
||||
A class for running scripts. Returned by vm.createScript.
|
||||
|
||||
### script.runInThisContext()
|
||||
### script.runInThisContext([timeout])
|
||||
|
||||
Similar to `vm.runInThisContext` but a method of a precompiled `Script` object.
|
||||
`script.runInThisContext` runs the code of `script` and returns the result.
|
||||
Running code does not have access to local scope, but does have access to the `global` object
|
||||
(v8: in actual context).
|
||||
`timeout` specifies an optional number of milliseconds to execute `code` before
|
||||
terminating execution. If execution is terminated, `null` will be thrown.
|
||||
|
||||
Example of using `script.runInThisContext` to compile code once and run it multiple times:
|
||||
|
||||
|
@ -189,11 +195,13 @@ Example of using `script.runInThisContext` to compile code once and run it multi
|
|||
// 1000
|
||||
|
||||
|
||||
### script.runInNewContext([sandbox])
|
||||
### script.runInNewContext([sandbox], [timeout])
|
||||
|
||||
Similar to `vm.runInNewContext` a method of a precompiled `Script` object.
|
||||
`script.runInNewContext` runs the code of `script` with `sandbox` as the global object and returns the result.
|
||||
Running code does not have access to local scope. `sandbox` is optional.
|
||||
`timeout` specifies an optional number of milliseconds to execute `code` before
|
||||
terminating execution. If execution is terminated, `null` will be thrown.
|
||||
|
||||
Example: compile code that increments a global variable and sets one, then execute this code multiple times.
|
||||
These globals are contained in the sandbox.
|
||||
|
|
|
@ -419,7 +419,7 @@ Module.prototype._compile = function(content, filename) {
|
|||
sandbox.global = sandbox;
|
||||
sandbox.root = root;
|
||||
|
||||
return runInNewContext(content, sandbox, filename, true);
|
||||
return runInNewContext(content, sandbox, filename, 0, true);
|
||||
}
|
||||
|
||||
debug('load root module');
|
||||
|
@ -430,13 +430,13 @@ Module.prototype._compile = function(content, filename) {
|
|||
global.__dirname = dirname;
|
||||
global.module = self;
|
||||
|
||||
return runInThisContext(content, filename, true);
|
||||
return runInThisContext(content, filename, 0, true);
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
var wrapper = Module.wrap(content);
|
||||
|
||||
var compiledWrapper = runInThisContext(wrapper, filename, true);
|
||||
var compiledWrapper = runInThisContext(wrapper, filename, 0, true);
|
||||
if (global.v8debug) {
|
||||
if (!resolvedArgv) {
|
||||
// we enter the repl if we're not given a filename argument.
|
||||
|
|
2
node.gyp
2
node.gyp
|
@ -99,6 +99,7 @@
|
|||
'src/node_script.cc',
|
||||
'src/node_stat_watcher.cc',
|
||||
'src/node_string.cc',
|
||||
'src/node_watchdog.cc',
|
||||
'src/node_zlib.cc',
|
||||
'src/pipe_wrap.cc',
|
||||
'src/signal_wrap.cc',
|
||||
|
@ -126,6 +127,7 @@
|
|||
'src/node_script.h',
|
||||
'src/node_string.h',
|
||||
'src/node_version.h',
|
||||
'src/node_watchdog.h',
|
||||
'src/ngx-queue.h',
|
||||
'src/pipe_wrap.h',
|
||||
'src/tty_wrap.h',
|
||||
|
|
|
@ -527,7 +527,7 @@
|
|||
'global.require = require;\n' +
|
||||
'return require("vm").runInThisContext(' +
|
||||
JSON.stringify(body) + ', ' +
|
||||
JSON.stringify(name) + ', true);\n';
|
||||
JSON.stringify(name) + ', 0, true);\n';
|
||||
}
|
||||
var result = module._compile(script, name + '-wrapper');
|
||||
if (process._print_eval) console.log(result);
|
||||
|
@ -888,7 +888,7 @@
|
|||
var source = NativeModule.getSource(this.id);
|
||||
source = NativeModule.wrap(source);
|
||||
|
||||
var fn = runInThisContext(source, this.filename, true);
|
||||
var fn = runInThisContext(source, this.filename, 0, true);
|
||||
fn(this.exports, NativeModule.require, this, this.filename);
|
||||
|
||||
this.loaded = true;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "node.h"
|
||||
#include "node_script.h"
|
||||
#include "node_watchdog.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace node {
|
||||
|
@ -42,6 +43,7 @@ using v8::Persistent;
|
|||
using v8::Integer;
|
||||
using v8::Function;
|
||||
using v8::FunctionTemplate;
|
||||
using v8::V8;
|
||||
|
||||
|
||||
class WrappedContext : ObjectWrap {
|
||||
|
@ -74,10 +76,12 @@ class WrappedScript : ObjectWrap {
|
|||
enum EvalInputFlags { compileCode, unwrapExternal };
|
||||
enum EvalContextFlags { thisContext, newContext, userContext };
|
||||
enum EvalOutputFlags { returnResult, wrapExternal };
|
||||
enum EvalTimeoutFlags { noTimeout, useTimeout };
|
||||
|
||||
template <EvalInputFlags input_flag,
|
||||
EvalContextFlags context_flag,
|
||||
EvalOutputFlags output_flag>
|
||||
EvalOutputFlags output_flag,
|
||||
EvalTimeoutFlags timeout_flag>
|
||||
static Handle<Value> EvalMachine(const Arguments& args);
|
||||
|
||||
protected:
|
||||
|
@ -243,7 +247,8 @@ Handle<Value> WrappedScript::New(const Arguments& args) {
|
|||
t->Wrap(args.This());
|
||||
|
||||
return
|
||||
WrappedScript::EvalMachine<compileCode, thisContext, wrapExternal>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
compileCode, thisContext, wrapExternal, noTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
|
@ -275,43 +280,50 @@ Handle<Value> WrappedScript::CreateContext(const Arguments& args) {
|
|||
|
||||
Handle<Value> WrappedScript::RunInContext(const Arguments& args) {
|
||||
return
|
||||
WrappedScript::EvalMachine<unwrapExternal, userContext, returnResult>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
unwrapExternal, userContext, returnResult, useTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> WrappedScript::RunInThisContext(const Arguments& args) {
|
||||
return
|
||||
WrappedScript::EvalMachine<unwrapExternal, thisContext, returnResult>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
unwrapExternal, thisContext, returnResult, useTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> WrappedScript::RunInNewContext(const Arguments& args) {
|
||||
return
|
||||
WrappedScript::EvalMachine<unwrapExternal, newContext, returnResult>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
unwrapExternal, newContext, returnResult, useTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> WrappedScript::CompileRunInContext(const Arguments& args) {
|
||||
return
|
||||
WrappedScript::EvalMachine<compileCode, userContext, returnResult>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
compileCode, userContext, returnResult, useTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> WrappedScript::CompileRunInThisContext(const Arguments& args) {
|
||||
return
|
||||
WrappedScript::EvalMachine<compileCode, thisContext, returnResult>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
compileCode, thisContext, returnResult, useTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
Handle<Value> WrappedScript::CompileRunInNewContext(const Arguments& args) {
|
||||
return
|
||||
WrappedScript::EvalMachine<compileCode, newContext, returnResult>(args);
|
||||
WrappedScript::EvalMachine<
|
||||
compileCode, newContext, returnResult, useTimeout>(args);
|
||||
}
|
||||
|
||||
|
||||
template <WrappedScript::EvalInputFlags input_flag,
|
||||
WrappedScript::EvalContextFlags context_flag,
|
||||
WrappedScript::EvalOutputFlags output_flag>
|
||||
WrappedScript::EvalOutputFlags output_flag,
|
||||
WrappedScript::EvalTimeoutFlags timeout_flag>
|
||||
Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
|
||||
HandleScope scope(node_isolate);
|
||||
|
||||
|
@ -346,7 +358,18 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
|
|||
? args[filename_index]->ToString()
|
||||
: String::New("evalmachine.<anonymous>");
|
||||
|
||||
const int display_error_index = args.Length() - 1;
|
||||
uint64_t timeout = 0;
|
||||
const int timeout_index = filename_index + 1;
|
||||
if (timeout_flag == useTimeout && args.Length() > timeout_index) {
|
||||
if (!args[timeout_index]->IsUint32()) {
|
||||
return ThrowException(Exception::TypeError(
|
||||
String::New("needs an unsigned integer 'ms' argument.")));
|
||||
}
|
||||
timeout = args[timeout_index]->Uint32Value();
|
||||
}
|
||||
|
||||
const int display_error_index = timeout_index +
|
||||
(timeout_flag == noTimeout ? 0 : 1);
|
||||
bool display_error = false;
|
||||
if (args.Length() > display_error_index &&
|
||||
args[display_error_index]->IsBoolean() &&
|
||||
|
@ -416,7 +439,17 @@ Handle<Value> WrappedScript::EvalMachine(const Arguments& args) {
|
|||
|
||||
|
||||
if (output_flag == returnResult) {
|
||||
result = script->Run();
|
||||
if (timeout) {
|
||||
Watchdog wd(timeout);
|
||||
result = script->Run();
|
||||
} else {
|
||||
result = script->Run();
|
||||
}
|
||||
if (try_catch.HasCaught() && try_catch.HasTerminated()) {
|
||||
V8::CancelTerminateExecution(args.GetIsolate());
|
||||
return ThrowException(Exception::Error(
|
||||
String::New("Script execution timed out.")));
|
||||
}
|
||||
if (result.IsEmpty()) {
|
||||
if (display_error) DisplayExceptionLine(try_catch);
|
||||
return try_catch.ReThrow();
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "node_watchdog.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::V8;
|
||||
|
||||
|
||||
Watchdog::Watchdog(uint64_t ms)
|
||||
: timer_started_(false)
|
||||
, thread_created_(false)
|
||||
, destroyed_(false) {
|
||||
|
||||
loop_ = uv_loop_new();
|
||||
if (!loop_)
|
||||
return;
|
||||
|
||||
int rc = uv_timer_init(loop_, &timer_);
|
||||
if (rc) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
thread_created_ = true;
|
||||
}
|
||||
|
||||
|
||||
Watchdog::~Watchdog() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
|
||||
void Watchdog::Dispose() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
|
||||
void Watchdog::Destroy() {
|
||||
if (destroyed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer_started_) {
|
||||
uv_timer_stop(&timer_);
|
||||
}
|
||||
|
||||
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(wd->loop_, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
void Watchdog::Timer(uv_timer_t* timer, int status) {
|
||||
V8::TerminateExecution();
|
||||
}
|
||||
|
||||
|
||||
} // namespace node
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#ifndef SRC_NODE_WATCHDOG_H_
|
||||
#define SRC_NODE_WATCHDOG_H_
|
||||
|
||||
#include "v8.h"
|
||||
#include "uv.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
class Watchdog {
|
||||
public:
|
||||
Watchdog(uint64_t ms);
|
||||
~Watchdog();
|
||||
|
||||
void Dispose();
|
||||
|
||||
private:
|
||||
void Destroy();
|
||||
|
||||
static void Run(void* arg);
|
||||
static void Timer(uv_timer_t* timer, int status);
|
||||
|
||||
uv_thread_t thread_;
|
||||
uv_loop_t* loop_;
|
||||
uv_timer_t timer_;
|
||||
bool timer_started_;
|
||||
bool thread_created_;
|
||||
bool destroyed_;
|
||||
};
|
||||
|
||||
} // namespace node
|
||||
|
||||
#endif // SRC_NODE_WATCHDOG_H_
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var vm = require('vm');
|
||||
|
||||
assert.throws(function() {
|
||||
vm.runInThisContext('while(true) {}', '', 100);
|
||||
});
|
||||
|
||||
assert.throws(function() {
|
||||
vm.runInThisContext('', '', -1);
|
||||
});
|
||||
|
||||
assert.doesNotThrow(function() {
|
||||
vm.runInThisContext('', '', 0);
|
||||
vm.runInThisContext('', '', 100);
|
||||
});
|
Loading…
Reference in New Issue