Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP forking and multiple child signals

Tags:

php

fork

process

I'm trying to write a script which creates a number of forked child processes using the pcntl_* functions.

Basically, there is a single script which runs in a loop for about a minute, periodically polling a database to see if there is a task to be run. If there is one, it should fork and run the task in a separate process so that the parent isn't held up by a long-running task.

Since there possibly could be a large number of tasks ready to be run, I want to limit the number of child processes that are created. Therefore, I am keeping track of the number of processes by incrementing a variable each time one is created (and then pausing if there's too many), and then decrementing it in a signal handler. Kind of like this:

define(ticks = 1);

$openProcesses = 0; // how many we have open
$max = 3;           // the most we want open at a time

pcntl_signal(SIGCHLD, "childFinished");

while (!time_is_up()) {
    if (there_is_something_to_do()) {
        $pid = pcntl_fork();
        if (!$pid) {      // I am the child
            foo();        //   run the long-running task
            exit(0);      //   and exit
        } else {          // I am the parent
            ++$openProcesses;
            if ($openProcesses >= $max) {
                pcntl_wait($status);    // wait for any child to exit 
            }                           // before continuing
        }
    } else {
        sleep(3);
    }
}

function childFinished($signo) {
    global $openProcesses;
    --$openProcesses;
}

This works pretty much ok most of the time, except for when two or more processes finish simultaneously - the signal handler function is only called once, which throws out my counter. The reason for this is explained by "Anonymous" in the notes of the PHP manual:

Multiple children return less than the number of children exiting at a given moment SIGCHLD signals is normal behavior for Unix (POSIX) systems. SIGCHLD might be read as "one or more children changed status -- go examine your children and harvest their status values".

My question is this: How do I examine the children and harvest their status? Is there any reliable way to check how many child processes are open at any given time?

Using PHP 5.2.9

like image 576
nickf Avatar asked Feb 16 '10 06:02

nickf


1 Answers

One way is to keep an array of the PIDs of the child processes, and in the signal handler check each PID to see if it's still running. The (untested) code would look like:

declare(ticks = 1);

$openProcesses = 0; 
$procs = array();
$max = 3;

pcntl_signal(SIGCHLD, "childFinished");

while (!time_is_up()) {
    if (there_is_something_to_do()) {
        $pid = pcntl_fork();
        if (!$pid) {      
            foo();        
            exit(0);      
        } else {          

            $procs[] = $pid; // add the PID to the list

            ++$openProcesses;
            if ($openProcesses >= $max) {
                pcntl_wait($status);     
            }                           
        }
    } else {
        sleep(3);
    }
}

function childFinished($signo) {

    global $openProcesses, $procs;

    // Check each process to see if it's still running
    // If not, remove it and decrement the count
    foreach ($procs as $key => $pid) if (posix_getpgid($pid) === false) {
        unset($procs[$key]);
        $openProcesses--;
    }

}
like image 162
wavemode Avatar answered Nov 18 '22 03:11

wavemode