*/ /** Manage the fork of children in Posix mode */ class fork { /** The PID list of childs */ private $pidList = array (); /** The constructor check if the posix functions exists */ public function __construct () { if (! function_exists ("pcntl_fork")) throw new \Exception ("Can't fork as PHP doesn't have the pcntl_fork", 500); declare (ticks=1); } /** Return the number of active PID */ public function childCount () { return count ($this->pidList); } /** Return the list of the active PID */ public function childList () { return $this->pidList; } /** Create a child * If some parameters are provided, the called child method will receive them * This function fork and return the child PID * @param callable $callable The callback method to use in child * @param mixed|null $params The params to provide to child method * @return The child PID */ public function startChild ($callable, $params = array ()) { $pid = pcntl_fork (); if ($pid === -1) throw new \Exception ("Can't fork the child", 500); elseif ($pid) { // The parent $this->pidList[$pid] = $pid; return $pid; } // Call the child method $args = func_get_args (); unset ($args[0]); call_user_func_array ($callable, $args); exit; } /** Create a detached child. The terminal is closed. All the displayed * messages from the child are silently dropped. * If some parameters are provided, the called child method will receive them * This function fork and return the child PID * @param string $name The name displayed in the processus list * @param callable $callable The callback method to use in child * @param mixed|null $params The params to provide to child method * @return The child PID */ public function startDetachedChild ($name, $callable, $params = array ()) { $pid = pcntl_fork (); if ($pid === -1) throw new \Exception ("Can't fork the child", 500); elseif ($pid) { // The parent $this->pidList[$pid] = $pid; return $pid; } // Call the child method $sid = posix_setsid(); // Will catch all the text messages from the application to not crash if // there is an "echo" ob_start (); // Close the file handlers STDOUT/STDIN fclose (STDIN); fclose (STDOUT); fclose (STDERR); if (function_exists ("cli_set_process_title")) cli_set_process_title ($name); $args = func_get_args (); unset ($args[0]); call_user_func_array ($callable, $args); exit; } /** Wait the end of one child * Return the PID of the dead child */ public function waitEndChild () { while (1) { $stoppedPid = pcntl_wait ($status, WNOHANG); if ($stoppedPid > 0) { unset ($this->pidList[$stoppedPid]); return $stoppedPid; } usleep (100); } } /** Clean the childs which are finished. Do not block the process */ public function cleanEndChild () { $stoppedPid = pcntl_wait ($status, WNOHANG); while ($stoppedPid > 0) { unset ($this->pidList[$stoppedPid]); $stoppedPid = pcntl_wait ($status, WNOHANG); } } /** Stop (SIGTERM) a specific child. * If the $maxWait parameter is set, wait the dead of the child for $maxWait * seconds. If $maxWait is not set, do not wait the child, only send it the * signal * @param integer $pid The PID of the child to stop * @param integer|null $maxWait The maximum time to wait the child if set * @return $pid if the child is dead correctely. Return false if the child is * not dead in the $maxWait time */ public function stopChild ($pid, $maxWait = null) { if (! key_exists ($pid, $this->pidList)) throw new \Exception ("Can't stop pid '$pid' : not in the fork list", 500); return $this->sendSignalToChild (SIGTERM, $pid, $maxWait); } /** Kill (SIGKILL) a specific child. * If the $maxWait parameter is set, wait the dead of the child for $maxWait * seconds. If $maxWait is not set, do not wait the child, only send it the * signal * @param integer $pid The PID of the child to stop * @param integer|null $maxWait The maximum time to wait the child if set * @return $pid if the child is dead correctely. Return false if the child is * not dead in the $maxWait time */ public function killChild ($pid, $maxWait = null) { if (! key_exists ($pid, $this->pidList)) throw new \Exception ("Can't kill pid '$pid' : not in the fork list", 500); return $this->sendSignalToChild (SIGKILL, $pid, $maxWait); } /** Send a signal to a specific child. * If the $maxWait parameter is set, wait the dead of the child for $maxWait * seconds. If $maxWait is not set, do not wait the child, only send it the * signal * @param integer $signal The signal to send to child (SIGTERM or SIGKILL) * @param integer $pid The PID of the child to stop * @param integer|null $maxWait The maximum time to wait the child if set * @return $pid if the child is dead correctely. Return false if the child is * not dead in the $maxWait time */ private function sendSignalToChild ($signal, $pid, $maxWait = null) { posix_kill ($pid, $signal); if ($maxWait !== null) { $startWait = time (); while (time () < $startWait + $maxWait) { if (pcntl_waitpid ($pid, $status, WNOHANG) > 0) { unset ($this->pidList[$pid]); return $pid; } usleep (100); } return false; } return $pid; } /** Stop all the existing children. * If the $maxWait parameter is set, wait the dead of the child for $maxWait * seconds. If $maxWait is not set, do not wait the child, only send it the * signal * @param integer|null $maxWait The maximum time to wait the child if set * @return true if all the children are dead correctely. Return false if at * least one child isnot dead in the $maxWait time */ public function stopAll ($maxWait = null) { return $this->sendSigToAll (SIGTERM, $maxWait); } /** Kill all the existing children. * If the $maxWait parameter is set, wait the dead of the child for $maxWait * seconds. If $maxWait is not set, do not wait the child, only send it the * signal * @param integer|null $maxWait The maximum time to wait the child if set * @return true if all the children are dead correctely. Return false if at * least one child isnot dead in the $maxWait time */ public function killAll ($maxWait = null) { return $this->sendSigToAll (SIGKILL, $maxWait); } /** Send a stop or kill signal to all the existing children. * If the $maxWait parameter is set, wait the dead of the child for $maxWait * seconds. If $maxWait is not set, do not wait the child, only send it the * signal * @param integer $signal The signal to send to child (SIGTERM or SIGKILL) * @param integer|null $maxWait The maximum time to wait the child if set * @return true if all the children are dead correctely. Return false if at * least one child isnot dead in the $maxWait time */ private function sendSigToAll ($signal, $maxWait = null) { foreach ($this->pidList as $pid) { posix_kill ($pid, $signal); } if ($maxWait !== null) { $startWait = time (); while (time () < $startWait + $maxWait) { $pid = pcntl_wait ($status, WNOHANG); if ($pid > 0) { unset ($this->pidList[$pid]); if (empty ($this->pidList)) return true; } usleep (100); } return false; } return true; } }