Selaa lähdekoodia

Merge pull request #168 from stesie/multithreading

Fix pthread extension compatibility
Stefan Siegl 9 vuotta sitten
vanhempi
commit
e16fb63899
7 muutettua tiedostoa jossa 218 lisäystä ja 50 poistoa
  1. 5 0
      Makefile.frag
  2. 1 2
      README.md
  3. 34 5
      php_v8js_macros.h
  4. 65 0
      tests/pthreads_001.phpt
  5. 54 25
      v8js.cc
  6. 36 11
      v8js_class.cc
  7. 23 7
      v8js_v8.cc

+ 5 - 0
Makefile.frag

@@ -3,6 +3,11 @@ ifneq (,$(realpath $(EXTENSION_DIR)/json.so))
 PHP_TEST_SHARED_EXTENSIONS+=-d extension=$(EXTENSION_DIR)/json.so
 endif
 
+# add pthreads extension, if available
+ifneq (,$(realpath $(EXTENSION_DIR)/pthreads.so))
+PHP_TEST_SHARED_EXTENSIONS+=-d extension=$(EXTENSION_DIR)/pthreads.so
+endif
+
 testv8: all
 	$(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
 

+ 1 - 2
README.md

@@ -24,8 +24,7 @@ Minimum requirements
 
 - PHP 5.3.3+
 
-  This embedded implementation of the V8 engine uses thread locking so it should work with ZTS enabled.
-  However, this has not been tested yet.
+  This embedded implementation of the V8 engine uses thread locking so it works with ZTS enabled.
 
 
 Compiling latest version

+ 34 - 5
php_v8js_macros.h

@@ -111,14 +111,10 @@ void v8js_register_accessors(std::vector<v8js_accessor_ctx*> *accessor_list, v8:
 
 /* Module globals */
 ZEND_BEGIN_MODULE_GLOBALS(v8js)
+  // Thread-local cache whether V8 has been initialized so far
   int v8_initialized;
-#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
-  v8::Platform *v8_platform;
-#endif
-  HashTable *extensions;
 
   /* Ini globals */
-  char *v8_flags; /* V8 command line flags */
   bool use_date; /* Generate JS Date objects instead of PHP DateTime */
   bool use_array_access; /* Convert ArrayAccess, Countable objects to array-like objects */
   bool compat_php_exceptions; /* Don't stop JS execution on PHP exception */
@@ -142,6 +138,39 @@ ZEND_EXTERN_MODULE_GLOBALS(v8js)
 # define V8JSG(v) (v8js_globals.v)
 #endif
 
+/*
+ *  Process-wide globals
+ *
+ * The zend_v8js_globals structure declared above is created once per thread
+ * (in a ZTS environment).  If a multi-threaded PHP process uses V8 there is
+ * some stuff shared among all of these threads
+ *
+ *  - whether V8 has been initialized at all
+ *  - the V8 backend platform
+ *  - loaded extensions
+ *  - V8 "command line" flags
+ *
+ * In a ZTS-enabled environment access to all of these variables must happen
+ * while holding a mutex lock.
+ */
+struct _v8js_process_globals {
+#ifdef ZTS
+	int v8_initialized;
+	std::mutex lock;
+#endif
+
+	HashTable *extensions;
+
+	/* V8 command line flags */
+	char *v8_flags;
+
+#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
+	v8::Platform *v8_platform;
+#endif
+};
+
+extern struct _v8js_process_globals v8js_process_globals;
+
 /* Register builtin methods into passed object */
 void v8js_register_methods(v8::Handle<v8::ObjectTemplate>, v8js_ctx *c);
 

+ 65 - 0
tests/pthreads_001.phpt

@@ -0,0 +1,65 @@
+--TEST--
+Test V8::executeString() : Pthreads test #1
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . '/skipif.inc');
+if(!class_exists('Thread')) {
+    die('skip pthreads extension required');
+}
+?>
+--FILE--
+<?php
+
+class Workhorse extends Thread
+{
+    protected $val;
+
+    public function __construct($val)
+    {
+        $this->val = $val;
+    }
+
+    public function run()
+    {
+        $v8 = new V8Js();
+        $v8->val = $this->val;
+        $v8->sync_var_dump = function($value) {
+            $this->synchronized(function($thread) use ($value) {
+                    while(!$thread->readyToPrint) {
+                        $thread->wait();
+                    }
+                    var_dump($value);
+                    $thread->notify();
+                }, $this);
+        };
+
+        $v8->executeString('PHP.sync_var_dump(PHP.val);');
+    }
+}
+
+$foo = new Workhorse('foo');
+$bar = new Workhorse('bar');
+
+$foo->start();
+$bar->start();
+
+$bar->synchronized(function($thread) {
+    $thread->readyToPrint = true;
+    $thread->notify();
+    $thread->wait();
+}, $bar);
+
+$foo->synchronized(function($thread) {
+    $thread->readyToPrint = true;
+    $thread->notify();
+    $thread->wait();
+}, $foo);
+
+$foo->join();
+$bar->join();
+?>
+===EOF===
+--EXPECT--
+string(3) "bar"
+string(3) "foo"
+===EOF===

+ 54 - 25
v8js.cc

@@ -29,20 +29,41 @@ extern "C" {
 #include "v8js_v8object_class.h"
 
 ZEND_DECLARE_MODULE_GLOBALS(v8js)
+struct _v8js_process_globals v8js_process_globals;
 
 /* {{{ INI Settings */
 
 static ZEND_INI_MH(v8js_OnUpdateV8Flags) /* {{{ */
 {
+	bool immutable = false;
+
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+
+	if(v8js_process_globals.v8_initialized) {
+		v8js_process_globals.lock.unlock();
+		immutable = true;
+	}
+
+	v8js_process_globals.lock.unlock();
+#else
+	immutable = V8JSG(v8_initialized);
+#endif
+
+	if(immutable) {
+		/* V8 already has been initialized -> cannot be changed anymore */
+		return FAILURE;
+	}
+
 	if (new_value) {
-		if (V8JSG(v8_flags)) {
-			free(V8JSG(v8_flags));
-			V8JSG(v8_flags) = NULL;
+		if (v8js_process_globals.v8_flags) {
+			free(v8js_process_globals.v8_flags);
+			v8js_process_globals.v8_flags = NULL;
 		}
 		if (!new_value[0]) {
 			return FAILURE;
 		}
-		V8JSG(v8_flags) = zend_strndup(new_value, new_value_length);
+		v8js_process_globals.v8_flags = zend_strndup(new_value, new_value_length);
 	}
 
 	return SUCCESS;
@@ -118,6 +139,35 @@ PHP_MINIT_FUNCTION(v8js)
 static PHP_MSHUTDOWN_FUNCTION(v8js)
 {
 	UNREGISTER_INI_ENTRIES();
+
+	bool v8_initialized;
+
+#ifdef ZTS
+	v8_initialized = v8js_process_globals.v8_initialized;
+#else
+	v8_initialized = V8JSG(v8_initialized);
+#endif
+
+	if(v8_initialized) {
+		v8::V8::Dispose();
+#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
+		v8::V8::ShutdownPlatform();
+		// @fixme call virtual destructor somehow
+		//delete v8js_process_globals.v8_platform;
+#endif
+	}
+
+	if (v8js_process_globals.v8_flags) {
+		free(v8js_process_globals.v8_flags);
+		v8js_process_globals.v8_flags = NULL;
+	}
+
+	if (v8js_process_globals.extensions) {
+		zend_hash_destroy(v8js_process_globals.extensions);
+		free(v8js_process_globals.extensions);
+		v8js_process_globals.extensions = NULL;
+	}
+
 	return SUCCESS;
 }
 /* }}} */
@@ -169,9 +219,7 @@ static PHP_GINIT_FUNCTION(v8js)
 	  run the destructors manually.
 	*/
 #ifdef ZTS
-	v8js_globals->extensions = NULL;
 	v8js_globals->v8_initialized = 0;
-	v8js_globals->v8_flags = NULL;
 
 	v8js_globals->timer_thread = NULL;
 	v8js_globals->timer_stop = false;
@@ -187,29 +235,10 @@ static PHP_GINIT_FUNCTION(v8js)
  */
 static PHP_GSHUTDOWN_FUNCTION(v8js)
 {
-	if (v8js_globals->extensions) {
-		zend_hash_destroy(v8js_globals->extensions);
-		free(v8js_globals->extensions);
-		v8js_globals->extensions = NULL;
-	}
-
-	if (v8js_globals->v8_flags) {
-		free(v8js_globals->v8_flags);
-		v8js_globals->v8_flags = NULL;
-	}
-
 #ifdef ZTS
 	v8js_globals->timer_stack.~deque();
 	v8js_globals->timer_mutex.~mutex();
 #endif
-
-	if (v8js_globals->v8_initialized) {
-		v8::V8::Dispose();
-#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
-		v8::V8::ShutdownPlatform();
-		delete v8js_globals->v8_platform;
-#endif
-	}
 }
 /* }}} */
 

+ 36 - 11
v8js_class.cc

@@ -827,10 +827,17 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint
 {
 	v8js_jsext *jsext = NULL;
 
-	if (!V8JSG(extensions)) {
-		V8JSG(extensions) = (HashTable *) malloc(sizeof(HashTable));
-		zend_hash_init(V8JSG(extensions), 1, NULL, (dtor_func_t) v8js_jsext_dtor, 1);
-	} else if (zend_hash_exists(V8JSG(extensions), name, name_len + 1)) {
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+#endif
+
+	if (!v8js_process_globals.extensions) {
+		v8js_process_globals.extensions = (HashTable *) malloc(sizeof(HashTable));
+		zend_hash_init(v8js_process_globals.extensions, 1, NULL, (dtor_func_t) v8js_jsext_dtor, 1);
+	} else if (zend_hash_exists(v8js_process_globals.extensions, name, name_len + 1)) {
+#ifdef ZTS
+		v8js_process_globals.lock.unlock();
+#endif
 		return FAILURE;
 	}
 
@@ -843,6 +850,9 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint
 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid dependency array passed");
 			v8js_jsext_dtor(jsext);
 			free(jsext);
+#ifdef ZTS
+			v8js_process_globals.lock.unlock();
+#endif
 			return FAILURE;
 		}
 	}
@@ -859,17 +869,23 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint
 
 	jsext->extension = new v8::Extension(jsext->name, jsext->source, jsext->deps_count, jsext->deps);
 
-	if (zend_hash_add(V8JSG(extensions), name, name_len + 1, jsext, sizeof(v8js_jsext), NULL) == FAILURE) {
+	if (zend_hash_add(v8js_process_globals.extensions, name, name_len + 1, jsext, sizeof(v8js_jsext), NULL) == FAILURE) {
 		v8js_jsext_dtor(jsext);
 		free(jsext);
+#ifdef ZTS
+		v8js_process_globals.lock.unlock();
+#endif
 		return FAILURE;
 	}
 
+#ifdef ZTS
+	v8js_process_globals.lock.unlock();
+#endif
+
 	jsext->extension->set_auto_enable(auto_enable ? true : false);
 	v8::RegisterExtension(jsext->extension);
 
 	free(jsext);
-
 	return SUCCESS;
 }
 /* }}} */
@@ -916,10 +932,15 @@ static PHP_METHOD(V8Js, getExtensions)
 	}
 
 	array_init(return_value);
-	if (V8JSG(extensions)) {
-		zend_hash_internal_pointer_reset_ex(V8JSG(extensions), &pos);
-		while (zend_hash_get_current_data_ex(V8JSG(extensions), (void **) &jsext, &pos) == SUCCESS) {
-			if (zend_hash_get_current_key_ex(V8JSG(extensions), &key, &key_len, &index, 0, &pos) == HASH_KEY_IS_STRING) {
+
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+#endif
+
+	if (v8js_process_globals.extensions) {
+		zend_hash_internal_pointer_reset_ex(v8js_process_globals.extensions, &pos);
+		while (zend_hash_get_current_data_ex(v8js_process_globals.extensions, (void **) &jsext, &pos) == SUCCESS) {
+			if (zend_hash_get_current_key_ex(v8js_process_globals.extensions, &key, &key_len, &index, 0, &pos) == HASH_KEY_IS_STRING) {
 				MAKE_STD_ZVAL(ext)
 				array_init(ext);
 				add_assoc_bool_ex(ext, ZEND_STRS("auto_enable"), jsext->auto_enable);
@@ -931,9 +952,13 @@ static PHP_METHOD(V8Js, getExtensions)
 				}
 				add_assoc_zval_ex(return_value, key, key_len, ext);
 			}
-			zend_hash_move_forward_ex(V8JSG(extensions), &pos);
+			zend_hash_move_forward_ex(v8js_process_globals.extensions, &pos);
 		}
 	}
+
+#ifdef ZTS
+	v8js_process_globals.lock.unlock();
+#endif
 }
 /* }}} */
 

+ 23 - 7
v8js_v8.cc

@@ -37,26 +37,42 @@ extern "C" {
 
 void v8js_v8_init(TSRMLS_D) /* {{{ */
 {
-	/* Run only once */
+	/* Run only once; thread-local test first */
 	if (V8JSG(v8_initialized)) {
 		return;
 	}
 
+	/* Set thread-local flag, that V8 was initialized. */
+	V8JSG(v8_initialized) = 1;
+
+#ifdef ZTS
+	v8js_process_globals.lock.lock();
+
+	if(v8js_process_globals.v8_initialized) {
+		/* V8 already has been initialized by another thread */
+		v8js_process_globals.lock.unlock();
+		return;
+	}
+#endif
+
 #if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
-	V8JSG(v8_platform) = v8::platform::CreateDefaultPlatform();
-	v8::V8::InitializePlatform(V8JSG(v8_platform));
+	v8js_process_globals.v8_platform = v8::platform::CreateDefaultPlatform();
+	v8::V8::InitializePlatform(v8js_process_globals.v8_platform);
 #endif
 
 	/* Set V8 command line flags (must be done before V8::Initialize()!) */
-	if (V8JSG(v8_flags)) {
-		v8::V8::SetFlagsFromString(V8JSG(v8_flags), strlen(V8JSG(v8_flags)));
+	if (v8js_process_globals.v8_flags) {
+		v8::V8::SetFlagsFromString(v8js_process_globals.v8_flags,
+								   strlen(v8js_process_globals.v8_flags));
 	}
 
 	/* Initialize V8 */
 	v8::V8::Initialize();
 
-	/* Run only once */
-	V8JSG(v8_initialized) = 1;
+#ifdef ZTS
+	v8js_process_globals.v8_initialized = 1;
+	v8js_process_globals.lock.unlock();
+#endif
 }
 /* }}} */