Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set a timeout on fopen with named pipes

Tags:

linux

php

fopen

ipc

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.)

like image 472
Austin Avatar asked Nov 17 '14 04:11

Austin


1 Answers

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.

like image 57
Mikael Lindqvist Avatar answered Oct 24 '22 03:10

Mikael Lindqvist