Frage

I have a script to limit the execution time length of commands.

limit.php

<?php
declare(ticks = 1);

if ($argc<2) die("Wrong parameter\n");
$cmd = $argv[1];
$tl = isset($argv[2]) ? intval($argv[2]) : 3;

$pid = pcntl_fork();
if (-1 == $pid) {
    die('FORK_FAILED');
} elseif ($pid == 0) {
    exec($cmd);
    posix_kill(posix_getppid(), SIGALRM);
} else {
    pcntl_signal(SIGALRM, create_function('$signo',"die('EXECUTE_ENDED');"));
    sleep($tl);
    posix_kill($pid, SIGKILL);
    die("TIMEOUT_KILLED : $pid");
}

Then I test this script with some commands.

TEST A

php limit.php "php -r 'while(1){sleep(1);echo PHP_OS;}'" 3

After 3s, we can find the processes were killed as we expected.

TEST B

Remove the output code and run again.

php limit.php "php -r 'while(1){sleep(1);}'" 3

Result looks not good, the process created by function "exec" was not killed like TEST A.

[alix@s4 tmp]$ ps aux | grep whil[e]
alix      4433  0.0  0.1 139644  6860 pts/0    S    10:32   0:00 php -r while(1){sleep(1);}

System info

[alix@s4 tmp]$ uname -a
Linux s4 2.6.18-308.1.1.el5 #1 SMP Wed Mar 7 04:16:51 EST 2012 x86_64 x86_64 x86_64 GNU/Linux
[alix@s4 tmp]$ php -v
PHP 5.3.9 (cli) (built: Feb 15 2012 11:54:46) 
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies

Why the processes killed in TEST A but not in TEST B? Does the output impact the SIGKILL?

Any suggestion?

War es hilfreich?

Lösung

There is a PIPE between php -r 'while(1){sleep(1);echo PHP_OS;} (process C) and it's parent (process B), posix_kill($pid, SIGKILL) sends KILL signal to process B, then process B is terminated, but process C doesn't know anything about the signal and continues to run and outputs something to the broken pipe, when process C receives the SIGPIPE signal but has no idea how to handle it so it exits.

You can verify it with strace (run php limit.php "strace php -r 'while(1){sleep(1); echo PHP_OS;};'" 1), and you will see something like this:

14:43:49.254809 write(1, "Linux", 5)    = -1 EPIPE (Broken pipe)
14:43:49.254952 --- SIGPIPE (Broken pipe) @ 0 (0) ---
14:43:49.255110 close(2)                = 0
14:43:49.255212 close(1)                = 0
14:43:49.255307 close(0)                = 0
14:43:49.255402 munmap(0x7fb0762f2000, 4096) = 0
14:43:49.257781 munmap(0x7fb076342000, 1052672) = 0
14:43:49.258100 munmap(0x7fb076443000, 266240) = 0
14:43:49.258268 munmap(0x7fb0762f3000, 323584) = 0
14:43:49.258555 exit_group(0)           = ?

As to php -r 'while(1){sleep(1);}, because there is no broken pipe occurs after it's parent dies, so it continues to run as expected.

Generally speaking, you should kill the whole process group but not only the process itself if you want to kill it's children too, with PHP you can add process B to its own process group, and kill the whole group then, here is the diff with your code:

--- limit.php   2012-08-11 20:50:22.000000000 +0800
+++ limit-new.php   2012-08-11 20:50:39.000000000 +0800
@@ -9,11 +9,13 @@
 if (-1 == $pid) {
     die('FORK_FAILED');
 } elseif ($pid == 0) {
+    $_pid = posix_getpid();
+    posix_setpgid($_pid, $_pid);
     exec($cmd);
     posix_kill(posix_getppid(), SIGALRM);
 } else {
     pcntl_signal(SIGALRM, create_function('$signo',"die('EXECUTE_ENDED');"));
     sleep($tl);
-    posix_kill($pid, SIGKILL);
+    posix_kill(-$pid, SIGKILL);
     die("TIMEOUT_KILLED : $pid");
 }

Andere Tipps

You send the kill signall to your forked process, but that does not propagate to it's children or grandchildren. As such they are orphaned and continue running until something stops them from doing so. (In this case, any attempts to write to stdout should cause an error that then forces them to exit. Redirection of output would also probably result in indefinitely-running orphans.)

You want to send a kill signal to the process and all it's children. Unfortunately I lack the knowledge to tell you a good way to do that. I'm not very familiar with the process control functionality of PHP. Could parse the output of ps.

One simple way I found that works though is to send a kill signal to the whole process group with the kill command. It's messy, and it adds an extra "Killed" message to output on my machine, but it seems to work.

<?php
declare(ticks = 1);

if ($argc<2) die("Wrong parameter\n");
$cmd = $argv[1];
$tl = isset($argv[2]) ? intval($argv[2]) : 3;

$pid = pcntl_fork();
if (-1 == $pid) {
    die('FORK_FAILED');
} elseif ($pid == 0) {
    exec($cmd);
    posix_kill(posix_getppid(), SIGALRM);
} else {
    pcntl_signal(SIGALRM, create_function('$signo',"die('EXECUTE_ENDED');"));
    sleep($tl);
    $gpid = posix_getpgid($pid);

    echo("TIMEOUT_KILLED : $pid");
    exec("kill -KILL -{$gpid}"); //This will also cause the script to kill itself.
}

For more information see: Best way to kill all child processes

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top