Skip to content

fix: pcntl signal reaching Go runtime causing long running cli scripts to get stuck#2445

Merged
dunglas merged 3 commits into
mainfrom
fix/long-running-phpcli
Jul 1, 2026
Merged

fix: pcntl signal reaching Go runtime causing long running cli scripts to get stuck#2445
dunglas merged 3 commits into
mainfrom
fix/long-running-phpcli

Conversation

@henderkes

@henderkes henderkes commented May 24, 2026

Copy link
Copy Markdown
Contributor

fixes #1902
fixes #2326
probably fixes #871
closes #614
probably fixes #2479

edit: draft until I can manually verify with a real laravel/symfony reproducer

@henderkes henderkes marked this pull request as draft May 24, 2026 15:16
@henderkes henderkes marked this pull request as ready for review May 25, 2026 13:45
@henderkes

Copy link
Copy Markdown
Contributor Author

Clanker got this one right: docker run --rm -t dubbleclick/frankenphp-1902-repro

https://github.com/laravel/framework/blob/v13.11.2/src/Illuminate/Queue/Worker.php#L853-L880
https://github.com/laravel/horizon/blob/v5.47.0/src/WorkerProcess.php#L67-L141

Workers set up signal receivers and the orchestrator issues them: https://github.com/laravel/horizon/blob/v5.47.0/src/Supervisor.php#L187-L220

@henderkes henderkes changed the title fix: signal handlers reaching Go runtime causing long running cli scripts to get stuck fix: pcntl signal reaching Go runtime causing long running cli scripts to get stuck May 25, 2026
@henderkes henderkes requested a review from withinboredom May 25, 2026 14:40
@henderkes henderkes added the bug Something isn't working label Jun 26, 2026
@dunglas dunglas force-pushed the fix/long-running-phpcli branch from 7341d26 to 86c62b5 Compare July 1, 2026 13:29
@dunglas dunglas requested a review from Copilot July 1, 2026 13:29

Copilot AI 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.

Pull request overview

This PR addresses long-running frankenphp php-cli scripts getting stuck or crashing due to pcntl-managed Unix signals being delivered on Go runtime threads that lack the required PHP TSRM state. It does this by blocking a set of signals early (before Go runtime init) so Go threads inherit the blocked mask, then explicitly unblocking those signals on the dedicated PHP CLI pthread.

Changes:

  • Block common pcntl/process-control signals in a C constructor so Go runtime threads inherit a safe signal mask.
  • Unblock those signals on the execute_script_cli pthread so PHP CLI scripts can still receive and handle them.
  • Add a regression test (Go + PHP script) to validate reliable pcntl_signal delivery in long-running CLI scripts.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
frankenphp.c Blocks a set of Unix signals before Go runtime init; unblocks them in the PHP CLI execution thread to avoid pcntl/TSRM crashes.
testdata/command-pcntl.php Adds a long-running PHP CLI signal exercise script to validate signal delivery behavior.
cli_test.go Adds a regression test invoking the new PHP script via the internal CLI test harness.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frankenphp.c
Comment on lines +216 to +220
frankenphp_fill_cli_signal_set(&set);
/* Single-threaded at this point (constructors run before Go's runtime),
* so sigprocmask is sufficient and portable. */
sigprocmask(SIG_BLOCK, &set, NULL);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Blocking the CLI-related signals process-wide in the constructor (via sigprocmask) also blocks termination signals like SIGTERM/SIGINT for all Go threads by default.

Yes, that's the point.

Please consider adding an explicit mitigation for library embedders (e.g. documented requirement, or a Go-side bootstrap that ensures SIGTERM/SIGINT remain deliverable even when no signal.Notify is used).

I'm not sure how realistic it is to actually hit this issue. The paths that Sloppilot mentioned should get stuck don't actually get stuck.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looks like Go unblocks sigkillable signals anyway, so this was effectively a no-op for the signals sloppilot flagged: https://github.com/golang/go/blob/1952e618b834bda60fb9efff0fc0af46b38e110d/src/runtime/signal_unix.go#L1356

@dunglas dunglas merged commit 3bc35af into main Jul 1, 2026
148 of 150 checks passed
@dunglas dunglas deleted the fix/long-running-phpcli branch July 1, 2026 15:41
@dunglas

dunglas commented Jul 1, 2026

Copy link
Copy Markdown
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

3 participants