🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

fix: don't treat a finished request's write as an aborted connection - #2569

Merged
dunglas merged 3 commits into
mainfrom
fix-finish-request-aborted-connection
Aug 4, 2026
Merged

fix: don't treat a finished request's write as an aborted connection#2569
dunglas merged 3 commits into
mainfrom
fix-finish-request-aborted-connection

Conversation

@dunglas

@dunglas dunglas commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

go_ub_write() reported any PHP output write that happens after fastcgi_finish_request()/frankenphp_finish_request() as an aborted connection (0 bytes written, closed=true), regardless of whether the client was actually still connected. frankenphp_ub_write() then calls php_handle_aborted_connection() for that write, which:

  • marks connection_status() as aborted even though the request finished normally, and
  • when ignore_user_abort is off (PHP's default outside worker mode), calls zend_bailout(), silently terminating the rest of the script right at that output statement.

Worker mode forces ignore_user_abort=1 on every request, which is why this doesn't visibly crash a worker script — it only corrupts connection_status() there. Outside worker mode, any code that echoes/outputs anything after fastcgi_finish_request() gets silently truncated.

Fix

Discard the write (the responseWriter may no longer be safe to touch, see #2535/#2538), but report the actual client-disconnect status via fc.clientHasClosed() instead of hardcoding true.

Reproducer

<?php
echo "Before";
fastcgi_finish_request();
echo "After";

In classic (non-worker) mode with default ignore_user_abort, echo "After" triggers the bailout and any code after it never runs.

Tests

  • Extended TestFinishRequest (testdata/finish-request.php) to log a line after the discarded write and assert it's reached — hangs on the unpatched code (module mode), passes with the fix.
  • TestConnectionAbort still passes, confirming a genuine client disconnect is still correctly reported after fastcgi_finish_request().

Fixes #2548.

go_ub_write() reported any write after fastcgi_finish_request() as an
aborted connection (0 bytes, closed=true). frankenphp_ub_write() then
calls php_handle_aborted_connection(), which marks connection_status()
as aborted and, when ignore_user_abort is off (the default outside
worker mode), bails out the rest of the script at that statement.

Worker mode forces ignore_user_abort=1, which is why this only silently
truncates connection_status() there instead of visibly bailing out, but
the underlying request never actually aborted in either case.

Fixes #2548.
@dunglas
dunglas marked this pull request as ready for review July 28, 2026 07:55
Comment thread frankenphp.go Outdated
dunglas added 2 commits July 29, 2026 00:16
…ish_request

@AlliBalliBaba caught this in review: request.Context() is canceled the
instant the handler returns (standard net/http behavior, independent of
whether the client is still connected), and frankenphp_finish_request()
is exactly what lets ServeHTTP return. Checking fc.clientHasClosed()
again after isDone was therefore reading true for virtually any write
following a normal finish_request, not just a real disconnect -
functionally the same bug this was meant to fix.

Verified with a real net/http.Server (httptest.NewRecorder(), used by
the rest of this suite, never exercises Go's cancel-on-return behavior
at all, which is how the original version passed its own tests).

Fix: capture clientHasClosed() once in closeContext(), before
close(fc.done) - which is what triggers the eventual handler return -
and use that cached value in go_ub_write instead of re-checking a
context that's since been cancelled for an unrelated reason.
@dunglas
dunglas merged commit a3c540c into main Aug 4, 2026
32 checks passed
@dunglas
dunglas deleted the fix-finish-request-aborted-connection branch August 4, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression in v1.12.5: fastcgi_finish_request() terminates FrankenPHP process

2 participants