From 9993d4039c786d474fc37d4979a9f3f08e924d11 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sat, 21 Mar 2026 23:40:04 +0100 Subject: [PATCH 1/3] catch shutdown bailouts. --- frankenphp.c | 48 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 07f9fdf67..c93c90ba6 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1033,12 +1033,7 @@ static void set_thread_name(char *thread_name) { #endif } -static void *php_thread(void *arg) { - thread_index = (uintptr_t)arg; - char thread_name[16] = {0}; - snprintf(thread_name, 16, "php-%" PRIxPTR, thread_index); - set_thread_name(thread_name); - +static void initialize_zts() { #ifdef ZTS /* initial resource fetch */ (void)ts_resource(0); @@ -1046,6 +1041,21 @@ static void *php_thread(void *arg) { ZEND_TSRMLS_CACHE_UPDATE(); #endif #endif +} + +static void shutdown_zts() { +#ifdef ZTS + ts_free_thread(); +#endif +} + +static void *php_thread(void *arg) { + thread_index = (uintptr_t)arg; + char thread_name[16] = {0}; + snprintf(thread_name, 16, "php-%" PRIxPTR, thread_index); + set_thread_name(thread_name); + + initialize_zts(); // loop until Go signals to stop char *scriptName = NULL; @@ -1054,9 +1064,7 @@ static void *php_thread(void *arg) { frankenphp_execute_script(scriptName)); } -#ifdef ZTS - ts_free_thread(); -#endif + shutdown_zts(); go_frankenphp_on_thread_shutdown(thread_index); @@ -1233,8 +1241,26 @@ int frankenphp_execute_script(char *file_name) { sandboxed_env = NULL; } - php_request_shutdown((void *)0); - frankenphp_free_request_context(); + zend_try { + php_request_shutdown((void *)0); + frankenphp_free_request_context(); + } + zend_catch { + /* + * php shutdown can also potentially bailout with an error + * clear all thread-local memory and re-initialize ZTS + * last error will also cause a crash if not cleared + */ + if (PG(last_error_message)) { + go_log_attrs(thread_index, PG(last_error_message), 8, NULL); + PG(last_error_message) = NULL; + PG(last_error_file) = NULL; + } + frankenphp_free_request_context(); + shutdown_zts(); + initialize_zts(); + } + zend_end_try(); return status; } From a35ed82295c57e5c6e579316dc4b820886adad7b Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 22 Mar 2026 10:57:01 +0100 Subject: [PATCH 2/3] Wraps the entire request block. --- frankenphp.c | 172 ++++++++++++++++++++++++++------------------------- frankenphp.h | 1 - 2 files changed, 88 insertions(+), 85 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index c93c90ba6..2a7a2cf19 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1033,40 +1033,109 @@ static void set_thread_name(char *thread_name) { #endif } -static void initialize_zts() { +static inline void reset_sandboxed_environment() { + if (sandboxed_env != NULL) { + zend_hash_release(sandboxed_env); + sandboxed_env = NULL; + } +} + +static void *php_thread(void *arg) { + thread_index = (uintptr_t)arg; + char thread_name[16] = {0}; + snprintf(thread_name, 16, "php-%" PRIxPTR, thread_index); + set_thread_name(thread_name); + + /* Initial allocation of all global PHP memory for this thread */ #ifdef ZTS - /* initial resource fetch */ (void)ts_resource(0); #ifdef PHP_WIN32 ZEND_TSRMLS_CACHE_UPDATE(); #endif #endif -} -static void shutdown_zts() { + bool thread_is_healthy = true; + bool has_attempted_shutdown = false; + + /* Main loop of the PHP thread, execute a PHP script and repeat until Go + * signals to stop */ + zend_first_try { + char *scriptName = NULL; + while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { + has_attempted_shutdown = false; + + frankenphp_update_request_context(); + + if (UNEXPECTED(php_request_startup() == FAILURE)) { + /* Request startup failed, bail out to zend_catch */ + frankenphp_log_message("Request startup failed, thread is unhealthy", + LOG_ERR); + zend_bailout(); + } + + zend_file_handle file_handle; + zend_stream_init_filename(&file_handle, scriptName); + + file_handle.primary_script = 1; + EG(exit_status) = 0; + + /* Execute the PHP script, potential bailout to zend_catch */ + php_execute_script(&file_handle); + zend_destroy_file_handle(&file_handle); + reset_sandboxed_environment(); + + has_attempted_shutdown = true; + + /* shutdown the request, potential bailout to zend_catch */ + php_request_shutdown((void *)0); + frankenphp_free_request_context(); + go_frankenphp_after_script_execution(thread_index, EG(exit_status)); + } + } + zend_catch { + /* Critical failure from php_execute_script or php_request_shutdown, mark + * the thread as unhealthy */ + thread_is_healthy = false; + if (!has_attempted_shutdown) { + /* php_request_shutdown() was not called, force a shutdown now */ + reset_sandboxed_environment(); + zend_try { php_request_shutdown((void *)0); } + zend_catch {} + zend_end_try(); + } + + /* Log the last error message, it must be cleared to prevent a crash when + * freeing execution globals */ + if (PG(last_error_message)) { + go_log_attrs(thread_index, PG(last_error_message), 8, NULL); + PG(last_error_message) = NULL; + PG(last_error_file) = NULL; + } + frankenphp_free_request_context(); + go_frankenphp_after_script_execution(thread_index, EG(exit_status)); + } + zend_end_try(); + #ifdef ZTS ts_free_thread(); #endif -} -static void *php_thread(void *arg) { - thread_index = (uintptr_t)arg; - char thread_name[16] = {0}; - snprintf(thread_name, 16, "php-%" PRIxPTR, thread_index); - set_thread_name(thread_name); - - initialize_zts(); + /* Thread is healthy, signal to Go that the thread has shut down */ + if (thread_is_healthy) { + go_frankenphp_on_thread_shutdown(thread_index); - // loop until Go signals to stop - char *scriptName = NULL; - while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { - go_frankenphp_after_script_execution(thread_index, - frankenphp_execute_script(scriptName)); + return NULL; } - shutdown_zts(); + /* Thread is unhealthy, PHP globals might be in a bad state after a bailout, + * restart the entire thread */ + frankenphp_log_message("Restarting unhealthy thread", LOG_WARNING); - go_frankenphp_on_thread_shutdown(thread_index); + if (!frankenphp_new_php_thread(thread_index)) { + /* probably unreachable */ + frankenphp_log_message("Failed to restart an unhealthy thread", + LOG_ERR); + } return NULL; } @@ -1200,71 +1269,6 @@ bool frankenphp_new_php_thread(uintptr_t thread_index) { return true; } -static int frankenphp_request_startup() { - frankenphp_update_request_context(); - if (php_request_startup() == SUCCESS) { - return SUCCESS; - } - - php_request_shutdown((void *)0); - frankenphp_free_request_context(); - - return FAILURE; -} - -int frankenphp_execute_script(char *file_name) { - if (frankenphp_request_startup() == FAILURE) { - - return FAILURE; - } - - int status = SUCCESS; - - zend_file_handle file_handle; - zend_stream_init_filename(&file_handle, file_name); - - file_handle.primary_script = 1; - - zend_first_try { - EG(exit_status) = 0; - php_execute_script(&file_handle); - status = EG(exit_status); - } - zend_catch { status = EG(exit_status); } - zend_end_try(); - - zend_destroy_file_handle(&file_handle); - - /* Reset the sandboxed environment */ - if (sandboxed_env != NULL) { - zend_hash_release(sandboxed_env); - sandboxed_env = NULL; - } - - zend_try { - php_request_shutdown((void *)0); - frankenphp_free_request_context(); - } - zend_catch { - /* - * php shutdown can also potentially bailout with an error - * clear all thread-local memory and re-initialize ZTS - * last error will also cause a crash if not cleared - */ - if (PG(last_error_message)) { - go_log_attrs(thread_index, PG(last_error_message), 8, NULL); - PG(last_error_message) = NULL; - PG(last_error_file) = NULL; - } - frankenphp_free_request_context(); - shutdown_zts(); - initialize_zts(); - } - zend_end_try(); - - return status; -} - /* Use global variables to store CLI arguments to prevent useless allocations */ static char *cli_script; static int cli_argc; diff --git a/frankenphp.h b/frankenphp.h index f25cb8512..7714c71a1 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -169,7 +169,6 @@ int frankenphp_new_main_thread(int num_threads); bool frankenphp_new_php_thread(uintptr_t thread_index); bool frankenphp_shutdown_dummy_request(void); -int frankenphp_execute_script(char *file_name); void frankenphp_update_local_thread_context(bool is_worker); int frankenphp_execute_script_cli(char *script, int argc, char **argv, From f89787eaf22677c4bdb147f0d0195901789b60be Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 22 Mar 2026 11:01:27 +0100 Subject: [PATCH 3/3] clang-format. --- frankenphp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 2a7a2cf19..f2e8f2b29 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1133,8 +1133,7 @@ static void *php_thread(void *arg) { if (!frankenphp_new_php_thread(thread_index)) { /* probably unreachable */ - frankenphp_log_message("Failed to restart an unhealthy thread", - LOG_ERR); + frankenphp_log_message("Failed to restart an unhealthy thread", LOG_ERR); } return NULL;