0
0
mirror of https://github.com/phpv8/v8js.git synced 2024-12-22 23:51:51 +00:00

Script timeout thread to forcefully terminate the current thread of V8 execution in the corresponding isolate.

Note that threads are implemented using std::thread which is only available in C++0x. The relevant compile flags have been added but compiler support has not been tested and is therefore not guaranteed.
This commit is contained in:
Simon Best 2013-04-09 22:52:42 +01:00
parent 79444ce76a
commit 15dc9e157b
2 changed files with 123 additions and 16 deletions

View File

@ -67,7 +67,7 @@ LDFLAGS=$old_LDFLAGS
AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ]) AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ])
fi fi
PHP_NEW_EXTENSION(v8js, v8js.cc v8js_convert.cc v8js_methods.cc v8js_variables.cc, $ext_shared) PHP_NEW_EXTENSION(v8js, v8js.cc v8js_convert.cc v8js_methods.cc v8js_variables.cc, $ext_shared, , "-std=c++0x")
PHP_ADD_MAKEFILE_FRAGMENT PHP_ADD_MAKEFILE_FRAGMENT
fi fi

137
v8js.cc
View File

@ -38,10 +38,34 @@ extern "C" {
#include <v8.h> #include <v8.h>
#include "php_v8js_macros.h" #include "php_v8js_macros.h"
#include <chrono>
#include <stack>
#include <thread>
/* Forward declarations */ /* Forward declarations */
static void php_v8js_throw_exception(v8::TryCatch * TSRMLS_DC); static void php_v8js_throw_exception(v8::TryCatch * TSRMLS_DC);
static void php_v8js_create_exception(zval *, v8::TryCatch * TSRMLS_DC); static void php_v8js_create_exception(zval *, v8::TryCatch * TSRMLS_DC);
/* {{{ Context container */
struct php_v8js_ctx {
zend_object std;
v8::Persistent<v8::String> object_name;
v8::Persistent<v8::Context> context;
zend_bool report_uncaught;
zval *pending_exception;
int in_execution;
v8::Isolate *isolate;
bool execution_terminated;
};
/* }}} */
// Timer context
struct php_v8js_timer_ctx
{
std::chrono::time_point<std::chrono::high_resolution_clock> time_point;
php_v8js_ctx *v8js_ctx;
};
/* Module globals */ /* Module globals */
ZEND_BEGIN_MODULE_GLOBALS(v8js) ZEND_BEGIN_MODULE_GLOBALS(v8js)
int v8_initialized; int v8_initialized;
@ -52,6 +76,11 @@ ZEND_BEGIN_MODULE_GLOBALS(v8js)
/* Ini globals */ /* Ini globals */
char *v8_flags; /* V8 command line flags */ char *v8_flags; /* V8 command line flags */
int max_disposed_contexts; /* Max disposed context allowed before forcing V8 GC */ int max_disposed_contexts; /* Max disposed context allowed before forcing V8 GC */
// Timer thread globals
std::stack<php_v8js_timer_ctx *> timer_stack;
std::thread *timer_thread;
bool timer_stop;
ZEND_END_MODULE_GLOBALS(v8js) ZEND_END_MODULE_GLOBALS(v8js)
#ifdef ZTS #ifdef ZTS
@ -126,17 +155,6 @@ struct php_v8js_jsext {
}; };
/* }}} */ /* }}} */
/* {{{ Context container */
struct php_v8js_ctx {
zend_object std;
v8::Persistent<v8::String> object_name;
v8::Persistent<v8::Context> context;
zend_bool report_uncaught;
zval *pending_exception;
int in_execution;
};
/* }}} */
/* {{{ Object container */ /* {{{ Object container */
struct php_v8js_object { struct php_v8js_object {
zend_object std; zend_object std;
@ -579,6 +597,8 @@ static PHP_METHOD(V8Js, __construct)
c->report_uncaught = report_uncaught; c->report_uncaught = report_uncaught;
c->pending_exception = NULL; c->pending_exception = NULL;
c->in_execution = 0; c->in_execution = 0;
c->isolate = v8::Isolate::New();
c->execution_terminated = false;
/* Initialize V8 */ /* Initialize V8 */
php_v8js_init(TSRMLS_C); php_v8js_init(TSRMLS_C);
@ -597,6 +617,10 @@ static PHP_METHOD(V8Js, __construct)
/* Declare configuration for extensions */ /* Declare configuration for extensions */
v8::ExtensionConfiguration extension_conf(exts_count, exts); v8::ExtensionConfiguration extension_conf(exts_count, exts);
// Isolate execution
v8::Locker locker(c->isolate);
v8::Isolate::Scope isolate_scope(c->isolate);
/* Handle scope */ /* Handle scope */
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
@ -663,17 +687,73 @@ static PHP_METHOD(V8Js, __construct)
} \ } \
\ \
(ctx) = (php_v8js_ctx *) zend_object_store_get_object(object TSRMLS_CC); \ (ctx) = (php_v8js_ctx *) zend_object_store_get_object(object TSRMLS_CC); \
v8::Locker locker((ctx)->isolate); \
v8::Isolate::Scope isolate_scope((ctx)->isolate); \
v8::Context::Scope context_scope((ctx)->context); v8::Context::Scope context_scope((ctx)->context);
static void timer_push(long timeout, php_v8js_ctx *c)
{
// Create context for this timer
php_v8js_timer_ctx *timer_ctx = (php_v8js_timer_ctx *)malloc(sizeof(php_v8js_timer_ctx));
// Calculate the time point when the timeout is exceeded
std::chrono::milliseconds duration(timeout);
std::chrono::time_point<std::chrono::high_resolution_clock> from = std::chrono::high_resolution_clock::now();
// Push the timer context
timer_ctx->time_point = from + duration;
timer_ctx->v8js_ctx = c;
V8JSG(timer_stack).push(timer_ctx);
}
static void timer_pop()
{
if (V8JSG(timer_stack).size()) {
// Free the timer context memory
php_v8js_timer_ctx *timer_ctx = V8JSG(timer_stack).top();
free(timer_ctx);
// Remove the timer context from the stack
V8JSG(timer_stack).pop();
}
}
static void php_v8js_timer_thread()
{
while (!V8JSG(timer_stop)) {
std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
if (V8JSG(timer_stack).size()) {
// Get the current timer context
php_v8js_timer_ctx *timer_ctx = V8JSG(timer_stack).top();
php_v8js_ctx *c = timer_ctx->v8js_ctx;
if (now > timer_ctx->time_point) {
// Forcefully terminate the current thread of V8 execution in the isolate
v8::V8::TerminateExecution(c->isolate);
// Remove this timer from the stack
timer_pop();
c->execution_terminated = true;
}
}
// Sleep for 10ms
std::chrono::milliseconds duration(10);
std::this_thread::sleep_for(duration);
}
}
/* {{{ proto mixed V8Js::executeString(string script [, string identifier [, int flags]]) /* {{{ proto mixed V8Js::executeString(string script [, string identifier [, int flags]])
*/ */
static PHP_METHOD(V8Js, executeString) static PHP_METHOD(V8Js, executeString)
{ {
char *str = NULL, *identifier = NULL; char *str = NULL, *identifier = NULL;
int str_len = 0, identifier_len = 0; int str_len = 0, identifier_len = 0;
long flags = V8JS_FLAG_NONE; long flags = V8JS_FLAG_NONE, timeout = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|sl", &str, &str_len, &identifier, &identifier_len, &flags) == FAILURE) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|sll", &str, &str_len, &identifier, &identifier_len, &flags, &timeout) == FAILURE) {
return; return;
} }
@ -700,14 +780,32 @@ static PHP_METHOD(V8Js, executeString)
/* Set flags for runtime use */ /* Set flags for runtime use */
V8JS_GLOBAL_SET_FLAGS(flags); V8JS_GLOBAL_SET_FLAGS(flags);
if (timeout > 0) {
// If timer thread is not running then start it
if (!V8JSG(timer_thread)) {
// If not, start timer thread
V8JSG(timer_thread) = new std::thread(php_v8js_timer_thread);
}
timer_push(timeout, c);
}
/* Execute script */ /* Execute script */
c->in_execution++; c->in_execution++;
v8::Local<v8::Value> result = script->Run(); v8::Local<v8::Value> result = script->Run();
c->in_execution--; c->in_execution--;
/* Script possibly terminated, return immediately */ if (timeout > 0) {
timer_pop();
}
if (c->execution_terminated) {
// Execution has been terminated due to timeout
zend_throw_exception(php_ce_v8js_exception, "Script timeout", 0 TSRMLS_CC);
}
if (!try_catch.CanContinue()) { if (!try_catch.CanContinue()) {
/* TODO: throw PHP exception here? */ // At this point we can't re-throw the exception
return; return;
} }
@ -920,6 +1018,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_executestring, 0, 0, 1)
ZEND_ARG_INFO(0, script) ZEND_ARG_INFO(0, script)
ZEND_ARG_INFO(0, identifier) ZEND_ARG_INFO(0, identifier)
ZEND_ARG_INFO(0, flags) ZEND_ARG_INFO(0, flags)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO() ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_v8js_getpendingexception, 0) ZEND_BEGIN_ARG_INFO(arginfo_v8js_getpendingexception, 0)
@ -1211,6 +1310,12 @@ static PHP_MSHUTDOWN_FUNCTION(v8js)
*/ */
static PHP_RSHUTDOWN_FUNCTION(v8js) static PHP_RSHUTDOWN_FUNCTION(v8js)
{ {
// If the timer thread is running then stop it
if (V8JSG(timer_thread)) {
V8JSG(timer_stop) = true;
V8JSG(timer_thread)->join();
}
#if V8JS_DEBUG #if V8JS_DEBUG
v8::HeapStatistics stats; v8::HeapStatistics stats;
v8::V8::GetHeapStatistics(&stats); v8::V8::GetHeapStatistics(&stats);
@ -1254,6 +1359,8 @@ static PHP_GINIT_FUNCTION(v8js)
v8js_globals->disposed_contexts = 0; v8js_globals->disposed_contexts = 0;
v8js_globals->v8_initialized = 0; v8js_globals->v8_initialized = 0;
v8js_globals->v8_flags = NULL; v8js_globals->v8_flags = NULL;
v8js_globals->timer_thread = NULL;
v8js_globals->timer_stop = false;
} }
/* }}} */ /* }}} */