1
votes

I have a program that normally run till it terminates, but under some situations, it created two children process, and the main process then exits. The children processes will be still running after the main process exited.

I want to call this program in PHP.

Originally, I used the following strategy (not valid php code),

$process=proc_open("python xxx.py");
while (1) {
   sleep(5);
   $status = proc_get_status($process);
   if (!$status['running']) {
       exit_loop;
   }
   if (timeout) {
      proc_terminate($process, 9);
      exit_loop;
   }
}

But then I soon found the bug that the process actually exited immediately that $status['running'] is false, but the children process is still running. How to actually wait for all children processes?

2nd question is i haven't looked into the proc_terminate, but I also observed that proc_terminate may not work as I expected. Is that because the $process is a bash but not the real python process? It may also because that what I terminated is only the parent main process but not the children process.

Anyone advise me that whether the above code is robust PHP code or not? Is there any better way to deal with this?

Thanks.

======================

More info,

  1. I run this program in shell (CLI), and not related to CGI functions. So I won't need to worry about max_execution_time.

  2. I want to wait the children processes created by the main process opened by proc_open, I will only create one process in the program.

  3. The python program I want to call may hang for very long time, so I want to check timeout and terminate it.

3

3 Answers

2
votes

proc_get_status can get pid of child process

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

then you can use pcntl_waitpid to wait until the child process exit

pcntl_waitpid($pid, $child_status);

But you need to have your php compiled with pcntl extension.

See PCNTL Functions

UPDATE: If you want to kill child process after a timeout

First: install a SIGALARM handler in your main process

function term_proc() {
   // terminate your all child process
}

pcntl_signal(SIGALARM, term_proc);

Second: use pcntl_alarm to send a alarm signal after seconds

pcntl_alarm($seconds)

For 2nd question, proc_terminate will work no matter what process be opend.

2
votes

If you do not intend to interact with the spawned process in some way (e.g. by redirecting its input or output), then you can use system instead of proc_open. system will wait for the spawned process to finish, so there's no need to check on it every so often.

Be careful: Waiting on another process may result in your script being terminated due to exceeding the max_execution_time. It may also not play well with your web server's internal management of execution units.

Update: It seems that you want to launch a process X, then wait for all processes that process X itself launches to exit before continuing. I 'm sorry to be the bearer of bad news, but PHP is not intended for this type of work and what you are after is not possible to achieve with stock PHP. It would certainly be possible for a custom extension to be written in C that exposes this functionality, but as far as I know there is no such extension publicly available.

2
votes

It is difficult for a parent process to follow all of its descendants. A process will only be notified of direct child deaths. There's a few different mechanisms you can take that can try to do what you want, but best would be re-writing your application to not care about your process's grandchildren.

  • cgroups provides enough mechanism for systemd to follow all the child processes started from a single initial execve(2) call.

    While you can use cgroups yourself, I haven't learned much about the consequences of trying to use cgroups for process management while systemd or libvirt or other tools might be doing similar things. (That's my ignorance more than a limitation of cgroups -- perhaps it handles it beautifully. I like to dream.)

  • Open a pipe(2) in your process and give the read end of the pipe(7) created to the child process in a file descriptor high enough that it won't be used "accidentally". (dup2(fd[0], 20) is probably good.) Make sure your other programs don't close unexpected file descriptors. Set the O_NONBLOCK flag on the writing end (fd[1]) using fcntl(2)'s F_SETFL command.

    Periodically try to write a byte into the pipe. Eventually, you'll either get EPIPE error returns indicating that the read end has been closed by all children OR you'll get an error return with errno set to EAGAIN meaning the pipe is full and there is still a child running.