diff --git a/classes/thread.c b/classes/thread.c index 083353ef..43b52487 100644 --- a/classes/thread.c +++ b/classes/thread.c @@ -129,3 +129,16 @@ Thread_method(getRunningCount) RETURN_LONG(count); } + +/* {{{ proto void Thread::setAutoloadFile(string|null $file) + Sets the path of the file used to set up new threads */ +Thread_method(setAutoloadFile) +{ + zend_string* file; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + Z_PARAM_STR(file) + ZEND_PARSE_PARAMETERS_END(); + + pmmpthread_globals_set_autoload_file(file); +} /* }}} */ diff --git a/src/globals.c b/src/globals.c index b9eb1ef8..76acac16 100644 --- a/src/globals.c +++ b/src/globals.c @@ -118,6 +118,8 @@ zend_bool pmmpthread_globals_init(){ PMMPTHREAD_G(thread_count) = 0; //only counting threads explicitly created by pmmpthread } + PMMPTHREAD_G(autoload_file) = NULL; + #define INIT_STRING(n, v) do { \ PMMPTHREAD_G(strings).n = zend_new_interned_string(zend_string_init(v, 1)); \ } while(0) @@ -222,6 +224,21 @@ zend_bool pmmpthread_globals_socket_shared(PHP_SOCKET socket) { } #endif +/* {{{ */ +zend_bool pmmpthread_globals_set_autoload_file(const zend_string *path) { + if (pmmpthread_globals_lock()) { + zend_string *copy = path ? zend_string_init(ZSTR_VAL(path), ZSTR_LEN(path), 1) : NULL; + + if (PMMPTHREAD_G(autoload_file)) { + zend_string_release(PMMPTHREAD_G(autoload_file)); + } + PMMPTHREAD_G(autoload_file) = copy; + pmmpthread_globals_unlock(); + return 1; + } + return 0; +} /* }}} */ + /* {{{ */ void pmmpthread_globals_shutdown() { if (PMMPTHREAD_G(init)) { diff --git a/src/globals.h b/src/globals.h index f331e420..6e2845d4 100644 --- a/src/globals.h +++ b/src/globals.h @@ -61,6 +61,11 @@ struct _pmmpthread_globals { zval undef_zval; + /* + * File included on all new threads before any user code runs, usually an autoloader + */ + zend_string *autoload_file; + /* * High Frequency Strings */ @@ -113,6 +118,9 @@ zend_bool pmmpthread_globals_lock(); /* }}} */ /* {{{ release global lock */ void pmmpthread_globals_unlock(); /* }}} */ +/* {{{ set autoload file used to bootstrap new threads */ +zend_bool pmmpthread_globals_set_autoload_file(const zend_string *autoload_file); /* }}} */ + /* {{{ shutdown global structures */ void pmmpthread_globals_shutdown(); /* }}} */ diff --git a/src/prepare.c b/src/prepare.c index 10066104..796de4d1 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -22,6 +22,7 @@ #include #include #include +#include #define PMMPTHREAD_PREPARATION_BEGIN_CRITICAL() pmmpthread_globals_lock(); #define PMMPTHREAD_PREPARATION_END_CRITICAL() pmmpthread_globals_unlock() @@ -826,8 +827,7 @@ static inline void pmmpthread_prepare_sapi(const pmmpthread_ident_t* source) { } /* }}} */ /* {{{ */ -int pmmpthread_prepared_startup(pmmpthread_object_t* thread, pmmpthread_monitor_t *ready, zend_class_entry *thread_ce, zend_ulong thread_options) { - +int pmmpthread_prepared_startup(pmmpthread_object_t* thread, pmmpthread_monitor_t *ready, zend_class_entry *thread_ce, zend_ulong thread_options, zend_string **autoload_file) { PMMPTHREAD_PREPARATION_BEGIN_CRITICAL() { thread->local.id = pmmpthread_self(); thread->local.ls = ts_resource(0); @@ -882,6 +882,9 @@ int pmmpthread_prepared_startup(pmmpthread_object_t* thread, pmmpthread_monitor_ if (thread_options & PMMPTHREAD_INHERIT_INCLUDES) pmmpthread_prepare_includes(&thread->creator); + if (PMMPTHREAD_G(autoload_file)) { + *autoload_file = zend_string_init(ZSTR_VAL(PMMPTHREAD_G(autoload_file)), ZSTR_LEN(PMMPTHREAD_G(autoload_file)), 1); + } pmmpthread_monitor_add(ready, PMMPTHREAD_MONITOR_READY); PMMPTHREAD_G(thread_count)++; diff --git a/src/prepare.h b/src/prepare.h index 275f4c2a..834e27a6 100644 --- a/src/prepare.h +++ b/src/prepare.h @@ -30,7 +30,7 @@ void pmmpthread_prepared_entry_late_bindings(const pmmpthread_ident_t* source, z void pmmpthread_context_late_bindings(const pmmpthread_ident_t* source); /* }}} */ /* {{{ */ -int pmmpthread_prepared_startup(pmmpthread_object_t* thread, pmmpthread_monitor_t *ready, zend_class_entry *thread_ce, zend_ulong thread_options); /* }}} */ +int pmmpthread_prepared_startup(pmmpthread_object_t* thread, pmmpthread_monitor_t *ready, zend_class_entry *thread_ce, zend_ulong thread_options, zend_string **autoload_file); /* }}} */ /* {{{ */ void pmmpthread_call_shutdown_functions(void); /* }}} */ diff --git a/src/routine.c b/src/routine.c index 8cca1f9c..86a91d0b 100644 --- a/src/routine.c +++ b/src/routine.c @@ -35,6 +35,57 @@ static void pmmpthread_routine_free(pmmpthread_routine_arg_t* r) { pmmpthread_monitor_destroy(&r->ready); } /* }}} */ +/* {{{ Includes the autoloader provided, if any. This code is borrowed from krakjoe/parallel. */ +static int pmmpthread_routine_run_bootstrap(zend_string* file) { + zend_file_handle fh; + zend_op_array* ops; + zval rv; + int result; + + if (!file) { + return SUCCESS; + } + + zend_stream_init_filename_ex(&fh, file); + result = php_stream_open_for_zend_ex(&fh, USE_PATH | REPORT_ERRORS | STREAM_OPEN_FOR_INCLUDE); + + if (result != SUCCESS) { + zend_error(E_ERROR, "Unable to open thread autoload file %s", ZSTR_VAL(file)); + return FAILURE; + } + + zend_hash_add_empty_element(&EG(included_files), + fh.opened_path ? + fh.opened_path : file); + + ops = zend_compile_file(&fh, ZEND_REQUIRE); + + zend_destroy_file_handle(&fh); + + if (ops) { + ZVAL_UNDEF(&rv); + zend_execute(ops, &rv); + destroy_op_array(ops); + efree(ops); + + if (EG(exception)) { + zend_exception_error(EG(exception), E_ERROR); + zend_error(E_ERROR, "Uncaught exception thrown from thread autoload file %s", ZSTR_VAL(file)); + return FAILURE; + } + + zval_ptr_dtor(&rv); + return SUCCESS; + } + + if (EG(exception)) { + zend_exception_error(EG(exception), E_ERROR); + zend_error(E_ERROR, "Error compiling thread autoload file %s", ZSTR_VAL(file)); + } + + return FAILURE; +} /* }}} */ + /* {{{ */ static inline zend_result pmmpthread_routine_run_function(pmmpthread_zend_object_t* connection) { zend_function* run; @@ -106,12 +157,24 @@ static void* pmmpthread_routine(pmmpthread_routine_arg_t* routine) { zend_ulong thread_options = routine->options; pmmpthread_object_t* ts_obj = thread->ts_obj; pmmpthread_monitor_t* ready = &routine->ready; + zend_string* autoload_file = NULL; - if (pmmpthread_prepared_startup(ts_obj, ready, thread->std.ce, thread_options) == SUCCESS) { + if (pmmpthread_prepared_startup(ts_obj, ready, thread->std.ce, thread_options, &autoload_file) == SUCCESS) { pmmpthread_queue done_tasks_cache; memset(&done_tasks_cache, 0, sizeof(pmmpthread_queue)); zend_first_try{ + if (autoload_file != NULL) { + zend_try { + if (pmmpthread_routine_run_bootstrap(autoload_file) == FAILURE) { + zend_bailout(); + } + } zend_catch { + zend_string_release(autoload_file); + zend_bailout(); + } zend_end_try(); + } + ZVAL_UNDEF(&PMMPTHREAD_ZG(this)); pmmpthread_object_connect(thread, &PMMPTHREAD_ZG(this)); if (pmmpthread_routine_run_function(PMMPTHREAD_FETCH_FROM(Z_OBJ_P(&PMMPTHREAD_ZG(this)))) == FAILURE) { @@ -140,6 +203,8 @@ static void* pmmpthread_routine(pmmpthread_routine_arg_t* routine) { } } } + } zend_catch { + pmmpthread_monitor_add(&ts_obj->monitor, PMMPTHREAD_MONITOR_ERROR); } zend_end_try(); pmmpthread_call_shutdown_functions(); diff --git a/stubs/Thread.stub.php b/stubs/Thread.stub.php index ab6ec7c7..8699ebfe 100644 --- a/stubs/Thread.stub.php +++ b/stubs/Thread.stub.php @@ -151,6 +151,14 @@ public static function getSharedGlobals() : ThreadSafeArray{} */ public static function getRunningCount() : int{} + /** + * Sets the file to be included when a new thread is started + * Typically this would be the path of your vendor/autoload.php + * + * @param string $file + */ + public static function setAutoloadFile(string $file) : void{} + /** * Will return the identity of the referenced Thread * diff --git a/stubs/Thread_arginfo.h b/stubs/Thread_arginfo.h index 42ccc63f..673d0b14 100644 --- a/stubs/Thread_arginfo.h +++ b/stubs/Thread_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f07c76a75d7bf70fbbcf7bfe841cc003d1ec6727 */ + * Stub hash: 48854b4be7c9443ff7d4eb5490f3fd79a6bc6118 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_pmmp_thread_Thread_getCreatorId, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -14,6 +14,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_pmmp_thread_Thread_getRunningCount arginfo_class_pmmp_thread_Thread_getCreatorId +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_pmmp_thread_Thread_setAutoloadFile, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, file, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_pmmp_thread_Thread_getThreadId arginfo_class_pmmp_thread_Thread_getCreatorId ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_pmmp_thread_Thread_isJoined, 0, 0, _IS_BOOL, 0) @@ -33,6 +37,7 @@ ZEND_METHOD(pmmp_thread_Thread, getCurrentThread); ZEND_METHOD(pmmp_thread_Thread, getCurrentThreadId); ZEND_METHOD(pmmp_thread_Thread, getSharedGlobals); ZEND_METHOD(pmmp_thread_Thread, getRunningCount); +ZEND_METHOD(pmmp_thread_Thread, setAutoloadFile); ZEND_METHOD(pmmp_thread_Thread, getThreadId); ZEND_METHOD(pmmp_thread_Thread, isJoined); ZEND_METHOD(pmmp_thread_Thread, isStarted); @@ -46,6 +51,7 @@ static const zend_function_entry class_pmmp_thread_Thread_methods[] = { ZEND_ME(pmmp_thread_Thread, getCurrentThreadId, arginfo_class_pmmp_thread_Thread_getCurrentThreadId, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(pmmp_thread_Thread, getSharedGlobals, arginfo_class_pmmp_thread_Thread_getSharedGlobals, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(pmmp_thread_Thread, getRunningCount, arginfo_class_pmmp_thread_Thread_getRunningCount, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(pmmp_thread_Thread, setAutoloadFile, arginfo_class_pmmp_thread_Thread_setAutoloadFile, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(pmmp_thread_Thread, getThreadId, arginfo_class_pmmp_thread_Thread_getThreadId, ZEND_ACC_PUBLIC) ZEND_ME(pmmp_thread_Thread, isJoined, arginfo_class_pmmp_thread_Thread_isJoined, ZEND_ACC_PUBLIC) ZEND_ME(pmmp_thread_Thread, isStarted, arginfo_class_pmmp_thread_Thread_isStarted, ZEND_ACC_PUBLIC) diff --git a/tests/assets/TestAutoloadFile.php b/tests/assets/TestAutoloadFile.php new file mode 100644 index 00000000..3be072d4 --- /dev/null +++ b/tests/assets/TestAutoloadFile.php @@ -0,0 +1,7 @@ +start(Thread::INHERIT_NONE); +$thread->join(); +?> +--EXPECTF-- +Warning: Unknown: Failed to open stream: No such file or directory in Unknown on line 0 + +Fatal error: Unable to open thread autoload file %si-dont-exist.php in Unknown on line 0 diff --git a/tests/autoload-file-invalid-script.phpt b/tests/autoload-file-invalid-script.phpt new file mode 100644 index 00000000..01956247 --- /dev/null +++ b/tests/autoload-file-invalid-script.phpt @@ -0,0 +1,26 @@ +--TEST-- +Test that using Thread::setAutoloadFile() with a broken PHP file errors properly +--DESCRIPTION-- +Not sure if we can validate paths at the time of setting them. They might not exist +when set, or might be deleted before we can use them. This means it's ultimately +up to the thread itself to handle errors from wrong include paths correctly. +--FILE-- +start(Thread::INHERIT_NONE); +$thread->join(); +?> +--EXPECTF-- +Parse error: Unclosed '(' on line 3 in %s on line %d + +Fatal error: Error compiling thread autoload file %sautoload-file-syntax-error.php in Unknown on line 0 diff --git a/tests/autoload-file-recursion.phpt b/tests/autoload-file-recursion.phpt new file mode 100644 index 00000000..d9a77f7b --- /dev/null +++ b/tests/autoload-file-recursion.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test that using Thread::setAutoloadFile() inside an autoload file doesn't cause a deadlock +--FILE-- +start(Thread::INHERIT_NONE); +$thread->join(); +?> +--EXPECT-- +ok diff --git a/tests/autoload-file-throws-exception.phpt b/tests/autoload-file-throws-exception.phpt new file mode 100644 index 00000000..a32bd993 --- /dev/null +++ b/tests/autoload-file-throws-exception.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test that using Thread::setAutoloadFile() behaves properly when a file throws errors +--FILE-- +start(Thread::INHERIT_NONE); +$thread->join(); +?> +--EXPECTF-- +Fatal error: Uncaught Exception: cya in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d + +Fatal error: Uncaught exception thrown from thread autoload file %sautoload-file-uncaught-exception.php in Unknown on line 0 diff --git a/tests/autoload-file.phpt b/tests/autoload-file.phpt new file mode 100644 index 00000000..c16edfa0 --- /dev/null +++ b/tests/autoload-file.phpt @@ -0,0 +1,19 @@ +--TEST-- +Tests basic functionality of Thread::setAutoloadFile() +--FILE-- +hi(); + } +}; +$t->start(Thread::INHERIT_NONE); +$t->join(); +?> +--EXPECT-- +string(20) "everything is great!"