فهرست منبع

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.
Simon Best 12 سال پیش
والد
کامیت
15dc9e157b
2فایلهای تغییر یافته به همراه123 افزوده شده و 16 حذف شده
  1. 1 1
      config.m4
  2. 122 15
      v8js.cc

+ 1 - 1
config.m4

@@ -67,7 +67,7 @@ LDFLAGS=$old_LDFLAGS
     AC_DEFINE_UNQUOTED([PHP_V8_VERSION], "$ac_cv_v8_version", [ ])
   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
 fi

+ 122 - 15
v8js.cc

@@ -38,10 +38,34 @@ extern "C" {
 #include <v8.h>
 #include "php_v8js_macros.h"
 
+#include <chrono>
+#include <stack>
+#include <thread>
+
 /* Forward declarations */
 static void php_v8js_throw_exception(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 */
 ZEND_BEGIN_MODULE_GLOBALS(v8js)
 	int v8_initialized;
@@ -52,6 +76,11 @@ ZEND_BEGIN_MODULE_GLOBALS(v8js)
 	/* Ini globals */
 	char *v8_flags; /* V8 command line flags */
 	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)
 
 #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 */
 struct php_v8js_object {
 	zend_object std;
@@ -579,6 +597,8 @@ static PHP_METHOD(V8Js, __construct)
 	c->report_uncaught = report_uncaught;
 	c->pending_exception = NULL;
 	c->in_execution = 0;
+	c->isolate = v8::Isolate::New();
+	c->execution_terminated = false;
 
 	/* Initialize V8 */
 	php_v8js_init(TSRMLS_C);
@@ -597,6 +617,10 @@ static PHP_METHOD(V8Js, __construct)
 	/* Declare configuration for extensions */
 	v8::ExtensionConfiguration extension_conf(exts_count, exts);
 
+	// Isolate execution
+	v8::Locker locker(c->isolate);
+	v8::Isolate::Scope isolate_scope(c->isolate);
+
 	/* 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); \
+	v8::Locker locker((ctx)->isolate); \
+	v8::Isolate::Scope isolate_scope((ctx)->isolate); \
 	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]])
  */
 static PHP_METHOD(V8Js, executeString)
 {
 	char *str = NULL, *identifier = NULL;
 	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;
 	}
 
@@ -700,14 +780,32 @@ static PHP_METHOD(V8Js, executeString)
 	/* Set flags for runtime use */
 	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 */
 	c->in_execution++;
 	v8::Local<v8::Value> result = script->Run();
 	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()) {
-		/* TODO: throw PHP exception here? */
+		// At this point we can't re-throw the exception
 		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, identifier)
 	ZEND_ARG_INFO(0, flags)
+	ZEND_ARG_INFO(0, timeout)
 ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_INFO(arginfo_v8js_getpendingexception, 0)
@@ -1211,6 +1310,12 @@ static PHP_MSHUTDOWN_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
 	v8::HeapStatistics stats;
 	v8::V8::GetHeapStatistics(&stats);
@@ -1254,6 +1359,8 @@ static PHP_GINIT_FUNCTION(v8js)
 	v8js_globals->disposed_contexts = 0;
 	v8js_globals->v8_initialized = 0;
 	v8js_globals->v8_flags = NULL;
+	v8js_globals->timer_thread = NULL;
+	v8js_globals->timer_stop = false;
 }
 /* }}} */