Question

So, I have this PHP daemon worker that listens to IPC messages. Weird thing is that the parent process (result from pcntl_fork) leaves a [php] < defunct> process untill the child process is done but ONLY when the script is started form a cronjob, not directly from command line.

I know < defunct> processes aren't evil, but I can't figure out why it's happening only when running from a cronjob.

Command

/path/to/php/binary/php /path/to/php/file/IpcServer.php

Forking code:

$iParent = posix_getpid();
$iChild  = pcntl_fork();

if ($iChild == -1)
    throw new Exception("Unable to fork into child process.");

elseif ($iChild)
{
    echo "Forking into background [{$iChild}].\n";

    Log::d('Killing parent process (' . $iParent. ').');
    exit;
}

Output

Forking into background [20835].
Killing parent process (20834).

ps aux | grep php

root 20834 0.0 0.0 0 0 ? Zs 14:28 0:00 [php] <defunct>
root 20835 0.0 0.2 275620 8064 ? Ss 15:35 0:00 /path/to/php/binary/php /path/to/php/file/IpcServer.php

I've found out that it's a known apache bug, but then why do I get this bug when running from cronjob?

No correct solution

OTHER TIPS

In PHP the child will become a zombie process unless the parent waits for it to return with a pcntl_wait() or pcntl_waitpid(). The zombies will be destroyed once all processes have ended or are handled. It looks like the parent will become a zombie too if children aren't handled and a child runs longer than the parent.

Example from pcntl_fork page:

$pid = pcntl_fork();
if ($pid == -1) {
     die('could not fork');
} else if ($pid) {
     // we are the parent
     pcntl_wait($status); //Protect against Zombie children
} else {
     // we are the child
}

Or use signal handling like so to prevent waiting on the main thread:

$processes = array(); // List of running processes
$signal_queue = array(); // List of signals for main thread read
// register signal handler
pcntl_signal(SIGCHLD, 'childSignalHandler');

// fork. Can loop around this for lots of children too.
switch ($pid = pcntl_fork()) {
    case -1: // FAILED
        break;
    case 0: // CHILD
        break;
    default: // PARENT
        // ID the process. Auto Increment or whatever unique thing you want
        $processes[$pid] = $someID;
        if(isset($signal_queue[$pid])){
            childSignalHandler(SIGCHLD, $pid, $signal_queue[$pid]);
            unset($signal_queue[$pid]);
        }
        break;
}

function childSignalHandler($signo, $pid=null, $status=null){
    global $processes, $signal_queue;
    // If no pid is provided, Let's wait to figure out which child process ended
    if(!$pid){
        $pid = pcntl_waitpid(-1, $status, WNOHANG);
    }

    // Get all exited children
    while($pid > 0){
        if($pid && isset($processes[$pid])){
            // I don't care about exit status right now.
            //  $exitCode = pcntl_wexitstatus($status);
            //  if($exitCode != 0){
            //      echo "$pid exited with status ".$exitCode."\n";
            //  }
            // Process is finished, so remove it from the list.
            unset($processes[$pid]);
        }
        else if($pid){
            // Job finished before the parent process could record it as launched.
            // Store it to handle when the parent process is ready
            $signal_queue[$pid] = $status;
        }
        $pid = pcntl_waitpid(-1, $status, WNOHANG);
    }
    return true;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top