[Process] Close STDIN early to avoid EPIPE when child closed it (6.4) #62016
+39
−7
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Type: bug fix
Component: symfony/process
Related: #61792
Target branch: 6.4
Context / Problem
When a child process exits before consuming all input sent via stdin, Process may still attempt to write the remaining buffer. On POSIX this triggers EPIPE, and with ErrorHandler promoting notices to exceptions, the error surfaces in run(), stop(), and even __destruct(). This prevents clean teardown and causes intermittent failures with large inputs.
What was done
In Process::updateStatus(), after readPipes(...), when running === false and processPipes is Pipes\AbstractPipes, closeStdin() is called to stop any further writes to a closed pipe.
In AbstractPipes::write(), fwrite() is silenced with @ and failures are handled by closing STDIN idempotently and clearing input buffers. When a partial chunk remains (suffix in $data), it is moved into inputBuffer and the method returns the writable pipe so the remainder will be retried on the next iteration.
Safety
STDIN is closed only when the child is no longer running or when the OS already reports a write failure. Writing in that condition is useless and the root cause of EPIPE.
Reading from stdout/stderr, exit codes, timeouts, TTY, and other behaviors remain unchanged. The race between checking “running” and writing is covered because write() also closes STDIN on any write error and preserves partial data for the next loop.
Repro / Test
Before fix (typical)
After Fix (Expected)
BC
No public contracts changed for consumer code. A public utility closeStdin() is added on an internal class (AbstractPipes); this does not introduce BC breaks for users of the component.
Technical Note
If someone directly extends AbstractPipes and already defines a closeStdin() method with an incompatible signature, it could conflict. This is very unlikely, as AbstractPipes is an internal building block and most projects interact only with Process.