From: Andrew Paprocki Date: Mon, 8 Oct 2012 19:24:08 +0000 (+0200) Subject: vm: add support for timeout argument X-Git-Tag: v0.11.2~30 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c081809344bd6e35c49a1c573b954583c0e3a27d;p=platform%2Fupstream%2Fnodejs.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. --- diff --git a/doc/api/vm.markdown b/doc/api/vm.markdown index 49df036..1767493 100644 --- a/doc/api/vm.markdown +++ b/doc/api/vm.markdown @@ -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. diff --git a/lib/module.js b/lib/module.js index 27bba73..460d870 100644 --- a/lib/module.js +++ b/lib/module.js @@ -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. diff --git a/node.gyp b/node.gyp index 6179a51..bb183db 100644 --- a/node.gyp +++ b/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', diff --git a/src/node.js b/src/node.js index e5833cb..0b5fe39 100644 --- a/src/node.js +++ b/src/node.js @@ -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; diff --git a/src/node_script.cc b/src/node_script.cc index 854ee80..21e169d 100644 --- a/src/node_script.cc +++ b/src/node_script.cc @@ -21,6 +21,7 @@ #include "node.h" #include "node_script.h" +#include "node_watchdog.h" #include 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 + EvalOutputFlags output_flag, + EvalTimeoutFlags timeout_flag> static Handle EvalMachine(const Arguments& args); protected: @@ -243,7 +247,8 @@ Handle WrappedScript::New(const Arguments& args) { t->Wrap(args.This()); return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + compileCode, thisContext, wrapExternal, noTimeout>(args); } @@ -275,43 +280,50 @@ Handle WrappedScript::CreateContext(const Arguments& args) { Handle WrappedScript::RunInContext(const Arguments& args) { return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + unwrapExternal, userContext, returnResult, useTimeout>(args); } Handle WrappedScript::RunInThisContext(const Arguments& args) { return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + unwrapExternal, thisContext, returnResult, useTimeout>(args); } Handle WrappedScript::RunInNewContext(const Arguments& args) { return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + unwrapExternal, newContext, returnResult, useTimeout>(args); } Handle WrappedScript::CompileRunInContext(const Arguments& args) { return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + compileCode, userContext, returnResult, useTimeout>(args); } Handle WrappedScript::CompileRunInThisContext(const Arguments& args) { return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + compileCode, thisContext, returnResult, useTimeout>(args); } Handle WrappedScript::CompileRunInNewContext(const Arguments& args) { return - WrappedScript::EvalMachine(args); + WrappedScript::EvalMachine< + compileCode, newContext, returnResult, useTimeout>(args); } template + WrappedScript::EvalOutputFlags output_flag, + WrappedScript::EvalTimeoutFlags timeout_flag> Handle WrappedScript::EvalMachine(const Arguments& args) { HandleScope scope(node_isolate); @@ -346,7 +358,18 @@ Handle WrappedScript::EvalMachine(const Arguments& args) { ? args[filename_index]->ToString() : String::New("evalmachine."); - 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 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(); diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc new file mode 100644 index 0000000..a8ec719 --- /dev/null +++ b/src/node_watchdog.cc @@ -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(arg); + uv_run(wd->loop_, UV_RUN_DEFAULT); +} + + +void Watchdog::Timer(uv_timer_t* timer, int status) { + V8::TerminateExecution(); +} + + +} // namespace node diff --git a/src/node_watchdog.h b/src/node_watchdog.h new file mode 100644 index 0000000..1bb4317 --- /dev/null +++ b/src/node_watchdog.h @@ -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_ diff --git a/test/simple/test-vm-run-timeout.js b/test/simple/test-vm-run-timeout.js new file mode 100644 index 0000000..ccc95f9 --- /dev/null +++ b/test/simple/test-vm-run-timeout.js @@ -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); +});