Skip to content

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

Open
dunglas wants to merge 1 commit into
mainfrom
fix-finish-request-aborted-connection
Open

fix: don't treat a finished request's write as an aborted connection#2569
dunglas wants to merge 1 commit 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
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

1 participant