Skip to content

Commit e16fb63

Browse files
committed
Merge pull request #168 from stesie/multithreading
Fix pthread extension compatibility
2 parents 6779cc5 + b292715 commit e16fb63

File tree

7 files changed

+218
-50
lines changed

7 files changed

+218
-50
lines changed

Makefile.frag

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ ifneq (,$(realpath $(EXTENSION_DIR)/json.so))
33
PHP_TEST_SHARED_EXTENSIONS+=-d extension=$(EXTENSION_DIR)/json.so
44
endif
55

6+
# add pthreads extension, if available
7+
ifneq (,$(realpath $(EXTENSION_DIR)/pthreads.so))
8+
PHP_TEST_SHARED_EXTENSIONS+=-d extension=$(EXTENSION_DIR)/pthreads.so
9+
endif
10+
611
testv8: all
712
$(PHP_EXECUTABLE) -n -d extension_dir=./modules -d extension=v8js.so test.php
813

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ Minimum requirements
2424

2525
- PHP 5.3.3+
2626

27-
This embedded implementation of the V8 engine uses thread locking so it should work with ZTS enabled.
28-
However, this has not been tested yet.
27+
This embedded implementation of the V8 engine uses thread locking so it works with ZTS enabled.
2928

3029

3130
Compiling latest version

php_v8js_macros.h

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,10 @@ void v8js_register_accessors(std::vector<v8js_accessor_ctx*> *accessor_list, v8:
111111

112112
/* Module globals */
113113
ZEND_BEGIN_MODULE_GLOBALS(v8js)
114+
// Thread-local cache whether V8 has been initialized so far
114115
int v8_initialized;
115-
#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
116-
v8::Platform *v8_platform;
117-
#endif
118-
HashTable *extensions;
119116

120117
/* Ini globals */
121-
char *v8_flags; /* V8 command line flags */
122118
bool use_date; /* Generate JS Date objects instead of PHP DateTime */
123119
bool use_array_access; /* Convert ArrayAccess, Countable objects to array-like objects */
124120
bool compat_php_exceptions; /* Don't stop JS execution on PHP exception */
@@ -142,6 +138,39 @@ ZEND_EXTERN_MODULE_GLOBALS(v8js)
142138
# define V8JSG(v) (v8js_globals.v)
143139
#endif
144140

141+
/*
142+
* Process-wide globals
143+
*
144+
* The zend_v8js_globals structure declared above is created once per thread
145+
* (in a ZTS environment). If a multi-threaded PHP process uses V8 there is
146+
* some stuff shared among all of these threads
147+
*
148+
* - whether V8 has been initialized at all
149+
* - the V8 backend platform
150+
* - loaded extensions
151+
* - V8 "command line" flags
152+
*
153+
* In a ZTS-enabled environment access to all of these variables must happen
154+
* while holding a mutex lock.
155+
*/
156+
struct _v8js_process_globals {
157+
#ifdef ZTS
158+
int v8_initialized;
159+
std::mutex lock;
160+
#endif
161+
162+
HashTable *extensions;
163+
164+
/* V8 command line flags */
165+
char *v8_flags;
166+
167+
#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
168+
v8::Platform *v8_platform;
169+
#endif
170+
};
171+
172+
extern struct _v8js_process_globals v8js_process_globals;
173+
145174
/* Register builtin methods into passed object */
146175
void v8js_register_methods(v8::Handle<v8::ObjectTemplate>, v8js_ctx *c);
147176

tests/pthreads_001.phpt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
Test V8::executeString() : Pthreads test #1
3+
--SKIPIF--
4+
<?php
5+
require_once(dirname(__FILE__) . '/skipif.inc');
6+
if(!class_exists('Thread')) {
7+
die('skip pthreads extension required');
8+
}
9+
?>
10+
--FILE--
11+
<?php
12+
13+
class Workhorse extends Thread
14+
{
15+
protected $val;
16+
17+
public function __construct($val)
18+
{
19+
$this->val = $val;
20+
}
21+
22+
public function run()
23+
{
24+
$v8 = new V8Js();
25+
$v8->val = $this->val;
26+
$v8->sync_var_dump = function($value) {
27+
$this->synchronized(function($thread) use ($value) {
28+
while(!$thread->readyToPrint) {
29+
$thread->wait();
30+
}
31+
var_dump($value);
32+
$thread->notify();
33+
}, $this);
34+
};
35+
36+
$v8->executeString('PHP.sync_var_dump(PHP.val);');
37+
}
38+
}
39+
40+
$foo = new Workhorse('foo');
41+
$bar = new Workhorse('bar');
42+
43+
$foo->start();
44+
$bar->start();
45+
46+
$bar->synchronized(function($thread) {
47+
$thread->readyToPrint = true;
48+
$thread->notify();
49+
$thread->wait();
50+
}, $bar);
51+
52+
$foo->synchronized(function($thread) {
53+
$thread->readyToPrint = true;
54+
$thread->notify();
55+
$thread->wait();
56+
}, $foo);
57+
58+
$foo->join();
59+
$bar->join();
60+
?>
61+
===EOF===
62+
--EXPECT--
63+
string(3) "bar"
64+
string(3) "foo"
65+
===EOF===

v8js.cc

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,41 @@ extern "C" {
2929
#include "v8js_v8object_class.h"
3030

3131
ZEND_DECLARE_MODULE_GLOBALS(v8js)
32+
struct _v8js_process_globals v8js_process_globals;
3233

3334
/* {{{ INI Settings */
3435

3536
static ZEND_INI_MH(v8js_OnUpdateV8Flags) /* {{{ */
3637
{
38+
bool immutable = false;
39+
40+
#ifdef ZTS
41+
v8js_process_globals.lock.lock();
42+
43+
if(v8js_process_globals.v8_initialized) {
44+
v8js_process_globals.lock.unlock();
45+
immutable = true;
46+
}
47+
48+
v8js_process_globals.lock.unlock();
49+
#else
50+
immutable = V8JSG(v8_initialized);
51+
#endif
52+
53+
if(immutable) {
54+
/* V8 already has been initialized -> cannot be changed anymore */
55+
return FAILURE;
56+
}
57+
3758
if (new_value) {
38-
if (V8JSG(v8_flags)) {
39-
free(V8JSG(v8_flags));
40-
V8JSG(v8_flags) = NULL;
59+
if (v8js_process_globals.v8_flags) {
60+
free(v8js_process_globals.v8_flags);
61+
v8js_process_globals.v8_flags = NULL;
4162
}
4263
if (!new_value[0]) {
4364
return FAILURE;
4465
}
45-
V8JSG(v8_flags) = zend_strndup(new_value, new_value_length);
66+
v8js_process_globals.v8_flags = zend_strndup(new_value, new_value_length);
4667
}
4768

4869
return SUCCESS;
@@ -118,6 +139,35 @@ PHP_MINIT_FUNCTION(v8js)
118139
static PHP_MSHUTDOWN_FUNCTION(v8js)
119140
{
120141
UNREGISTER_INI_ENTRIES();
142+
143+
bool v8_initialized;
144+
145+
#ifdef ZTS
146+
v8_initialized = v8js_process_globals.v8_initialized;
147+
#else
148+
v8_initialized = V8JSG(v8_initialized);
149+
#endif
150+
151+
if(v8_initialized) {
152+
v8::V8::Dispose();
153+
#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
154+
v8::V8::ShutdownPlatform();
155+
// @fixme call virtual destructor somehow
156+
//delete v8js_process_globals.v8_platform;
157+
#endif
158+
}
159+
160+
if (v8js_process_globals.v8_flags) {
161+
free(v8js_process_globals.v8_flags);
162+
v8js_process_globals.v8_flags = NULL;
163+
}
164+
165+
if (v8js_process_globals.extensions) {
166+
zend_hash_destroy(v8js_process_globals.extensions);
167+
free(v8js_process_globals.extensions);
168+
v8js_process_globals.extensions = NULL;
169+
}
170+
121171
return SUCCESS;
122172
}
123173
/* }}} */
@@ -169,9 +219,7 @@ static PHP_GINIT_FUNCTION(v8js)
169219
run the destructors manually.
170220
*/
171221
#ifdef ZTS
172-
v8js_globals->extensions = NULL;
173222
v8js_globals->v8_initialized = 0;
174-
v8js_globals->v8_flags = NULL;
175223

176224
v8js_globals->timer_thread = NULL;
177225
v8js_globals->timer_stop = false;
@@ -187,29 +235,10 @@ static PHP_GINIT_FUNCTION(v8js)
187235
*/
188236
static PHP_GSHUTDOWN_FUNCTION(v8js)
189237
{
190-
if (v8js_globals->extensions) {
191-
zend_hash_destroy(v8js_globals->extensions);
192-
free(v8js_globals->extensions);
193-
v8js_globals->extensions = NULL;
194-
}
195-
196-
if (v8js_globals->v8_flags) {
197-
free(v8js_globals->v8_flags);
198-
v8js_globals->v8_flags = NULL;
199-
}
200-
201238
#ifdef ZTS
202239
v8js_globals->timer_stack.~deque();
203240
v8js_globals->timer_mutex.~mutex();
204241
#endif
205-
206-
if (v8js_globals->v8_initialized) {
207-
v8::V8::Dispose();
208-
#if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036
209-
v8::V8::ShutdownPlatform();
210-
delete v8js_globals->v8_platform;
211-
#endif
212-
}
213242
}
214243
/* }}} */
215244

v8js_class.cc

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -827,10 +827,17 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint
827827
{
828828
v8js_jsext *jsext = NULL;
829829

830-
if (!V8JSG(extensions)) {
831-
V8JSG(extensions) = (HashTable *) malloc(sizeof(HashTable));
832-
zend_hash_init(V8JSG(extensions), 1, NULL, (dtor_func_t) v8js_jsext_dtor, 1);
833-
} else if (zend_hash_exists(V8JSG(extensions), name, name_len + 1)) {
830+
#ifdef ZTS
831+
v8js_process_globals.lock.lock();
832+
#endif
833+
834+
if (!v8js_process_globals.extensions) {
835+
v8js_process_globals.extensions = (HashTable *) malloc(sizeof(HashTable));
836+
zend_hash_init(v8js_process_globals.extensions, 1, NULL, (dtor_func_t) v8js_jsext_dtor, 1);
837+
} else if (zend_hash_exists(v8js_process_globals.extensions, name, name_len + 1)) {
838+
#ifdef ZTS
839+
v8js_process_globals.lock.unlock();
840+
#endif
834841
return FAILURE;
835842
}
836843

@@ -843,6 +850,9 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint
843850
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid dependency array passed");
844851
v8js_jsext_dtor(jsext);
845852
free(jsext);
853+
#ifdef ZTS
854+
v8js_process_globals.lock.unlock();
855+
#endif
846856
return FAILURE;
847857
}
848858
}
@@ -859,17 +869,23 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint
859869

860870
jsext->extension = new v8::Extension(jsext->name, jsext->source, jsext->deps_count, jsext->deps);
861871

862-
if (zend_hash_add(V8JSG(extensions), name, name_len + 1, jsext, sizeof(v8js_jsext), NULL) == FAILURE) {
872+
if (zend_hash_add(v8js_process_globals.extensions, name, name_len + 1, jsext, sizeof(v8js_jsext), NULL) == FAILURE) {
863873
v8js_jsext_dtor(jsext);
864874
free(jsext);
875+
#ifdef ZTS
876+
v8js_process_globals.lock.unlock();
877+
#endif
865878
return FAILURE;
866879
}
867880

881+
#ifdef ZTS
882+
v8js_process_globals.lock.unlock();
883+
#endif
884+
868885
jsext->extension->set_auto_enable(auto_enable ? true : false);
869886
v8::RegisterExtension(jsext->extension);
870887

871888
free(jsext);
872-
873889
return SUCCESS;
874890
}
875891
/* }}} */
@@ -916,10 +932,15 @@ static PHP_METHOD(V8Js, getExtensions)
916932
}
917933

918934
array_init(return_value);
919-
if (V8JSG(extensions)) {
920-
zend_hash_internal_pointer_reset_ex(V8JSG(extensions), &pos);
921-
while (zend_hash_get_current_data_ex(V8JSG(extensions), (void **) &jsext, &pos) == SUCCESS) {
922-
if (zend_hash_get_current_key_ex(V8JSG(extensions), &key, &key_len, &index, 0, &pos) == HASH_KEY_IS_STRING) {
935+
936+
#ifdef ZTS
937+
v8js_process_globals.lock.lock();
938+
#endif
939+
940+
if (v8js_process_globals.extensions) {
941+
zend_hash_internal_pointer_reset_ex(v8js_process_globals.extensions, &pos);
942+
while (zend_hash_get_current_data_ex(v8js_process_globals.extensions, (void **) &jsext, &pos) == SUCCESS) {
943+
if (zend_hash_get_current_key_ex(v8js_process_globals.extensions, &key, &key_len, &index, 0, &pos) == HASH_KEY_IS_STRING) {
923944
MAKE_STD_ZVAL(ext)
924945
array_init(ext);
925946
add_assoc_bool_ex(ext, ZEND_STRS("auto_enable"), jsext->auto_enable);
@@ -931,9 +952,13 @@ static PHP_METHOD(V8Js, getExtensions)
931952
}
932953
add_assoc_zval_ex(return_value, key, key_len, ext);
933954
}
934-
zend_hash_move_forward_ex(V8JSG(extensions), &pos);
955+
zend_hash_move_forward_ex(v8js_process_globals.extensions, &pos);
935956
}
936957
}
958+
959+
#ifdef ZTS
960+
v8js_process_globals.lock.unlock();
961+
#endif
937962
}
938963
/* }}} */
939964

0 commit comments

Comments
 (0)