[php-src] Issue #8242: SSE terminate the script & proc_open leaves child's running in background
| From: | hassandraga | Date: | Wed, 23 Mar 2022 11:34:11 +0000 |
| Subject: | [php-src] Issue #8242: SSE terminate the script & proc_open leaves child's running in background | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-240463@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/8242
Author: hassandraga
### Description
This code is simply a combination of [Mozilla Server-Sent Events
Example](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
and [PHP proc_open() Example](https://www.php.net/manual/en/function.proc-open.php), which make the
browser receive the output of a command in real-time.
The first issue is [proc_terminate() leaves children
running](https://bugs.php.net/bug.php?id=39992), which is already open since 2006, but people
suggested some workarounds (_workarounds 1, 2, 4_).
The second issue which is why I'm open this, is the PHP script get killed as soon as the SSE
connection get closed, which mean _kill_process($process)_ never reached, while in PHP 5.6 the
script still alive and terminate normally. I fix it by
register_shutdown_function()
(_workarounds 3_).
```
<?php
// --------------------------------------------
set_time_limit(0);
ini_set('auto_detect_line_endings', 1);
ini_set('max_execution_time', '0');
date_default_timezone_set("America/Halifax");
ob_end_clean();
gc_enable();
header("Content-Type: text/event-stream");
header("Cache-Control: no-store");
header('Access-Control-Allow-Credentials: true');
header("Access-Control-Allow-Origin: *");
header('Access-Control-Expose-Headers: X-Events');
// -------------------------------------------
$process;
$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", "w") // stderr is a pipe that the child will
write to
);
$env = '/home';
$pipes;
$process;
// -------------------------------------------
function kill_process() {
global $process;
$status = proc_get_status($process);
if($status['running'] == true) {
// Close all pipes that are still open
fclose($pipes[1]); //stdout
fclose($pipes[2]); //stderr
// Get the parent pid of the process we want to kill
$ppid = $status['pid'];
// Use ps to get all the children of this process, and kill them
$pids = preg_split('/\s+/', ps -o pid --no-heading --ppid $ppid);
foreach($pids as $pid) {
if(is_numeric($pid)) {
posix_kill($pid, 9); // SIGKILL
}
}
}
// Workaround 1
posix_kill($ppid, 9);
// Close The Process
proc_close($process);
// Workaround 2
proc_terminate($process);
}
function SendEventToBrowser($event, $msg) {
echo ("event: $msg\n".PHP_EOL);
echo ("data: $msg\n".PHP_EOL);
while (ob_get_level() > 0)
ob_end_flush();
flush();
}
function SendToBrowser($msg) {
echo ("data: $msg\n".PHP_EOL);
while (ob_get_level() > 0)
ob_end_flush();
flush();
}
// ---------------------------------------------
// Workaround 3
register_shutdown_function('kill_process');
// Workaround 4 (exec)
$process = proc_open("exec /bin/bash /home/MyScript.sh", $descriptorspec, $pipes,
$env, array());
if(is_resource($process)) {
SendToBrowser('Shell Script Started');
while ($s = fgets($pipes[1])) {
SendToBrowser($s);
// break the loop if the client
// aborted the connection
if (connection_aborted()){
break;
}
}
kill_process($process); // Never Reached
}
SendToBrowser('Shell Script Closed');
?>
```
### PHP Version
PHP 7.4.3 (cli) (built: Mar 2 2022 15:36:52) ( NTS )
### Operating System
Ubuntu 20.04.4 LTS