/** * @param ServerEvent $event */ public function bindPnctlEvent(ServerEvent $event) { if (!extension_loaded('pcntl')) { return; } $loop = $event->getEventLoop(); $server = $event->getServer(); $pnctlEmitter = new PnctlEmitter($loop); $pnctlEmitter->on(SIGTERM, function () use($server, $loop) { $server->emit('end'); $server->shutdown(); $loop->stop(); $this->logger->notice('Server stopped !'); }); $pnctlEmitter->on(SIGINT, function () use($server, $loop) { $this->logger->notice('Press CTLR+C again to stop the server'); if (SIGINT === pcntl_sigtimedwait([SIGINT], $siginfo, 5)) { $this->logger->notice('Stopping server ...'); $server->emit('end'); $server->shutdown(); foreach ($this->periodicRegistry->getPeriodics() as $periodic) { if ($periodic instanceof TimerInterface && $loop->isTimerActive($periodic)) { $loop->cancelTimer($periodic); } } $loop->stop(); $this->logger->notice('Server stopped !'); } else { $this->logger->notice('CTLR+C not pressed, continue to run normally'); } }); }
protected function serverLoop() { while ($this->_listenLoop) { if (($client = @socket_accept($this->sockServer)) === false) { $info = array(); if (pcntl_sigtimedwait(array(SIGUSR1), $info, 1) > 0) { if ($info['signo'] == SIGUSR1) { $this->handleProcess(); } } continue; } $socketClient = new SocketClientBroadcast($client, $this); if (is_array($this->connectionHandler)) { $object = $this->connectionHandler[0]; $method = $this->connectionHandler[1]; $childPid = $object->{$method}($socketClient); } else { $function = $this->connectionHandler; $childPid = $function($socketClient); } if (!$childPid) { // force child process to exit from loop return; } $this->connections[$childPid] = $socketClient; } }
/** * @param ServerEvent $event */ public function bindPnctlEvent(ServerEvent $event) { if (!extension_loaded('pcntl')) { return; } $loop = $event->getEventLoop(); $server = $event->getServer(); $pnctlEmitter = new PnctlEmitter($loop); $pnctlEmitter->on(SIGTERM, function () use($server, $loop) { $this->closure($server, $loop); }); $pnctlEmitter->on(SIGINT, function () use($pnctlEmitter) { $this->logger->notice('Press CTLR+C again to stop the server'); if (SIGINT === pcntl_sigtimedwait([SIGINT], $siginfo, 5)) { $pnctlEmitter->emit(SIGTERM); } else { $this->logger->notice('CTLR+C not pressed, continue to run normally'); } }); }
/** * 次のトリガまで待機 * * @param int $sec */ public function wait($sec) { $limit = microtime(true) + $sec; for (;;) { $sec = (int) ($limit - microtime(true) + 1); if ($sec < 1) { return true; } if ($signo = pcntl_sigtimedwait($this->_sigset, $info, $sec)) { switch ($signo) { case SIGTERM: case SIGINT: case SIGHUP: return false; case SIGUSR2: return true; } } } }
function sigSleep($secs = 10) { // Uses pcntl_sigtimedwait instead of sleep, so we can be sure we catch sighup // signals from the OS. This may seem counterproductive, as sleep(3) will wake // on *any* signal, but php does all sorts of crazy things to make this // unreliable. if ($secs < 5) { $secs = 5; } pcntl_sigtimedwait(array(SIGHUP), $sig, $secs); if ($sig['signo'] === SIGHUP) { sigHupHandler(1); } }
/** * Sig Timed Wait * * @param array $set Array of signals to wait for. * @param array $sigInfo The siginfo is set to an array containing informations about the signal. See pcntl_sigwaitinfo(). * @param int $seconds Timeout in seconds. * @param int $nanoseconds Timeout in nanoseconds. * * @return int On success, pcntl_sigtimedwait() returns a signal number. */ public function sigTimedWait(array $set, array $sigInfo, $seconds = 0, $nanoseconds = 0) { return pcntl_sigtimedwait($set, $sigInfo, $seconds, $nanoseconds); }
/** * Waits for signals, with a timeout * @param int Seconds * @param int Nanoseconds * @return void */ protected function sigwait($sec = 0, $nano = 1) { $siginfo = NULL; $signo = pcntl_sigtimedwait(self::$signalsno, $siginfo, $sec, $nano); if (is_bool($signo)) { return $signo; } if ($signo > 0) { $this->sighandler($signo); return TRUE; } return FALSE; }
/** * Wait some time for signal. * If signal received, it will be correclty handled. */ public function wait() { $siginfo = []; $signal = pcntl_sigtimedwait(array_keys($this->handlers), $siginfo, 0, pow(10, 9) * 0.01); $this->handle($signal); }
/** * Waits for signals, with a timeout * @param int Seconds * @param int Nanoseconds * @return boolean Success */ protected function sigwait($sec = 0, $nano = 300000000.0) { $siginfo = null; if (!function_exists('pcntl_sigtimedwait')) { $signo = $this->sigtimedwait(array_keys(static::$signals), $siginfo, $sec, $nano); } else { $signo = @\pcntl_sigtimedwait(array_keys(static::$signals), $siginfo, $sec, $nano); } if (is_bool($signo)) { return $signo; } if ($signo > 0) { $this->sighandler($signo); return true; } return false; }
posix_kill($child1, SIGHUP); $firstChildIsFree = false; $got2execution = true; } elseif ($secondChildIsFree) { posix_kill($child2, SIGHUP); $secondChildIsFree = false; $got2execution = true; } if ($got2execution) { file_put_contents('db.txt', ''); } else { echo "No free workers\n"; } } //Check signals from child processes if (pcntl_sigtimedwait(array(SIGUSR1, SIGUSR2), $siginfo, 0, 1) != -1) { if ($siginfo['signo'] == SIGUSR1) { echo "Got signal from child1\n"; $firstChildIsFree = true; } elseif ($siginfo['signo'] == SIGUSR2) { echo "Got signal from child2\n"; $secondChildIsFree = true; } } sleep(1); } } else { pcntl_setpriority(5); echo "Child2 waiting for SIGHUP\n"; while (true) { pcntl_sigwaitinfo(array(SIGHUP));
echo "signo === SIGCHLD\n"; var_dump($siginfo['signo'] === SIGCHLD); echo "signo === uid\n"; var_dump($siginfo['uid'] === posix_getuid()); echo "signo === pid\n"; var_dump($siginfo['pid'] === $pid); pcntl_waitpid($pid, $status); set_error_handler(function ($errno, $errstr) { echo "Error triggered\n"; }, E_WARNING); echo "sigprocmask with invalid arguments\n"; /* Valgrind expectedly complains about this: * "sigprocmask: unknown 'how' field 2147483647" * Skip */ if (getenv("USE_ZEND_ALLOC") !== '0') { var_dump(pcntl_sigprocmask(PHP_INT_MAX, array(SIGTERM))); } else { echo "Error triggered\n"; echo "bool(false)\n"; } var_dump(pcntl_sigprocmask(SIG_SETMASK, array(0))); echo "sigwaitinfo with invalid arguments\n"; var_dump(pcntl_sigwaitinfo(array(0))); echo "sigtimedwait with invalid arguments\n"; var_dump(pcntl_sigtimedwait(array(SIGTERM), $signo, PHP_INT_MAX, PHP_INT_MAX)); } else { $siginfo = NULL; pcntl_sigtimedwait(array(SIGINT), $siginfo, 3600, 0); exit; } }
public function sigwait($millisec = 10) { $siginfo = array(); $sec = $millisec * 0.001 > 0 ? $millisec * 0.001 : 0; $nanosec = $millisec * 1000000.0 < 1000000000.0 ? $millisec * 1000000.0 : 0; $signo = pcntl_sigtimedwait(Thread::$signalsno, $siginfo, $sec, $nanosec); if (is_bool($signo)) { return $signo; } if ($signo > 0) { $this->sighandler($signo); return TRUE; } return FALSE; }
/** * Dispatcher responsible for the thread intercommunication and communication with their parent process. * @param bool $useBlocking On true blocks the internal execution until communication data is available for the current dispatched thread otherwise it skips it. * @return void */ public static function dispatch($useBlocking = false) { // {{{ $NULL = null; // prevent any threads to run their own dispatchers if (self::$instancesCreatedEverAArr === null || count(self::$instancesCreatedEverAArr) == 0) { return; } // for checking SIGCHLD child signals informing that a particular "thread" was paused $sigSet = array(SIGCHLD); $sigInfo = array(); // begin the dispatching process foreach (self::$instancesCreatedEverAArr as $instId => &$inst) { // loop through ALL active instances of GPhpCriticalSection foreach ($inst->mastersThreadSpecificData as $threadId => &$specificDataAArr) { // loop though the threads per each instance in GPhpCriticalSection // checking for child signals informing that a thread has exited or was paused while (pcntl_sigtimedwait($sigSet, $sigInfo) == SIGCHLD) { if ($sigInfo['code'] >= 0 && $sigInfo['code'] <= 3) { // child has exited self::$threadsForRemovalAArr[$sigInfo['pid']] = $sigInfo['pid']; } else { if ($sigInfo['code'] == 5) { // stopped (paused) child $specificDataAArr['dispatchPriority'] = 0; // make the dispatch priority lowest } else { if ($sigInfo['code'] == 6) { // resume stopped (paused) child $specificDataAArr['dispatchPriority'] = 1; // increase little bit the priority since we expect interaction with the critical section } } } } $inst->intercomInterlocutorPid =& $specificDataAArr['intercomInterlocutorPid']; if (isset(self::$threadsForRemovalAArr[$inst->intercomInterlocutorPid])) { unset($inst->mastersThreadSpecificData[$threadId]); continue; } $inst->intercomRead =& $specificDataAArr['intercomRead']; $inst->intercomWrite =& $specificDataAArr['intercomWrite']; $inst->dispatchPriority =& $specificDataAArr['dispatchPriority']; if (!$useBlocking && !$inst->intercomRead->isReceivingDataAvailable()) { $inst->dispatchPriority = 0; if ($inst->isIntercomBroken()) { unset($inst->mastersThreadSpecificData[$threadId]); // remove the thread from the dispatching list as soon as we can } continue; } self::dataDispatch($inst, $threadId); $mostPrioritizedThreadId = NULL; if ($inst->dispatchPriority !== 2) { foreach ($inst->mastersThreadSpecificData as $threadId2 => &$specificDataAArr2) { if ($specificDataAArr2['dispatchPriority'] === 2) { $mostPrioritizedThreadId = $threadId2; } } } else { $mostPrioritizedThreadId = $threadId; } if ($mostPrioritizedThreadId !== NULL && $mostPrioritizedThreadId !== $threadId) { $inst->intercomInterlocutorPid =& $inst->mastersThreadSpecificData[$mostPrioritizedThreadId]['intercomInterlocutorPid']; $inst->intercomRead =& $inst->mastersThreadSpecificData[$mostPrioritizedThreadId]['intercomRead']; $inst->intercomWrite =& $inst->mastersThreadSpecificData[$mostPrioritizedThreadId]['intercomWrite']; $inst->dispatchPriority =& $inst->mastersThreadSpecificData[$mostPrioritizedThreadId]['dispatchPriority']; if (!$useBlocking && !$inst->intercomRead->isReceivingDataAvailable()) { $inst->dispatchPriority = 0; if ($inst->isIntercomBroken()) { unset($inst->mastersThreadSpecificData[$mostPrioritizedThreadId]); // remove the thread from the dispatching list as soon as we can } continue; } self::dataDispatch($inst, $threadId); } } // rearrange the threads in the current critical section // instance using their new dispatch priority number // if a lock has already occurred that thread will have the // highest priority uksort($inst->mastersThreadSpecificData, function ($a, $b) use($inst) { if ($inst->mastersThreadSpecificData[$a]['intercomInterlocutorPid'] == $inst->ownerPid) { return -1; } if ($inst->mastersThreadSpecificData[$b]['intercomInterlocutorPid'] == $inst->ownerPid) { return 1; } return $inst->mastersThreadSpecificData[$a]['dispatchPriority'] < $inst->mastersThreadSpecificData[$b]['dispatchPriority']; }); $inst->intercomInterlocutorPid =& $NULL; $inst->intercomRead =& $NULL; $inst->intercomWrite =& $NULL; $inst->dispatchPriority =& $NULL; } // make sure that no terminated threads are left in the internal thread // dispatching list that all instances of GPhpThreadCriticalSection have foreach (self::$instancesCreatedEverAArr as $instId => &$inst) { foreach ($inst->mastersThreadSpecificData as $threadId => &$specificDataAArr) { $inst->intercomInterlocutorPid =& $specificDataAArr['intercomInterlocutorPid']; if (isset(self::$threadsForRemovalAArr[$inst->intercomInterlocutorPid])) { unset($inst->mastersThreadSpecificData[$threadId]); } } $inst->intercomInterlocutorPid =& $NULL; } self::$threadsForRemovalAArr = array(); // rearrange the active instances of GPhpThreadCriticalSection in the // following priority order (the higher the number the bigger the priority): // 2. the instance with the thread that has currently locked the critical section // 1. instances with threads with the highest dispatch priority // 0. instances with the most threads inside $instCrtdEver =& self::$instancesCreatedEverAArr; uksort($instCrtdEver, function ($a, $b) use($instCrtdEver) { // the locker thread is with highest priority if ($instCrtdEver[$a]->mastersThreadSpecificData['intercomInterlocutorPid'] == $instCrtdEver[$a]->ownerPid) { return -1; } if ($instCrtdEver[$b]->mastersThreadSpecificData['intercomInterlocutorPid'] == $instCrtdEver[$b]->ownerPid) { return 1; } // deal with the case of critical sections with no threads if (!empty($instCrtdEver[$a]->mastersThreadSpecificData) && empty($instCrtdEver[$b]->mastersThreadSpecificData)) { return -1; } else { if (empty($instCrtdEver[$a]->mastersThreadSpecificData) && !empty($instCrtdEver[$b]->mastersThreadSpecificData)) { return 1; } else { if (empty($instCrtdEver[$b]->mastersThreadSpecificData) && empty($instCrtdEver[$b]->mastersThreadSpecificData)) { return 0; } } } // a // gather the thread dispatch priorities for the compared critical sections $dispPriorTableA = array(); // priority value => occurrences count $dispPriorTableB = array(); // priority value => occurrences count foreach ($instCrtdEver[$a]->mastersThreadSpecificData as $thrdSpecificData) { @($dispPriorTableA[$thrdSpecificData['dispatchPriority']] += 1); } foreach ($instCrtdEver[$b]->mastersThreadSpecificData as $thrdSpecificData) { @($dispPriorTableB[$thrdSpecificData['dispatchPriority']] += 1); } // both critical sections have threads // make the tables to have the same amount of keys (rows) foreach ($dispPriorTableA as $key => $value) { @($dispPriorTableB[$key] = $dispPriorTableB[$key]); } // this is done on purpose foreach ($dispPriorTableB as $key => $value) { @($dispPriorTableA[$key] = $dispPriorTableA[$key]); } ksort($dispPriorTableA); ksort($dispPriorTableB); // compare the tables while taking into account the priority // and the thread count that have it per critical section foreach ($dispPriorTableA as $key => $value) { if ($value < $dispPriorTableB[$key]) { return 1; } else { if ($value > $dispPriorTableB[$key]) { return -1; } } // a } return 0; // a }); }
<?php pcntl_signal(SIGALRM, function () { }); var_dump(pcntl_alarm()); pcntl_alarm(0); var_dump(pcntl_alarm(60)); var_dump(pcntl_alarm(1) > 0); $siginfo = array(); var_dump(pcntl_sigtimedwait(array(SIGALRM), $siginfo, 2) === SIGALRM);
/** * 主进程主循环 主要是监听子进程退出、服务终止、平滑重启信号 * @return void */ public static function loop() { $siginfo = array(); while (1) { @pcntl_sigtimedwait(array(SIGCHLD), $siginfo, 1); // 初始化任务系统 Lib\Task::tick(); // 检查是否有进程退出 self::checkWorkerExit(); // 触发信号处理 pcntl_signal_dispatch(); } }
/** * Kill all children with the supplied signal and wait for them to exit * * @param int $signal * @return bool */ protected function killChildren($signal = SIGKILL) { // Signal all processes to exit foreach ($this->processPIDs as $pid => $name) { \posix_kill($pid, $signal); } for ($i = 0; $i < 3000; $i++) { if (\count($this->processPIDs) === 0) { return true; } $signalInfo = null; if (-1 !== \pcntl_sigtimedwait([SIGCHLD], $signalInfo, 0, 100000000)) { $this->cleanupChild($signalInfo, true); } } // Forcefully destroy all remaining processes foreach ($this->processPIDs as $pid => $name) { \posix_kill($pid, SIGKILL); } return false; }