Reputation: 5981
The following scripts monitors /dev/shm/test
for new files and outputs info about it in real time.
The problem is that when user closes the browser, a inotifywait
process remains open, and so on.
Is there any way to avoid this?
<?php
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "a") // stderr is a file to write to
);
$process = proc_open('inotifywait -mc -e create /dev/shm/test/', $descriptorspec, $pipes);
if (is_resource($process)) {
header("Content-type: text/html;charset=utf-8;");
ob_end_flush(); //ends the automatic ob started by PHP
while ($s = fgets($pipes[1])) {
print $s;
flush();
}
fclose($pipes[1]);
fclose($pipes[0]);
fclose($pipes[2]);
// It is important that you close any pipes before calling
// proc_close in order to avoid a deadlock
$return_value = proc_close($process);
echo "command returned $return_value\n";
}
?>
Upvotes: 4
Views: 3239
Reputation: 4414
As inotifywait
runs as own process that basically never ends you need to send it a KILL signal. If you run the script on cli the Ctrl+C signal is sent to the inotifywait process too - but you don't have that when running in the webserver.
You send the signal in a function that gets called by register_shutdown_function
or by __destruct
in a class.
This simple wrapper around proc_open could help:
class Proc
{
private $_process;
private $_pipes;
public function __construct($cmd, $descriptorspec, $cwd = null, $env = null)
{
$this->_process = proc_open($cmd, $descriptorspec, $this->_pipes, $cwd, $env);
if (!is_resource($this->_process)) {
throw new Exception("Command failed: $cmd");
}
}
public function __destruct()
{
if ($this->isRunning()) {
$this->terminate();
}
}
public function pipe($nr)
{
return $this->_pipes[$nr];
}
public function terminate($signal = 15)
{
$ret = proc_terminate($this->_process, $signal);
if (!$ret) {
throw new Exception("terminate failed");
}
}
public function close()
{
return proc_close($this->_process);
}
public function getStatus()
{
return proc_get_status($this->_process);
}
public function isRunning()
{
$st = $this->getStatus();
return $st['running'];
}
}
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "a") // stderr is a file to write to
);
$proc = new Proc('inotifywait -mc -e create /dev/shm/test/', $descriptorspec);
header("Content-type: text/html;charset=utf-8;");
ob_end_flush(); //ends the automatic ob started by PHP
$pipe = $proc->pipe(1);
while ($s = fgets($pipe)) {
print $s;
flush();
}
fclose($pipe);
$return_value = proc->close($process);
echo "command returned $return_value\n";
Or you could use the Symfony Process Component which does exactly the same (plus other useful things)
Upvotes: 1
Reputation: 36264
That's because inotifywait
will wait until changes happen to the file /dev/shm/test/
, then will output diagnostic information on standard error and event information on standard output, and fgets()
will wait until it can read a line: Reading ends when $length
- 1 bytes (2nd parameter) have been read, or a newline (which is included in the return value), or an EOF (whichever comes first). If no length is specified, it will keep reading from the stream until it reaches the end of the line.
So basically, you should read data from the child process' stdout pipe non-blocking mode with stream_set_blocking($pipes[1], 0)
, or check manually if there is data on that pipe with stream_select()
.
Also, you need to ignore user abort with ignore_user_abort(true)
.
Upvotes: 1
Reputation: 13725
Does this help?
$proc_info = proc_get_status($process);
pcntl_waitpid($proc_info['pid']);
Upvotes: 0
Reputation: 437664
You can use ignore_user_abort
to specify that the script should not stop executing when the user closes the browser window. That will solve half of the problem, so you also need to check if the window was closed inside your loop with connection_aborted
to determine when you need to shut down everything in an orderly manner:
header("Content-type: text/html;charset=utf-8;");
ignore_user_abort(true);
ob_end_flush(); //ends the automatic ob started by PHP
while ($s = fgets($pipes[1])) {
print $s;
flush();
if (connection_aborted()) {
proc_terminate($process);
break;
}
}
Upvotes: 0