_initialize(); while ($this->getNumberOfSpokes() < self::MAX_NUMBER_OF_SPOKES) { $spoke = new Spoke(); $spoke->appendPath($this->_getSpoke()->getPath()); $spoke->start(); $this->_spokes[$spoke->getProcessId()] = $spoke; } pcntl_sigprocmask(SIG_BLOCK, $this->_waitSignals); while (true) { $this->_info = []; pcntl_sigwaitinfo($this->_waitSignals, $this->_info); switch ($this->_info[self::SIGNAL_NUMBER]) { case SIGCHLD: while ($processId = pcntl_wait($status, WNOHANG)) { unset($this->_spokes[$processId]); $spoke = new Spoke(); $spoke->appendPath($this->_getSpoke()->getPath()); $spoke->start(); $this->_spokes[$spoke->getProcessId()] = $spoke; } break; case SIGALRM: pcntl_alarm(8); break; case SIGINT: case SIGTERM: $this->terminateChildProcesses(); break 2; default: throw new \UnexpectedValueException('Unexpected blocked signal.'); } } } public function setSpoke(Spoke $spoke): Wheel { $this->_spoke = $spoke; return $this; } protected function _getSpoke(): Spoke { if ($this->_spoke === null) { throw new \LogicException('Spoke is not set.'); } return $this->_spoke; } protected function _initialize(): Wheel { register_shutdown_function([$this, 'terminateChildProcesses']); pcntl_signal(SIGTERM, [$this, 'terminateChildProcesses']); pcntl_signal(SIGINT, [$this, 'terminateChildProcesses']); return $this; } public function terminateChildProcesses(): Wheel { if (!empty($this->_processes)) { /** @var Spoke $spoke */ foreach ($this->_spokes as $spoke) { $processId = $spoke->getProcessId(); if (rand(1, 0)) { posix_kill($processId, SIGKILL); }else { posix_kill($processId, SIGTERM); } unset($this->_processes[$processId]); } } return $this; } public function getNumberOfSpokes(): int { return count($this->_spokes); } } class Spoke { const FORK_FAILURE_CODE = -1; protected $_parentProcessId; protected $_processId; protected $_wheel; protected $_path = ''; public function start(): Spoke { $processId = pcntl_fork(); if ($processId === self::FORK_FAILURE_CODE) { throw new \RuntimeException('Failed to fork a new spoke.'); }elseif ($processId > 0) { $this->_initialize($processId); }else { $this->_initialize(); if (substr_count($this->getPath(), '/') <= 3) { $this->_startNewWheel(); } exit(0); } return $this; } public function getProcessId(): int { if ($this->_processId === null) { throw new \LogicException('Process ID is not set.'); } return $this->_processId; } protected function _startNewWheel(): Spoke { $this->_wheel = new Wheel(); $this->_wheel->setSpoke($this); $this->_wheel->start(); return $this; } protected function _initialize(int $processId = null): Spoke { if ($processId === null) { $this->_parentProcessId = posix_getppid(); $this->_processId = posix_getpid(); $this->appendPath((string)$this->getProcessId()); }else { $this->_processId = $processId; } return $this; } public function getPath(): string { return $this->_path; } public function appendPath(string $path): Spoke { $this->_path .= '/' . $path; return $this; } } $spoke = new Spoke(); $spoke->start(); return;