From 6ca2e62be5ed7c3b1873d6ccb525d2177d8ad3f5 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 15 Feb 2026 12:18:05 +0100 Subject: [PATCH 1/5] Always ignores user abort. --- docs/worker.md | 1 + frankenphp.c | 6 ++++++ worker_test.go | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/docs/worker.md b/docs/worker.md index f344becacd..fd24ee3575 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -73,6 +73,7 @@ The following example shows how to create your own worker script without relying // public/index.php // Prevent worker script termination when a client connection is interrupted +// This line may be omitted in newer FrankenPHP versions ignore_user_abort(true); // Boot your app diff --git a/frankenphp.c b/frankenphp.c index 20b36130b6..67ffdf28bd 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -71,6 +71,8 @@ frankenphp_config frankenphp_get_config() { } bool should_filter_var = 0; +bool original_user_abort_setting = 0; + __thread uintptr_t thread_index; __thread bool is_worker_thread = false; __thread zval *os_environment = NULL; @@ -113,6 +115,9 @@ __thread session_user_handlers *worker_session_handlers_snapshot = NULL; void frankenphp_update_local_thread_context(bool is_worker) { is_worker_thread = is_worker; + + /* workers should keep running if the user aborts the connection */ + PG(ignore_user_abort) = is_worker ? 1 : original_user_abort_setting; } static void frankenphp_update_request_context() { @@ -1241,6 +1246,7 @@ static void *php_main(void *arg) { char *default_filter; cfg_get_string("filter.default", &default_filter); should_filter_var = default_filter != NULL; + original_user_abort_setting = PG(ignore_user_abort); go_frankenphp_main_thread_is_ready(); diff --git a/worker_test.go b/worker_test.go index 37ba192068..36807c2fec 100644 --- a/worker_test.go +++ b/worker_test.go @@ -1,6 +1,7 @@ package frankenphp_test import ( + "context" "fmt" "io" "log" @@ -157,3 +158,19 @@ func TestWorkerHasOSEnvironmentVariableInSERVER(t *testing.T) { assert.Contains(t, string(body), "custom_env_variable_value") }, &testOptions{workerScript: "worker.php", nbWorkers: 1, nbParallelRequests: 1}) } + +func TestKeepRunningOnConnectionAbort(t *testing.T) { + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { + req := httptest.NewRequest("GET", "http://example.com/worker-with-counter.php", nil) + + ctx, cancel := context.WithCancel(req.Context()) + req = req.WithContext(ctx) + cancel() + body1, _ := testRequest(req, handler, t) + + assert.Equal(t, body1, "requests:1", "should have handled exactly one request") + body2, _ := testGet("http://example.com/worker-with-counter.php", handler, t) + + assert.Equal(t, body2, "requests:2", "should not have stopped execution after the first request was aborted") + }, &testOptions{workerScript: "worker-with-counter.php", nbWorkers: 1, nbParallelRequests: 1}) +} From 45cbd50f189ac442a8815e01c7dfeb6d032ed972 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 15 Feb 2026 20:44:54 +0100 Subject: [PATCH 2/5] Removes ignore_user_abort mentions. --- docs/worker.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/worker.md b/docs/worker.md index fd24ee3575..f952c305c5 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -72,10 +72,6 @@ The following example shows how to create your own worker script without relying Date: Sun, 15 Feb 2026 12:18:05 +0100 Subject: [PATCH 3/5] Always ignores user abort. --- docs/worker.md | 1 + frankenphp.c | 6 ++++++ worker_test.go | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/docs/worker.md b/docs/worker.md index f344becacd..fd24ee3575 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -73,6 +73,7 @@ The following example shows how to create your own worker script without relying // public/index.php // Prevent worker script termination when a client connection is interrupted +// This line may be omitted in newer FrankenPHP versions ignore_user_abort(true); // Boot your app diff --git a/frankenphp.c b/frankenphp.c index 20b36130b6..67ffdf28bd 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -71,6 +71,8 @@ frankenphp_config frankenphp_get_config() { } bool should_filter_var = 0; +bool original_user_abort_setting = 0; + __thread uintptr_t thread_index; __thread bool is_worker_thread = false; __thread zval *os_environment = NULL; @@ -113,6 +115,9 @@ __thread session_user_handlers *worker_session_handlers_snapshot = NULL; void frankenphp_update_local_thread_context(bool is_worker) { is_worker_thread = is_worker; + + /* workers should keep running if the user aborts the connection */ + PG(ignore_user_abort) = is_worker ? 1 : original_user_abort_setting; } static void frankenphp_update_request_context() { @@ -1241,6 +1246,7 @@ static void *php_main(void *arg) { char *default_filter; cfg_get_string("filter.default", &default_filter); should_filter_var = default_filter != NULL; + original_user_abort_setting = PG(ignore_user_abort); go_frankenphp_main_thread_is_ready(); diff --git a/worker_test.go b/worker_test.go index 37ba192068..36807c2fec 100644 --- a/worker_test.go +++ b/worker_test.go @@ -1,6 +1,7 @@ package frankenphp_test import ( + "context" "fmt" "io" "log" @@ -157,3 +158,19 @@ func TestWorkerHasOSEnvironmentVariableInSERVER(t *testing.T) { assert.Contains(t, string(body), "custom_env_variable_value") }, &testOptions{workerScript: "worker.php", nbWorkers: 1, nbParallelRequests: 1}) } + +func TestKeepRunningOnConnectionAbort(t *testing.T) { + runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { + req := httptest.NewRequest("GET", "http://example.com/worker-with-counter.php", nil) + + ctx, cancel := context.WithCancel(req.Context()) + req = req.WithContext(ctx) + cancel() + body1, _ := testRequest(req, handler, t) + + assert.Equal(t, body1, "requests:1", "should have handled exactly one request") + body2, _ := testGet("http://example.com/worker-with-counter.php", handler, t) + + assert.Equal(t, body2, "requests:2", "should not have stopped execution after the first request was aborted") + }, &testOptions{workerScript: "worker-with-counter.php", nbWorkers: 1, nbParallelRequests: 1}) +} From 3a17d417713e0ba82c5eef265062e09170cbf715 Mon Sep 17 00:00:00 2001 From: Alliballibaba Date: Sun, 15 Feb 2026 20:44:54 +0100 Subject: [PATCH 4/5] Removes ignore_user_abort mentions. --- docs/worker.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/worker.md b/docs/worker.md index fd24ee3575..f952c305c5 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -72,10 +72,6 @@ The following example shows how to create your own worker script without relying Date: Mon, 16 Feb 2026 22:37:31 +0100 Subject: [PATCH 5/5] Fixes order. --- worker_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker_test.go b/worker_test.go index 36807c2fec..75b82426f0 100644 --- a/worker_test.go +++ b/worker_test.go @@ -168,9 +168,9 @@ func TestKeepRunningOnConnectionAbort(t *testing.T) { cancel() body1, _ := testRequest(req, handler, t) - assert.Equal(t, body1, "requests:1", "should have handled exactly one request") + assert.Equal(t, "requests:1", body1, "should have handled exactly one request") body2, _ := testGet("http://example.com/worker-with-counter.php", handler, t) - assert.Equal(t, body2, "requests:2", "should not have stopped execution after the first request was aborted") + assert.Equal(t, "requests:2", body2, "should not have stopped execution after the first request was aborted") }, &testOptions{workerScript: "worker-with-counter.php", nbWorkers: 1, nbParallelRequests: 1}) }