Austin Austin - 20 days ago 7
PHP Question

How to set a timeout on fopen with named pipes

I am trying to do some IPC in PHP using named pipes. I have a named pipe created via

$pipePath = __DIR__ . '/pipe';
posix_mkfifo($pipePath, 0600);


There is another process that should write to that pipe after finishing some computation. I can wait for it to finish and read the result with something like:

$result = file_get_contents($pipePath);


or the more verbose

$in = fopen($pipePath, 'r');
$result = fread($in, 8192);
fclose($in);


(I have simplified the second approach; in the real code I would check for errors, run
fread
in a loop in case the result is > 8192 bytes, etc.)

However, while the other process should finish, I don't trust it to be successful, so I want to have a timeout on trying to read the result. After waiting for some time, I want to give up and report an error saying it crashed, etc. With the two approaches given, the PHP code will hang forever (or for a very long time) waiting for something to write to the pipe. Specifically,
file_get_contents
and
fread
will hang.

The only solution I was able to come up with is something like this:

$timeout = 10; //seconds
for ($i = 0; $i < $timeout; $i++) {
$in = @fopen($pipePath, 'rn');
if ($in) break;
sleep(1);
}
if (!$in) {
throw new RuntimeException("The other process did not finish in the allotted time");
}
$result = fread($in, 8192);
fclose($in);


This uses the undocumented
'n'
flag to
fopen
as shown in one of the comments on this question. It causes the
fopen
call to fail immediately if it would block.

However, I don't like this solution for two reasons:


  1. It does unnecessary work by checking the pipe every second.

  2. If the computation in the other process takes 1.01 seconds to complete, this will wait a full 2 seconds to get the result. For some things that I want to do, this is enough wasted time that it is worth trying to fix the issue.



When
fopen
is called on a URL, I have the ability to add a context parameter the specifies a timeout value. However, this doesn't seem to work for this, nor does setting the default socket timeout.

Is there a better way to do this with pipes? If not, I may consider switching to Unix sockets, but those are not so easy to support in the other process so I would rather not do this.

(FYI, I am only concerned with Linux; no need to have something that works on Windows or anything else, in case this matters.)

Answer

I found one way to do this...

First, I didn't know about the n flag, this was very useful info!

However, it is not exactly true that the function fails if it would block. It still returns a file handle. We can use the file handle and pass it to the stream_select function to wait for data to become available.

Something like this:

$f=fopen("my.fifo","rn");
$r=array($f);
$w=array();
$x=array();
stream_select($r,$w,$x,10);

This code waits for 10 seconds for someone else to write to the other end of the fifo.