Selaa lähdekoodia

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 vuotta sitten
vanhempi
commit
15dc9e157b
2 muutettua tiedostoa jossa 123 lisäystä ja 16 poistoa
  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;
 }
 /* }}} */