Skip to content

fix: don't segfault when the PHP CLI is executed while FrankenPHP is running - #2593

Open
ousamabenyounes wants to merge 2 commits into
php:mainfrom
ousamabenyounes:fix/issue-2342
Open

fix: don't segfault when the PHP CLI is executed while FrankenPHP is running#2593
ousamabenyounes wants to merge 2 commits into
php:mainfrom
ousamabenyounes:fix/issue-2342

Conversation

@ousamabenyounes

Copy link
Copy Markdown
Contributor

What

frankenphp.ExecuteScriptCLI() (and ExecutePHPCode()) crash the whole process
with a segmentation fault when called after frankenphp.Init() in the same
process (issue #2342):

FrankenPHP started 🐘  php_version=8.5.10-dev ...
signal: segmentation fault

Why

Both functions start the embedded PHP CLI SAPI through php_embed_init(). That
runs sapi_startup() + php_module_startup() for the embed SAPI. When FrankenPHP
is already running, its own SAPI and the PHP engine are already started, so
booting a second SAPI on top corrupts global engine state and the process
crashes.

What this PR does

The minimal, safe fix: prevent the crash. When FrankenPHP is already running,
ExecuteScriptCLI/ExecutePHPCode now log an error and return a non-zero exit
status instead of booting the embedded CLI SAPI and segfaulting. The standalone
CLI path (no prior Init) is untouched.

This intentionally does not add a new way to run CLI scripts inside a running
server (the broader idea discussed on the issue, e.g. running on a dedicated
thread/child process); it only turns an undefined-behaviour crash into a defined,
recoverable error. That larger feature can be built on top later.

Thread-safety note: the guard reads isRunning (and globalLogger) without an
extra lock, consistent with how Init/Shutdown already manage isRunning and
how these globals are read elsewhere in the package. CLI-after-Init is a
sequential misuse, not concurrent access.

Test verification (RED → GREEN)

Run in the dev image (dev.Dockerfile), PHP 8.5 ZTS, -tags nowatcher.

RED — unmodified main (only the repro/test applied):

--- FAIL: TestExecuteScriptCLIWhileRunning (0.18s)
    cli_test.go:89: process was killed by a signal instead of exiting cleanly:
        signal: segmentation fault (core dumped)
    cli_test.go:91: Not equal: expected: 1  actual: -1
FAIL	github.com/dunglas/frankenphp

(A standalone Init() + ExecuteScriptCLI() program likewise crashes with
signal: segmentation fault, exit 139.)

GREEN — with the fix (go test -race):

=== RUN   TestRefuseCLIWhileRunning
--- PASS: TestRefuseCLIWhileRunning (0.03s)
=== RUN   TestExecuteScriptCLI
--- PASS: TestExecuteScriptCLI (0.08s)
=== RUN   TestExecuteCLICode
--- PASS: TestExecuteCLICode (0.01s)
=== RUN   TestExecuteScriptCLIWhileRunning
--- PASS: TestExecuteScriptCLIWhileRunning (0.03s)
PASS
ok  	github.com/dunglas/frankenphp	1.225s

Existing standalone CLI tests still pass (no regression). refuseCLIWhileRunning
shows 100% coverage; go build, go vet, gofmt, the Caddy module build and
go mod tidy -diff are all clean.

ousamabenyounes and others added 2 commits August 5, 2026 23:51
…running

ExecuteScriptCLI and ExecutePHPCode start the embedded PHP CLI SAPI via
php_embed_init(). When FrankenPHP is already running in the same process
(Init has been called), booting the embedded SAPI on top of the running
FrankenPHP SAPI corrupts the PHP engine and crashes the whole process with
a segmentation fault.

Refuse the call cleanly instead: when FrankenPHP is running, log an error and
return a non-zero exit status rather than crashing. The standalone CLI path
(no prior Init) is unchanged.

Fixes php#2342

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
staticcheck QF1008: exec.ExitError embeds *os.ProcessState, so Exited()
is promoted (as ExitCode() is already used on the next line).
@henderkes

Copy link
Copy Markdown
Contributor

But this can never happen? There's no code wiring to create this scenario.

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.

2 participants