feof may hang
brstgt opened this issue · comments
I encountered hanging feof
resulting in StreamException("The stream was closed by the peer")
.
This happens when doing requests in Kubernetes to Facebook.
Omitting the feof check "fixes" it.
A workaround (and better code) would be to not check on @feof
before the write but setting an error handler for the fwrite
and cathing warnings and converting them to stream exceptions.
Example:
$eh = set_error_handler([$this, 'onSocketWriteError']);
if ($chunkSize) {
$written = \fwrite($stream, $data, $chunkSize);
} else {
$written = \fwrite($stream, $data);
}
set_error_handler($eh);
fwrite
emits a warning when attempting a write and the buffer is full. I'm not aware of any way to avoid that problem.
Do we need the feof
check at all? It seems unreliable at best, which is why we started counting empty writes in the first place.
It seems like this works pretty well:
set_error_handler(function ($errno, $msg, $file, $line) {
// Skip EAGAIN errors
if (strpos($msg, "errno=11") !== false) {
return;
}
throw new StreamException("Error writing to stream: $msg ($errno)", $errno);
});
try {
// Use conditional, because PHP doesn't like getting null passed
if ($this->chunkSize) {
$written = \fwrite($this->resource, $data, $this->chunkSize);
} else {
$written = \fwrite($this->resource, $data);
}
}
finally {
restore_error_handler();
}
What happens if you only delete the feof
and don't bother with the error handler? Does it just keep waiting to write then?
There was no error, so it was fine but i guess the proper way should be to handle fwrite errors, shouldn't it?
I'm just trying to understand why feof
hangs, but fwrite
succeeds. The custom error handler is probably fine. I think EAGAIN writes are uncommon enough that the performance difference will be negligible.
On macOS EAGAIN
seems to equal 35 rather than 11. PHP has some extension specific constants that expose this number (PCNTL_EAGAIN
, SOCKET_EAGAIN
, and MSG_EAGAIN
). Will have to look if there's another way to find this value.
Using strpos("Resource temporarily unavailable")
also works for me. Can I depend on that error message though?
Edit: Looks like no, that is also an OS-dependent message.
Removing the feof
check from the initial write in ResourceOutputStream::send()
causes the test testClosedRemoteSocketWithFork
to fail, so I'm not sure we can remove that entirely.
Does it fail also with the error handler?
I get temporarily unavailable
on Linux but i first thought errno=11 would be safer. It shouldnt be a big deal to have a platform dependent check, right?
Platform dependent checks are always ugly. Error number will be better than message, because these messages can be localized.
@brstgt The test still fails with the error handler.
Maybe we should be looking at why feof
is hanging on a non-blocking stream.
I encountered hanging feof resulting in StreamException("The stream was closed by the peer").
If it results in a StreamException
, it doesn't hang. Could you clarify what hang means?
The only thing I recall that might result in hanging are fragmented TLS records (packets), but we have never verified that.
Seems like https://github.com/php/php-src/blob/9a497f5c10ee547234b678208875ef3490870bb7/ext/openssl/xp_ssl.c#L2475-L2476 is the problem that results in the hang.
See php/php-src#3729 for a fix.
The latest PHP releases are fixed now.