pcntl_fork() results in defunct parent process
Asked Answered
T

1

2

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?

Torsibility answered 26/4, 2013 at 14:6 Comment(1)
I have the exact same problem, however I'm running my PHP daemon through Upstart. My application forks once. Started via Upstart the parent process becomes a zombie. Started via CLI - no parent process zombie.L
S
4

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;
}
Shaped answered 1/5, 2013 at 22:23 Comment(5)
I'm having a zombie parent, not children. But are you saying that my parent process should constantly wait for the child(ren) to finish? To me that doesn´t make any sense when the main goal is having a daemon process.Torsibility
Ahh, I misread your question. I believe you can also prevent zombie children with a pcntl_signal, but I'm not great with PHP processes. I suspect all PHP processes become zombies (parent or child) if they end and aren't handled while other processes are still running.Shaped
Both my child and parent processes have proper signal handling. I'm investigating now whether maybe it's PHP not garbage cleaning the parent process really well because the child is using some resources from the parent process.Torsibility
Could be related to that. I know you have to open database and ftp (maybe files too) connections after you fork because otherwise the child is given a reference to the same object (And will destroy it on exit) and not a copy like with other variables and objects. Weird problem though.Shaped
on your example, the function childSignalHandler is not executed on my tests here...i dont know why... any help?Hauck

© 2022 - 2024 — McMap. All rights reserved.