I've got this block of code which works perfectly for my needs in my various php cli programs. Except that sometimes a child will become a zombie.
My question is where to place code to check if a child runs say for 5 minutes and if it's longer then to kill it?
I know about posix_kill to kill it and how to keep track of it. There are examples of taskmanagers here.
I am unsure how to combine these new features into the code. Everytime I attempt to, I just get into a mess. Maybe someone who knows about forking can fix my code up?
Ignore all the error_logs - I like to see what is happening when it runs.
public function __construct($data) {
//Keep track of all of the children processes
$this->children = Array();
//Specify the maximum number of child processes to fork at any given time
$this->max_children = 5;
}
private function process()
{
foreach ($collection as $stuff)
{
//FORK THE PROCESS
$pid = pcntl_fork();
//Something went wrong
if($pid == -1)
{
error_log ("could not fork");
die ();
}
//PARENT PROCESS
if($pid)
{
//error_log ("Parent: forked " . $pid);
$this->children[] = $pid;
}
//CHILD PROCESS
else
{
// Do stuff here
exit(); //Exit the child thread so it doesn't continue to process the data
}
//COLLECT ALL OF THE CHILDREN AS THEY FINISH
while( ($c = pcntl_wait($status, WNOHANG OR WUNTRACED) ) > 0)
{
//error_log ("Collected Child - " . $c);
$this->remove_thread($this->children, $c);
error_log ("children left: " . count($this->children));
}
//WAIT FOR A CHILD TO FINISH IF MAXIMUM PROCESSES IS EXCEEDED
if(sizeof($this->children) > $this->max_children)
{
//error_log ("Maximum children exceeded. Waiting...");
if( ($c = pcntl_wait($status, WUNTRACED) ) > 0)
{
//error_log ("Waited for Child - " . $c);
$this->remove_thread($this->children, $c);
error_log ("children left: " . count($this->children));
}
}
}
//COLLECT ALL OF THE CHILDREN PROCESSES BEFORE PROCEEDING
while( ($c = pcntl_wait($status, WUNTRACED) ) > 0){
//error_log ("Child Finished - " . $c);
$this->remove_thread($this->children, $c);
error_log ("children left: " . count($this->children));
}
}
//Function to remove elements from an array
private function remove_thread(&$Array, $Element)
{
for($i = 0; $i < sizeof($Array); $i++)
{
//Found the element to remove
if($Array[$i] == $Element){
unset($Array[$i]);
$Array = array_values($Array);
break;
}
}
}
First of all: WNOHANG OR WUNTRACED
equals (bool true), WNOHANG | WUNTRACED
is int (3), makes all lot of difference, although not necessarily here.
//set maximum child time.
$maxruntime = 300;
//.....
//..... skip a lot of code, prefer trigger_error($msg,E_USER_ERROR) above die($msg) though
//.....
//if we are the parent
if($pid)
{
//store the time it started
$this->children[$pid] = time();
}
//.....
//..... skip
//.....
//COLLECT ALL OF THE CHILDREN AS THEY FINISH
while(count($this->children) > 0){
//copy array as we will unset $this->children items:
$children = $this->children;
foreach($children as $pid => $starttime){
$check = pcnt_waitpid($pid, $status, WNOHANG | WUNTRACED);
switch($check){
case $pid:
//ended successfully
unset($this->children[$pid];
break;
case 0:
//busy, with WNOHANG
if( ( $starttime + $maxruntime ) < time() || pcntl_wifstopped( $status ) ){
if(!posix_kill($pid,SIGKILL)){
trigger_error('Failed to kill '.$pid.': '.posix_strerror(posix_get_last_error()), E_USER_WARNING);
}
unset($this->children[$pid];
}
break;
case -1:
default:
trigger_error('Something went terribly wrong with process '.$pid, E_USER_WARNING);
// unclear how to proceed: you could try a posix_kill,
// simply unsetting it from $this->children[$pid]
// or dying here with fatal error. Most likely cause would be
// $pid is not a child of this process.
break;
}
// if your processes are likely to take a long time, you might
// want to increase the time in sleep
sleep(1);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With