So, you need a php process that can launch other processes (background tasks, if you will) and monitor them for completion. This is a common problem but the solution in PHP isn't exactly straight-forward. There are a number of process control extensions listed on the PHP.net website and PCNTL is a common solution.
PCNTL uses a "forking" design similar to all unix processes: every new process is a clone of its parent process. The Basic usage example on PHP.net is, indeed, complete but I found it hard to decypher at first. Overall, there were two parts of the implementation I had trouble grokking.
Control flow of processes through the PHP file
The control flow hinges on the pcntl_fork()
function. It creates a copy of the parent process where execution is at the same place in both processes. Its return value can mean 3 different things and serves as the identifier for which process the current execution is in.
- If the value is
-1
, there was an error starting the child process. This is returned on the parent process. - If the value is greater than
0
, it is the process id ($pid
) of the child process. This is also returned on the parent process. - If the value is
0
, it is the child process.
I like to organize this code like this:
function code_for_child_process() {
// Executed in the child process.
}
function code_for_parent_process() {
// Executed in the parent process.
}
function code_for_failed_to_launch_child_process() {
// Executed in the parent process when forking a child didn't work.
}
function fork_process() {
$pid = pcntl_fork();
if ($pid == -1) {
code_for_failed_to_launch_child_process();
}
else if ($pid) {
code_for_parent_process();
}
else {
code_for_child_process();
exit(); // Make sure to exit.
}
}
This illustrates which code will be executed in which process and abstracts that away from the confusing pcntl_fork()
return value.
Cool, now we know how to make a new process and tell what code is executed in each.
Notifying the parent process when a child is done executing
This is accomplished with the pcntl_wait()
function that returns the $pid
of a child process that has exited.
This function can either block execution until a $pid
is returned or return immediately with 0
if no child process has exited since its last call. Use WNOHANG
as the 2nd argument to do the latter.
For the non-blocking version you can use a loop to keep polling for completed child processes. This is done with code like this on the parent process:
function code_when_child_is_done($pid) {
// Parent runs this when the child process, $pid, is done.
}
while (TRUE) {
$pid = pcntl_wait($status, WNOHANG);
if ($pid > 0) {
code_when_child_is_done($pid);
}
}
All together now
Putting this all together gives us a parent process that can launch children and run code when the child process is finished:
function code_for_child_process() {
// Executed in the child process.
}
function code_for_failed_to_launch_child_process() {
// Executed in the parent process when forking a child didn't work.
}
function code_when_child_is_done($pid) {
// Executed in the parent process when the child process, $pid, is done.
}
function fork_process() {
$pid = pcntl_fork();
if ($pid == -1) {
code_for_failed_to_launch_child_process();
}
else if ($pid == 0) {
code_for_child_process();
exit(); // Make sure to exit.
}
else {
return $pid;
}
}
// Start two child processes.
$child_pids = array();
$child_pids[] = fork_process();
$child_pids[] = fork_process();
while (TRUE) {
$pid = pcntl_wait(-1, $status, WNOHANG);
if ($pid > 0) {
code_when_child_is_done($pid);
}
sleep(1);
}
And that's it, a bare-bones parent/child process management system in PHP. A few things to keep in mind:
- You need to have the PCNTL extension installed on your server, which can be a pain depending on how you host.
- Forking a process can do weird things with database connections. The most common is that you'll need to reconnect to the database after the fork in the child process.