Repository navigation
Conversation
47566d0 to
ffd4ab3
Compare
ffd4ab3 to
5c32f9f
Compare
|
We tested this patch in production for last 2 weeks without issues. Now it is ready for review. |
|
|
||
| $error = \posix_get_last_error(); | ||
| if ($error === self::ESRCH) { | ||
| return; |
There was a problem hiding this comment.
The OS process may exit before the event loop updates its status. In that case, isRunning() still returns true, but posix_kill() fails with ESRCH because the PID no longer exists.
signal() and kill() already return silently for processes marked as Ended. Ignoring ESRCH keeps this behavior consistent. Other errors throw ProcessException, and join() still reads the exit code normally.
testSignalIgnoresProcessThatAlreadyExited() covers this race.
| 'require %s;' | ||
| . '$process = Amp\\Process\\Process::start("cat >/dev/null");' | ||
| . '$handle = (new ReflectionProperty($process, "handle"))->getValue($process);' | ||
| . '$handle->__destruct();', |
There was a problem hiding this comment.
Which situation does this really emulate?
There was a problem hiding this comment.
The test protects against a hang introduced by an earlier version of this fix:
- We added a blocking waitpid() in the handle destructor so shell cleanup would not depend on the event loop running again.
- During cyclic garbage collection, the handle destructor can run before ProcHolder terminates the command. Blocking there prevents execution from reaching the cleanup that calls kill(). The test forces this ordering by invoking the handle destructor directly.
- The current implementation blocks in the destructor only when the status is Ended. For running commands, it returns without blocking, allowing ProcHolder cleanup to proceed. kill() then synchronously reaps the shell, without requiring another event-loop iteration.
There was a problem hiding this comment.
I updated pull request desctiption with "Behavior changes" section for clarity.
Summary
Ensures that the POSIX wrapper shell created by
proc_open()is reaped after process termination, both with and withoutext-pcntl.Shutdown remains non-blocking while the requested command is still running.
Root cause
proc_get_status($proc)['pid'], stored as$shellPid, identifies the wrapper shell created byproc_open(). This shell is a direct child of PHP.The requested command PID, stored as
$pid, belongs to a process started by the wrapper shell. It is not a direct child of PHP, so waiting for$pidcannot reap the wrapper shell.Shell reaping was deferred through the event loop. If the event loop did not run again after the shell exited, the shell remained a zombie.
When
ext-pcntlwas unavailable, the cleanup path returned without performing any wait operation. By default pcntl is absent in php-fpm.A blocking reap after
kill()also requires checking signal delivery first.ESRCHmeans that the requested process has already exited and the wrapper shell can still be reaped. Other delivery errors must be reported instead of proceeding to a potentially blocking wait.Changes
posix_kill()failures, treatingESRCHas an already-exited process and throwing for other errors.$shellPidafterkill()and after completed process destruction.pcntl_waitpid()withEINTRhandling when PCNTL is available.proc_get_status()to reap the wrapper shell when PCNTL is unavailable.This corrects the POSIX shutdown handling introduced in 6c711ee.
Behavior changes
kill()called while status is notEndedEndedproc_get_status()Our zombie issue resulted from two combined conditions: PHP-FPM lacked PCNTL, so explicit shell reaping was skipped, and
kill()sent SIGKILL without reaping the wrapper shell.Testing