Search code examples
phplinuxio

Explicitly timeout file read I/O operation in php in Linux?


I have production script that copies and renames files from one mounted network share to another (both CIFS), and sometimes the copy function hangs indefinitely on read I/O dropping the process into uninterruptible sleep where it can only be killed with SIGKILL. (Thankfully, TASK_KILLABLE, apparently.)

Since operation is blocked at the copy call I can't gracefully handle the I/O failure, nor, more importantly, log it.

$res = copy("/path/to/mount/file.pdf", "/path/to/productionqueue/newfile.pdf");

//This doesn't run because of the process state.
if($res) {
  //Report success to the log.
} else {
  //Report failure to the log.
}

It's an environment, file-specific issue, and manual intervention can fix it, but I need to log the failure so an administrator can be notified about the condition. (Aside: I think it's a lock race condition. I can fix the problem by unmounting and remounting the source share, but reproducing it is a bit of an issue.)

Ideally, I'd like the copy call to timeout after 10 seconds, so I can log the error, but there doesn't seem to be a way in PHP to do that.

What I'm considering is delegating copy or read operations to a subprocess monitored by the timeout command and responding based on the exit codes, but is there a simpler way in a PHP script to handle this sort of scenario?

Everything I've found in my search suggests there's only runtime configuration available for socket/network timeouts, not filesystem calls.


Solution

  • The answer is no as far as I can tell - there's no built in way to timeout a file read operation in PHP. However, on systems where your I/O is TASK_KILLABLE you can do the I/O operation in a subprocess and use timeout -s 9 to terminate it. timeout can will then return an exit status that is non-zero.

    For example, with the cp utility:

    $ssrc = escapeshellarg($src);
    $sdst = escapeshellarg($dst);
    //10 second timeout, send KILL
    exec("timeout -s 9 10 cp $ssrc $sdst 2>&1", $output, $ret);
    if($ret != 0) {
      log("Failed copying $src to $dst, exit status: $ret");
    }