From 1cc9346050a1e6a19d97ba7a88c722cc38e1df8e Mon Sep 17 00:00:00 2001 From: henderkes Date: Mon, 4 May 2026 12:54:56 +0700 Subject: [PATCH 1/6] extra pthread handling unlikely to be needed, but better safe than sorry --- frankenphp.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/frankenphp.c b/frankenphp.c index 8037886f67..b482f4590e 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -89,7 +89,12 @@ __thread HashTable *sandboxed_env = NULL; #ifndef PHP_WIN32 static bool is_forked_child = false; -static void frankenphp_fork_child(void) { is_forked_child = true; } +static void frankenphp_fork_child(void) { + is_forked_child = true; +#ifdef __linux__ + prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); +#endif +} #endif void frankenphp_update_local_thread_context(bool is_worker) { @@ -762,6 +767,12 @@ static int frankenphp_startup(sapi_module_struct *sapi_module) { static int frankenphp_deactivate(void) { return SUCCESS; } static size_t frankenphp_ub_write(const char *str, size_t str_length) { +#ifndef PHP_WIN32 + if (UNEXPECTED(is_forked_child)) { + return 0; + } +#endif + struct go_ub_write_return result = go_ub_write(thread_index, (char *)str, str_length); @@ -773,6 +784,12 @@ static size_t frankenphp_ub_write(const char *str, size_t str_length) { } static int frankenphp_send_headers(sapi_headers_struct *sapi_headers) { +#ifndef PHP_WIN32 + if (UNEXPECTED(is_forked_child)) { + return SAPI_HEADER_SEND_FAILED; + } +#endif + if (SG(request_info).no_headers == 1) { return SAPI_HEADER_SENT_SUCCESSFULLY; } @@ -798,6 +815,12 @@ static int frankenphp_send_headers(sapi_headers_struct *sapi_headers) { } static void frankenphp_sapi_flush(void *server_context) { +#ifndef PHP_WIN32 + if (UNEXPECTED(is_forked_child)) { + return; + } +#endif + sapi_send_headers(); if (go_sapi_flush(thread_index)) { php_handle_aborted_connection(); From 1f2963ec1945fc1ae8569d73d367a1a7d59a7c6b Mon Sep 17 00:00:00 2001 From: henderkes Date: Mon, 4 May 2026 13:06:46 +0700 Subject: [PATCH 2/6] fix ci --- .github/workflows/tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 396d9ccf77..144285ae18 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -56,6 +56,8 @@ jobs: env: phpts: ts debug: true + - name: Install system dependencies + run: sudo apt-get update && sudo apt-get install --reinstall -y libbrotli-dev - name: Install e-dant/watcher uses: ./.github/actions/watcher - name: Set CGO flags From c66995164f938eab9e9a949cb2c7efee869ea3e3 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 12 May 2026 20:37:22 +0700 Subject: [PATCH 3/6] Update .github/workflows/tests.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kévin Dunglas Signed-off-by: Marc --- .github/workflows/tests.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 09325d361c..b4299db99c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -56,8 +56,6 @@ jobs: env: phpts: ts debug: true - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install --reinstall -y libbrotli-dev - name: Install e-dant/watcher uses: ./.github/actions/watcher - name: Reinstall libbrotli-dev From 3875ca212711436940036f7a1590f46f71d11f29 Mon Sep 17 00:00:00 2001 From: henderkes Date: Wed, 13 May 2026 11:55:06 +0700 Subject: [PATCH 4/6] suggestions from @dunglas --- frankenphp.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 0fefddef88..deb9d575b8 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -26,14 +26,24 @@ #include #include #include -#ifndef ZEND_WIN32 -#include -#endif #if defined(__linux__) +#include #include -#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#elif defined(__FreeBSD__) +#include +#include +#include +#elif defined(__OpenBSD__) +#include #include #endif +#if defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) +#define FRANKENPHP_KQUEUE_PARENT_DEATH 1 +#include +#include +#include +#endif #include "_cgo_export.h" #include "frankenphp_arginfo.h" @@ -95,10 +105,61 @@ __thread HashTable *sandboxed_env = NULL; #ifndef PHP_WIN32 static bool is_forked_child = false; +static pid_t fork_parent_pid = 0; + +static void frankenphp_fork_prepare(void) { fork_parent_pid = getpid(); } + +#if defined(FRANKENPHP_KQUEUE_PARENT_DEATH) +/* Watcher thread for platforms without a kernel parent-death signal. + * Blocks in kevent() until the parent exit, then force-kills this child. */ +static void *frankenphp_parent_death_watcher(void *arg) { + pid_t ppid = (pid_t)(intptr_t)arg; + int kq = kqueue(); + if (kq < 0) { + _exit(1); + } + struct kevent kev; + EV_SET(&kev, ppid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, NULL); + if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { + _exit(1); + } + struct kevent triggered; + if (kevent(kq, NULL, 0, &triggered, 1, NULL) > 0) { + _exit(1); + } + close(kq); + return NULL; +} +#endif + static void frankenphp_fork_child(void) { is_forked_child = true; -#ifdef __linux__ - prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); +#if defined(__linux__) + // if the parent process dies between fork() and this prctl() + if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0 || + getppid() != fork_parent_pid) { + _exit(1); + } +#elif defined(__FreeBSD__) + /* FreeBSD analogue of PR_SET_PDEATHSIG - except with + * parent process death, rather than parent thread death. */ + int sig = SIGKILL; + if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sig) != 0 || + getppid() != fork_parent_pid) { + _exit(1); + } +#elif defined(FRANKENPHP_KQUEUE_PARENT_DEATH) + /* No kernel parent-death signal available; spawn a watcher that + * blocks on EVFILT_PROC/NOTE_EXIT. */ + if (getppid() != fork_parent_pid) { + _exit(1); + } + pthread_t watcher; + if (pthread_create(&watcher, NULL, frankenphp_parent_death_watcher, + (void *)(intptr_t)fork_parent_pid) != 0) { + _exit(1); + } + pthread_detach(watcher); #endif } #endif @@ -867,7 +928,7 @@ static const zend_function_entry frankenphp_test_hook_functions[] = { PHP_MINIT_FUNCTION(frankenphp) { register_frankenphp_symbols(module_number); #ifndef PHP_WIN32 - pthread_atfork(NULL, NULL, frankenphp_fork_child); + pthread_atfork(frankenphp_fork_prepare, NULL, frankenphp_fork_child); #endif #ifdef FRANKENPHP_TEST From 57cdd5cd3cdc4c9f15ea76768b24bd6b74f35f39 Mon Sep 17 00:00:00 2001 From: henderkes Date: Wed, 13 May 2026 12:04:27 +0700 Subject: [PATCH 5/6] fix borked merge --- frankenphp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index f0adb80856..5b4e7c744e 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -162,6 +162,10 @@ static void frankenphp_fork_child(void) { pthread_detach(watcher); #endif } + +static void frankenphp_register_atfork(void) { + pthread_atfork(frankenphp_fork_prepare, NULL, frankenphp_fork_child); +} #endif /* Best-effort force-kill for stuck PHP threads. @@ -927,8 +931,10 @@ static const zend_function_entry frankenphp_test_hook_functions[] = { PHP_MINIT_FUNCTION(frankenphp) { register_frankenphp_symbols(module_number); -#ifndef - pthread_atfork(frankenphp_fork_prepare, NULL, frankenphp_fork_child); +#ifndef PHP_WIN32 + /* MINIT runs once per ZTS thread — guard the atfork registration */ + static pthread_once_t atfork_once = PTHREAD_ONCE_INIT; + pthread_once(&atfork_once, frankenphp_register_atfork); #endif #ifdef FRANKENPHP_TEST From 91546cd102e3b0ae04ac76746e77515ff8a1fc46 Mon Sep 17 00:00:00 2001 From: henderkes Date: Wed, 13 May 2026 12:24:12 +0700 Subject: [PATCH 6/6] make clang format happy --- frankenphp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frankenphp.c b/frankenphp.c index 5b4e7c744e..6ccc3389a0 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -27,22 +27,22 @@ #include #include #if defined(__linux__) -#include #include -#elif defined(__FreeBSD__) #include +#elif defined(__FreeBSD__) #include #include -#elif defined(__OpenBSD__) #include +#elif defined(__OpenBSD__) #include +#include #endif #if defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ defined(__DragonFly__) #define FRANKENPHP_KQUEUE_PARENT_DEATH 1 -#include #include #include +#include #endif #include "_cgo_export.h"