symfony: Process AbstractPipes.php infinite fwrite() loop on error
| Q | A |
|---|---|
| Bug report? | yes |
| Feature request? | no |
| BC Break report? | no |
| RFC? | no |
| Symfony version | 3.2.3 (>= 2.7) |
In the write() method of src/Symfony/Component/Process/Pipes/AbstractPipes.php you are currently not checking the result of fwrite and are just assuming that it is an integer greater 0.
if (isset($this->inputBuffer[0])) {
$written = fwrite($stdin, $this->inputBuffer);
$this->inputBuffer = substr($this->inputBuffer, $written);
if (isset($this->inputBuffer[0])) {
return array($this->pipes[0]);
}
}
fwrite() PHP documentation:
fwrite() returns the number of bytes written, or FALSE on error.
Also in a comment on http://php.net/manual/en/function.fwrite.php there is the info that this function might return 0 or any value less than the length of the string to be written if something went wrong.
So in this code above you are just moving the offset of the input buffer even if the it hasn’t moved at all (0 or false casted to 0). So we should check for this cases ($written is false or written is smaller than integer 1) and e.g. throw an exception (since a time out exception is thrown otherwise after a period of time):
use Symfony\Component\Process\Exception\RuntimeException;
...
if (isset($this->inputBuffer[0])) {
$written = fwrite($stdin, $this->inputBuffer);
if (false === $written || 1 > $written) {
throw new RuntimeException('Writing to stdin pipe failed');
}
$this->inputBuffer = substr($this->inputBuffer, $written);
if (isset($this->inputBuffer[0])) {
return array($this->pipes[0]);
}
}
The problem in my case would be, that an exception is thrown even when every works fine with the command. I’m calling a ApacheTika JAR with file type detection. So after just a few loops of the write() function, Tika recognizes the file type by its magic numbers. And it stops then reading further on stdin(), processes the input and after a short moment writes the detected mime type to stdout.
In my case I’d need to check the result of the pipes with the stdout or the errors. So I get the results in the second (stdout) pipe and everything works like charm. Or there actually was an error then I’d have an error in the third (error) pipe.
This would require some work in the calling readAndWrite() method of UnixPipes.php and WindowsPipes.php. Instead of
$w = $this->write();
we need
use Symfony\Component\Process\Exception\RuntimeException;
...
try
$w = $this->write();
} catch (RuntimeException $exception) {
// Something went wrong while writing pipe to command's stdin
// Unset it so we can go on reading command's stdout and error pipes
unset($this->pipes[0]);
}
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 2
- Comments: 26 (14 by maintainers)
Also catch this kind of error. The problem occurs on the second and further write in STDIN. fwrite returns 0 when the input buffer length is more than 65536 in my case.
It happens in UnixPipes actually. I’m using MacOS
It is still relevant.
I have written my own Process class with similar features as Symfony/Process with this bug being resolved. It was hard for me to find a way to send a fix here, because the code is more complicated than just my needs and it has to be backward compatible and support Windows and older PHP versions too. Please take a look at my docs and code if you are still interested.
Also experienced the same problem like @wtorsi .
Repro:
If the file being passed is <=65536 it’s okay, if it’s anything larger it freezes.
OS: Linux