4

I'm trying to invoke a long-running shell command inside a PHP CLI script with exec(). But I can't for the life of me figure out how to interrupt the PHP script and kill the spawned child process(s). It seems like as soon as I call exec(), my signal handler is ignored. The following code works as I would expect; if I send SIGTERM to the process, it echoes SIGTERM and exits immediately.

<?php
  declare(ticks = 1);

  function sig_handler($signo) {
    switch ($signo) {
      case SIGTERM:
        echo 'SIGTERM' . PHP_EOL;
        flush();
        break;
      default:
    }
  }

  pcntl_signal(SIGTERM, 'sig_handler', false);

  sleep(60);
?>

However, if I replace sleep(60); with exec('sleep 60');, I don't reach my signal handler until the sleep finishes. I have two questions:

  1. How can I get signals to work with exec (or shell_exec or proc_open)?
  2. Upon trapping a signal, how can I kill any child processes spawned by exec?
hakre
  • 193,403
  • 52
  • 435
  • 836
ThisSuitIsBlackNot
  • 23,492
  • 9
  • 63
  • 110

1 Answers1

5

The documentation does say that:

If a program is started with this function, in order for it to continue running in the background, the output of the program must be redirected to a file or another output stream. Failing to do so will cause PHP to hang until the execution of the program ends.

(Emphasis mine.) I guess the hanging applies to signal handlers, too.

To be able to control the child process it looks like you have to execute them in the old-fashioned way, with fork+exec:

switch ($pid = pcntl_fork()) {
  case -1: // failed to create process
     die('Fork failed');
  case 0: // child
     pcntl_exec($path,$args);
     die('Exec failed');
}

Once the parent process has the child's process id in $pid, it could send SIGINT to the child with posix_kill($pid, SIGINT).

Update: apparently you could also use proc_open and proc_terminate.

robbmj
  • 16,085
  • 8
  • 38
  • 63
Joni
  • 108,737
  • 14
  • 143
  • 193
  • Doh! I even knew about exec() hanging, already ran into that in a different context. I tried the pcntl_fork, pcntl_exec method yesterday and about a thousand other permutations but just couldn't get it to work in my project. But after seeing your answer I created a simple test script and it worked like a charm. The only problem now is, after I send a signal to my child process, it becomes a zombie. – ThisSuitIsBlackNot Apr 24 '12 at 16:57
  • I solved the zombie child process by adding SIGCHLD to my signal handler and running pcntl_waitpid(0, $status) (also in the signal handler). So, if the parent receives SIGTERM, it stops waiting for the child and sends SIGTERM; when the child exits, it sends SIGCHLD to the parent and gets cleaned up...everyone is happy. – ThisSuitIsBlackNot Apr 24 '12 at 17:12