Handling "Bad file descriptor" during stream_select()

php.internals

Grzegorz Zdanowski

10 years ago
Hello! During refactoring of one of my server projects written in PHP I was curious what PHP will do if one of FDs given to stream_select() will gone away. Of course by „gone away“ I mean some underlying error like forceful close of FD and not a standard disconnection of the client. I did some tests, read documentation for select() call, dig into both kernel & PHP sources and I came to conclusion that there’s no way to cleanly recover from stream_select() error :( It's also worth to mention PHP displays E_WARNING with error, but there's no clean way to determine actual error. I wrote simple test code: http://pastebin.com/6ws1xp5q After running it one of it's sockets need to be killed. The easiest way is to use gdb on linux: 1. gdb -p PID 2. call close(3) //it will kill listener 3. q 4. y After such sequence stream_select() will return false and all arrays will stay unmodified. Also as I mentioned before "PHP Warning: stream_select(): unable to select [9]: Bad file descriptor (max_fd=4)" will be triggered. I tried is_resource(), fread(), stream_get_meta_data(), feof() - none of them are giving me any clues which socket errored. Killing whole server on single socket error is not an option for me. Is there any (even crazy) way to determine which socket is considered bad? p.s. I found similar problem regarding C on SO, but they didn't found a good solution: http://stackoverflow.com/questions/12838041/method-to-handle-bad-file-descriptor-error --- Greg 'aka kiler129

Dan Ackroyd

10 years ago
On 28 March 2016 at 02:57, Grzegorz Zdanowski <grzegorz129@gmail.com> wrote:
> Hello! > > by „gone away“ > I mean some underlying error like forceful close of FD and not a > standard disconnection > of the client.
What problem exactly are you trying to solve, and do you have a way of showing the problem that isn't just what looks like a programming mistake? e.g. The following code works fine. The file being closed doesn't affect the filehandle $fileHandle = fopen('foo.txt', 'r+'); unlink('foo.txt'); $data = fread($fileHandle, 20); The following code doesn't work fine. The filehandle being closed, surprisingly enough, makes it not usable any more: $fileHandle = fopen('foo.txt', 'r+'); fclose($fileHandle); $data = fread($fileHandle, 20); or, as the link you sent puts it:
> If a connection is closed at client side, and you call select() on the server side socket, FD_READ will be triggered and recv() will return zero, then you can close the socket at server side. > > The "bad file descriptor" error only happens when you call API on an already closed socket.
So I don't understand what problem you're trying to analyze, other than closed file descriptors are closed. cheers Dan

Grzegorz Zdanowski

10 years ago
2016-03-28 11:19 GMT-05:00 Dan Ackroyd <danack@basereality.com>:
> On 28 March 2016 at 02:57, Grzegorz Zdanowski <grzegorz129@gmail.com> wrote: >> by „gone away“ >> I mean some underlying error like forceful close of FD and not a >> standard disconnection >> of the client. > > What problem exactly are you trying to solve, and do you have a way of > showing the problem that isn't just what looks like a programming > mistake?
You're missing one point - the native file descriptor living inside a kernel and somewhat managed by PHP C code can change it's state independently from PHP knowledge. The problem is stream resource is only an loosely representation of native FD, not an actual file descriptor. TBH I don't know any way to damage the actual FD by programmer mistake in PHP. I came across the problem in real environment in two cases: 1. Socket was listening with some specific IP and network card gone away. Every call to stream_select() produced situation described in OP and send the code into dead-loop. 2. Using hardware-dependent socket (like ttys to USB<>RS232 converters) you can trigger such error by simply disconnecting the USB cord (not always, because some drivers will just trigger EOF on socket and close it gracefully).
> e.g. The following code works fine. The file being closed doesn't > affect the filehandle > > $fileHandle = fopen('foo.txt', 'r+'); > unlink('foo.txt'); > $data = fread($fileHandle, 20);
It's perfectly understandable - files have nothing to do with file descriptors opened for them.
> The following code doesn't work fine. The filehandle being closed, > surprisingly enough, makes it not usable any more: > > $fileHandle = fopen('foo.txt', 'r+'); > fclose($fileHandle); > $data = fread($fileHandle, 20);
Yes, that's true - after you call fclose() PHP gracefully closes the socket and marks the memory, so $fileHandle will no longer be a valid stream resource.
> So I don't understand what problem you're trying to analyze, other > than closed file descriptors are closed.
If the socket was properly closed there's no discussion - you deal with thousand times a day, but the problem comes when the underlying FD will gone away. Actually I found in C you can just call fcntl(FD, F_GETFD) and you'll get -1 if given FD is no longer valid FD. In PHP there's no way to do it - that's my problem. --- Greg 'aka kiler129