Skip to content

fix: keep PHP running until Caddy finishes graceful shutdown - #2645

Open
nesl247 wants to merge 2 commits into
php:mainfrom
nesl247:fix/graceful-php-shutdown
Open

nesl247 wants to merge 2 commits into
php:mainfrom
nesl247:fix/graceful-php-shutdown

Conversation

@nesl247

@nesl247 nesl247 commented Sep 11, 2026

Copy link
Copy Markdown

Fixes #2644.

Caddy stops its apps without dependency ordering. Calling frankenphp.Shutdown() from FrankenPHPApp.Stop() can therefore stop PHP while the HTTP app is still accepting requests during shutdown_delay, producing HTTP 500 responses.

Register a single process-global caddy.OnExit callback so PHP stays available until all Caddy apps have stopped, including the HTTP shutdown delay and in-flight request draining. Keep the existing reload shutdown path unchanged.

Add a process-level regression to the existing Linux test workflow, using the CLI it already builds. Five fresh processes exercise the unordered stop path. The test verifies PHP responses during the shutdown delay, an in-flight response spanning listener closure, actual PHP teardown, and clean process exit. A bounded release handshake avoids waiting for a fixed long-running request.

Validated against current main (daead870980d2aba0c2df76cbea880b5a608ff0b) on Linux ARM64 with PHP 8.5.9 and Go 1.27.1:

  • The regression passes with this change and fails on unpatched main after SIGTERM.
  • The existing reload_test.sh integration check passes.
  • Go formatting, Bash syntax, ShellCheck, and actionlint pass.

The full PHP-version matrix and worker-mode shutdown were not run locally. The regression is wired into the existing PHP 8.2–8.5 Linux jobs. Feedback on the process-exit lifecycle hook is welcome.

Caddy stops apps without dependency ordering. Shutting down PHP from the
FrankenPHP app Stop method can break requests while HTTP remains open.
Register one global OnExit callback after all apps have drained instead,
while preserving the existing reload shutdown path.

Add a process-level regression for service during shutdown_delay,
in-flight request draining, PHP teardown, and successful process exit.
Run it in the existing Linux PHP matrix using the already-built CLI.

Validation on current main with Linux ARM64, PHP 8.5.9 and Go 1.27.1:
patched regression and existing reload integration pass; unpatched main
fails the regression. Go formatting, Bash syntax, ShellCheck and actionlint
pass. Full PHP-version matrix and worker-mode shutdown not run locally.
@AlliBalliBaba

Copy link
Copy Markdown
Contributor

Makes sense 👍 . Could you maybe also test that a worker script is still able to do graceful shutdown, eg:

#  worker.php
while(frankenphp_handle_request($handler)){
}

# Make sure this code here still runs in a test, for example by creating a temp file
shutdownlogic();

Exercise a real worker request before SIGTERM and require cleanup code
after the frankenphp_handle_request loop to finish before process exit.
Check the marker is absent before shutdown and contains the expected
value afterward, with a bounded wait and successful server exit.

Validated the full shutdown script against the patched Linux ARM64
runtime with PHP 8.5.9: all five request-draining cycles and worker cleanup
pass. Omitting the fixture cleanup makes the new assertion fail. Bash
syntax, ShellCheck and git diff checks pass.
@nesl247
nesl247 marked this pull request as ready for review September 14, 2026 14:31

@AlliBalliBaba AlliBalliBaba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably also makes senso to move shutdown_test.sh into the tests folder

@nesl247

nesl247 commented Sep 16, 2026

Copy link
Copy Markdown
Author

Probably also makes senso to move shutdown_test.sh into the tests folder

I kept it where the reset test is. I can move them both here, but I wouldn't do one and not the other IMO.

@AlliBalliBaba

Copy link
Copy Markdown
Contributor

Yeah I'd move them both tbh

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.

PHP requests return 500 during shutdown_delay after SIGTERM

2 participants