Greg
Greg

Reputation: 21909

Cannot interrupt process started with proc_open() to start a PHP development server

For a project I'm working on currently, I am using the PHP inbuilt development server to serve files for a part of the test runner. I noticed inconsistent behaviour, and after some debugging I realised that the process that was being started due to proc_open() was never being closed, so the development server continued running into the next test.

I have boiled down the issue so I can reproduce it in the simplest way. A file time.php simply outputs the current time:

time.php:

The time is <?php echo date("H:i:s");

Then start.php will start the inbuilt PHP development server (php -S) on port 8888 so the time.php script can be accessed on http://localhost:8888/time.php. This part works, but after the script ends, the server continues to run. As you can see in the script's output, the Process ID reported by proc_get_status() is actually a different number to the PHP process that continues to run after the script has completed.

My question is, why does the start.php script create more than one process, and how can I properly kill/interrupt the actual process that is running the PHP development server?

start.php:

<?php
// This script is executed on the CLI and will start a PHP dev server.
echo "Starting dev server..." . PHP_EOL;

$command = "php -S 0.0.0.0:8888";

$descriptor = [
    0 => ["pipe", "r"],
    1 => ["pipe", "w"],
    2 => ["pipe", "w"],
];
$pipes = [];
$process = proc_open($command, $descriptor, $pipes);

$status = proc_get_status($process);
$pid = $status["pid"];

echo "Server started on process ID: $pid" . PHP_EOL;
sleep(1);
$output = file_get_contents("http://localhost:8888/time.php");
echo "Output on port 8888: $output" . PHP_EOL;

echo "Leaving running for 10 seconds..." . PHP_EOL;

for($i = 0; $i < 10; $i++) {
    sleep(1);
    echo ".";
}

posix_kill($pid, SIGKILL);
sleep(1);
proc_close($process);
sleep(1);

echo "Closed process $pid. Nothing should be serving on port 8888." . PHP_EOL;

$output = file_get_contents("http://localhost:8888/time.php");
echo "Output on port 8888: $output" . PHP_EOL;

echo "That's weird. The server's still running..." . PHP_EOL;

Output:

$ pidof php
# Good, there are no php processes running yet. Let's start.
$ php start.php 
Starting dev server...
Server started on process ID: 4402
Output on port 8888: The time is 18:30:47
Leaving running for 10 seconds...
..........Closed process 4402. Nothing should be serving on port 8888.
Output on port 8888: The time is 18:30:59
That's weird. The server's still running...
$ # Let me check if there is a PHP process running still.
$ pidof php
4403
$ # Weird! It's one digit from the process that was started with proc_open

Upvotes: 0

Views: 460

Answers (1)

Nigel Ren
Nigel Ren

Reputation: 57131

The problem is that proc_open() seems to be a sh process, I modified the code so that it didn't stop the process started and the results are...

Server started on process ID: 15098

and then without stopping 15098 and looking at that process...

nigel@Laptop-Ubuntu:~/eclipse-workspace/Test$ ps -eaf | grep 15098
nigel    15098  4058  0 18:52 pts/0    00:00:00 sh -c php -S 0.0.0.0:8888
nigel    15099 15098  0 18:52 pts/0    00:00:00 php -S 0.0.0.0:8888
nigel    15440 13229  0 18:52 pts/0    00:00:00 grep --color=auto 15098

As to how to resolve this, having read https://www.php.net/manual/en/function.proc-terminate.php#81353 which seems to be stating that it doesn't properly kill the child process and suggests

1) call proc_get_status() to get the parent pid (ppid) of the process I want to kill.

2) use ps to get all pids that have that ppid as their parent pid

3) use posix_kill() to send the SIGKILL (9) signal to each of those child pids

4) call proc_close() on process resource

$pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid $pid`);
foreach($pids as $pid) {
    if(is_numeric($pid)) {
        echo "Killing $pid\n";
        posix_kill($pid, SIGKILL);
    }
}

posix_kill($pid, SIGKILL);
sleep(1);
proc_close($process);
sleep(1);

which seems to clear up properly.

Upvotes: 1

Related Questions