diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-20 19:28:45 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-20 19:29:04 +0900 |
| commit | 77ff62b1e5333cd05b6c405feb2f42d0d8dcfab8 (patch) | |
| tree | 5bc6d0372c23a7e0c655699a6dfe1d0954083178 | |
| parent | dc0087845355222d1cbf2820289444db3eebc87e (diff) | |
| download | php-shirabe-77ff62b1e5333cd05b6c405feb2f42d0d8dcfab8.tar.gz php-shirabe-77ff62b1e5333cd05b6c405feb2f42d0d8dcfab8.tar.zst php-shirabe-77ff62b1e5333cd05b6c405feb2f42d0d8dcfab8.zip | |
feat(php-rpc): attach worker process state to request I/O errors
A dead worker and a live one hitting a framing bug both surface as a
raw socket I/O error (e.g. "Broken pipe"), which doesn't say whether
the child crashed, was signaled, or is still running. Query the
child's exit status via try_wait() and attach it as anyhow::Context
so the panic message shows the root cause directly.
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 45 | ||||
| -rwxr-xr-x | scripts/bench/create-project.sh | 4 |
2 files changed, 41 insertions, 8 deletions
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index 9ba02629..d939829a 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -1,5 +1,6 @@ //! Rust-to-PHP RPC over a Unix domain socket. See `docs/dev/php-rpc.md`. +use anyhow::Context as _; use shirabe_external_packages::symfony::process::PhpExecutableFinder; use shirabe_php_shim::PhpMixed; use std::io::{Read as _, Write as _}; @@ -91,9 +92,11 @@ const GLUE_SCRIPT: &str = include_str!("../php/worker.php"); struct Worker { stream: UnixStream, - // Kept alive for the process lifetime: the child interpreter and the temp dir holding the socket - // and glue script. Neither is dropped because the worker lives in a never-dropped static. - _child: std::process::Child, + // Also queried for its exit status when a socket read/write fails, to tell a dead worker + // apart from a framing bug. Kept alive for the process lifetime along with the temp dir + // holding the socket and glue script; neither is dropped because the worker lives in a + // never-dropped static. + child: std::process::Child, _tempdir: tempfile::TempDir, } @@ -102,8 +105,22 @@ impl Worker { let mut payload = name.as_bytes().to_vec(); payload.push(0); payload.extend_from_slice(arg.as_bytes()); - write_frame(&mut self.stream, &payload)?; - Ok(read_frame(&mut self.stream)?) + write_frame(&mut self.stream, &payload).with_context(|| self.worker_state())?; + read_frame(&mut self.stream).with_context(|| self.worker_state()) + } + + /// Describes the PHP worker's current process state, to be attached as `anyhow::Context` to + /// an I/O error so a dead worker (crash, OOM kill, ...) can be told apart from a live one + /// hitting a framing bug. + fn worker_state(&mut self) -> String { + match self.child.try_wait() { + Ok(Some(status)) => format!("PHP worker process already exited: {status}"), + Ok(None) => format!( + "PHP worker process (pid {}) is still running", + self.child.id() + ), + Err(wait_err) => format!("failed to check PHP worker process status: {wait_err}"), + } } } @@ -165,7 +182,7 @@ fn spawn_worker() -> anyhow::Result<Worker> { Ok(Worker { stream, - _child: child, + child, _tempdir: tempdir, }) } @@ -260,6 +277,22 @@ mod tests { } #[test] + fn request_error_reports_dead_worker_exit_status() { + let mut worker = spawn_worker().expect("failed to spawn PHP worker"); + worker.child.kill().expect("failed to kill PHP worker"); + worker.child.wait().expect("failed to reap PHP worker"); + + let err = worker + .request("defined", "PHP_VERSION") + .expect_err("request against a dead worker should fail"); + let message = format!("{err:#}"); + assert!( + message.contains("PHP worker process already exited"), + "unexpected error message: {message}" + ); + } + + #[test] fn rejects_non_numeric_length() { assert_eq!(parse_serialized_string(b"s:x:\"ab\";"), None); } diff --git a/scripts/bench/create-project.sh b/scripts/bench/create-project.sh index 5277cd1f..74d183bf 100755 --- a/scripts/bench/create-project.sh +++ b/scripts/bench/create-project.sh @@ -32,8 +32,8 @@ hyperfine \ --prepare "rm -rf '$TARGET_DIR-shirabe' '$TARGET_DIR-composer'" \ --export-json "$OUTDIR/results-$PACKAGE_SLUG.json" \ --export-markdown "$OUTDIR/results-$PACKAGE_SLUG.md" \ - --ignore-failure \ - --command-name Shirabe "'$BIN' create-project --profile --no-plugins --no-scripts --no-audit '$PACKAGE' '$TARGET_DIR-shirabe'" \ + --show-output \ + --command-name Shirabe "RUST_BACKTRACE=1 '$BIN' create-project --profile --no-plugins --no-scripts --no-audit '$PACKAGE' '$TARGET_DIR-shirabe'" \ --command-name Composer "composer create-project --profile --no-plugins --no-scripts --no-audit '$PACKAGE' '$TARGET_DIR-composer'" echo ">> results: $OUTDIR/results-$PACKAGE_SLUG.json" >&2 |
