Home » Php » process – PHP forking and multiple child signals

process – PHP forking and multiple child signals

Posted by: admin July 12, 2020 Leave a comment

Questions:

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

How to&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:

define(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--;
    }

}

Answer:

You could have children send a SIGUSR1 to the parent when they start,then a SIGUSR2 before they exit. The other thing you are dealing with when using primitive signals is the kernel merging them, which it does not do with RT signals. In theory, ANY non-rt signal could be merged.

You might implement some kind of simple locking using sqlite, where only one child at a time can have the talking stick. Just make sure that children handle normally fatal signals so that they remain alive to free the lock.

Answer:

I know this is about 8 years too late (and I hope you found an answer), but just in case it helps someone else I am going to answer.

The use of the pcntl_w* functions will be your friend here and you will probably want to implement a process reaper. The documentation is not very helpful and still does not contain any useful examples.

This would be a multi-part process:

1 – use pcntl_signal send trapped signals to your signal handler

2 – Do your looping/polling and within that loop;

3 – Iterate through the array of your children (which you will create below) and reap them as necessary

4 – fork(): This will consist of the following:

pcntl_async_signals(true);

$children = array();
while ($looping === true)
{
    reapChildren();
    if (($pid = pcntl_fork()) exit (1); // error
    elseif ($pid) // parent
    { 
        $children[] = $pid;
        // close files/sockets/etc
        posix_setpgid ($pid,posix_getpgrp());
    }
    else
    { // child
        posix_setpgid(posix_getpid(),posix_getppid());
        // ... jump to child function/object/code/etc ...
        exit (0); // or whatever code you want to return
    }
} // end of loop

In the reaper, you will need the following:

function reapChildren()
{
    global $children;
    foreach ($children as $idx => $pid)
    {
        $rUsage = array();
        $status = 0; // integer which will be used as the $status pointer
        $ret = pcntl_waitpid($pid, $status, WNOHANG|WUNTRACED, $rUsage);
        if (pcntl_wifexited($status)) // the child exited normally
        {
            $exitCode = pcntl_wexitstatus($status); // returns the child exit status
        }
        if (pcntl_wifsignaled($status))  // the child received a signal
        {
            $signal = pcntl_wtermsig($status); // returns the signal that abended the child
        }
        if (pcntl_wifstopped($status))
        {
            $signal = pcntl_wstopsig($status); // returns the signal that  stopped the child
        }
    }
}

The above reaper code will allow you to poll the status of your children and if you are using php7+, the $signalInfo array which is filled in at your signal handler will contain a lot of useful information you can use.. var_dump it.. check it out. Also, using pcntl_async_signals(true) in php7+ replaces the need for declare(ticks=1) and manually calling pcntl_signal_dispatch();

I hope this helps.