function minecraft_command($signal) { global $_CONFIG, $_STATE; switch ($signal) { case SIGHUP: logmsg("SIGHUP - reload"); $command = 'reload'; break; case SIGUSR1: logmsg("SIGUSR1 - save-on"); $command = 'save-on'; break; case SIGUSR2: logmsg("SIGUSR2 - save-off"); $command = 'save-off'; break; case SIGALRM: logmsg("SIGALRM - save-all"); $command = 'save-all'; if (isset($_CONFIG['AlarmInterval'])) { pcntl_alarm($_CONFIG['AlarmInterval']); } break; } fwrite($_STATE['Descriptors'][0], $command . PHP_EOL); fflush($_STATE['Descriptors'][0]); }
private function sleepUntilThereIsSomethingInteresting($timeLimit, $child) { pcntl_signal(SIGALRM, [$this, "alarm"], true); pcntl_alarm($timeLimit); pcntl_waitpid($child, $status); //pcntl_signal_dispatch(); }
public function beat() { if (false === $this->stopped) { pcntl_alarm((int) 1); $this->tick(); } }
/** * Signal handler * * @param int $signalNumber * @return void */ public function signalHandler($signalNumber) { echo 'Handling signal: #' . $signalNumber . PHP_EOL; global $consumer; switch ($signalNumber) { case SIGTERM: // 15 : supervisor default stop // 15 : supervisor default stop case SIGQUIT: // 3 : kill -s QUIT $consumer->stopHard(); break; case SIGINT: // 2 : ctrl+c $consumer->stop(); break; case SIGHUP: // 1 : kill -s HUP $consumer->restart(); break; case SIGUSR1: // 10 : kill -s USR1 // send an alarm in 1 second pcntl_alarm(1); break; case SIGUSR2: // 12 : kill -s USR2 // send an alarm in 10 seconds pcntl_alarm(10); break; default: break; } return; }
protected function setSigAlarm() { pcntl_signal(SIGALRM, array(&$this, 'sigAlarmCallback')); $sec = $this->getSigAlarmTimeout(); if ($sec > -1) { pcntl_alarm($this->getSigAlarmTimeout()); } }
/** * Loads to configuration from the daemon options and installs signal * handlers. * * @param DaemonOptions $daemonOptions */ private function setupDaemon(DaemonOptions $daemonOptions) { $this->requestCount = 0; $this->requestLimit = $daemonOptions->getOption(DaemonOptions::REQUEST_LIMIT); $this->memoryLimit = $daemonOptions->getOption(DaemonOptions::MEMORY_LIMIT); $timeLimit = $daemonOptions->getOption(DaemonOptions::TIME_LIMIT); if (DaemonOptions::NO_LIMIT !== $timeLimit) { pcntl_alarm($timeLimit); } $this->installSignalHandlers(); }
/** * Test stream_select signal interrupt doesn't trigger \RunTime exception. */ public function testStreamSelectSignalInterrupt() { $address = 'tcp://localhost:7000'; $serverSocket = stream_socket_server($address); $connectionPool = new StreamSocketConnectionPool($serverSocket); $alarmCalled = false; declare (ticks=1); pcntl_signal(SIGALRM, function () use(&$alarmCalled) { $alarmCalled = true; }); pcntl_alarm(1); $connectionPool->getReadableConnections(2); $this->assertTrue($alarmCalled); }
public function setUp() { // Make this process a session leader so we can send signals // to this job as a whole (including any subprocesses such as spawned by Symfony). posix_setsid(); if (function_exists('pcntl_alarm') && function_exists('pcntl_signal')) { if (!empty($this->args['sigFile'])) { echo sprintf('[-] Signal file requested, polling "%s".' . PHP_EOL, $this->args['sigFile']); declare (ticks=1); pcntl_signal(SIGALRM, [$this, 'alarmHandler']); pcntl_alarm(1); } } $this->updateStatus(DNDeployment::TR_DEPLOY); chdir(BASE_PATH); }
function pleac_Timing_Out_an_Operation() { declare (ticks=1); $aborted = false; function handle_alarm($signal) { global $aborted; $aborted = true; } pcntl_signal(SIGALRM, 'handle_alarm'); pcntl_alarm(3600); // long-time operations here pcntl_alarm(0); if ($aborted) { // timed out - do what you will here } }
/** * Invokes a callable and raises an exception when the execution does not * finish before the specified timeout. * * @param callable $callable * @param array $arguments * @param int $timeout in seconds * @return mixed * @throws InvalidArgumentException */ public function invoke($callable, array $arguments, $timeout) { if (!is_callable($callable)) { throw new InvalidArgumentException(); } if (!is_integer($timeout)) { throw new InvalidArgumentException(); } pcntl_signal(SIGALRM, array($this, 'callback'), TRUE); pcntl_alarm($timeout); $this->timeout = $timeout; try { $result = call_user_func_array($callable, $arguments); } catch (Exception $e) { pcntl_alarm(0); throw $e; } pcntl_alarm(0); return $result; }
/** * Executes a callable until a timeout is reached or the callable returns `true`. * * @param Callable $callable The callable to execute. * @param integer $timeout The timeout value. * @return mixed */ public static function run($callable, $timeout = 0) { if (!is_callable($callable)) { throw new InvalidArgumentException(); } $timeout = (int) $timeout; if (!function_exists('pcntl_signal')) { throw new Exception("PCNTL threading is not supported on your OS."); } pcntl_signal(SIGALRM, function ($signal) use($timeout) { throw new TimeoutException("Timeout reached, execution aborted after {$timeout} second(s)."); }, true); pcntl_alarm($timeout); $result = null; try { $result = $callable(); } catch (Exception $e) { throw $e; } finally { pcntl_alarm(0); } return $result; }
/** * * 添加一个任务 * * @param int $time_long 多长时间运行一次 单位秒 * @param callback $func 任务运行的函数或方法 * @param mix $args 任务运行的函数或方法使用的参数 * @return void */ public static function add($time_long, $func, $args = array(), $persistent = true) { if ($time_long <= 0) { return false; } if (!is_callable($func)) { if (class_exists('\\Man\\Core\\Lib\\Log')) { \Man\Core\Lib\Log::add(var_export($func, true) . "not callable\n"); } return false; } // 有任务时才出发计时器 if (empty(self::$tasks)) { pcntl_alarm(1); } $time_now = time(); $run_time = $time_now + $time_long; if (!isset(self::$tasks[$run_time])) { self::$tasks[$run_time] = array(); } self::$tasks[$run_time][] = array($func, $args, $persistent, $time_long); return true; }
/** * 运行守护进程,监控有变化的日志文件。 * * @see ZtChart_Model_Monitor_Abstract::daemon() */ public function run() { if (0 == ($pid = pcntl_fork())) { // 子进程负责处理当前日志 try { $this->tail($this->_console->getLogPaths()); } catch (ZtChart_Model_Monitor_Exception $e) { $this->_logger->err($e->getMessage()); exit(1); } } else { if (0 < $pid) { pcntl_setpriority(-1); // 父进程负责把子进产生的数据写入数据库 if (pcntl_sigprocmask(SIG_BLOCK, array(SIGCHLD, SIGALRM, SIGINT, SIGTERM))) { $interval = 10; // 10秒写一次 pcntl_alarm($interval); while ($signo = pcntl_sigwaitinfo(array(SIGCHLD, SIGALRM, SIGINT, SIGTERM))) { if (SIGALRM == $signo) { pcntl_alarm($interval); } try { $this->extract($interval); } catch (ZtChart_Model_Monitor_Exception $e) { posix_kill($pid, 9); exit(1); } if (SIGCHLD == $signo || SIGINT == $signo || SIGTERM == $signo) { break; } } } } } exit(0); }
/** * Remove all timers. * @return void */ public static function delAll() { self::$_tasks = array(); pcntl_alarm(0); if (self::$_event) { self::$_event->clearAllTimer(); } }
/** * 处理内部通讯收到的数据 * @param event_buffer $event_buffer * @param int $fd * @return void */ public function recvInnerTcp($connection, $flag, $fd = null) { $this->currentDealFd = $fd; $buffer = stream_socket_recvfrom($connection, $this->recvBuffers[$fd]['remain_len']); // 出错了 if ('' == $buffer && '' == ($buffer = fread($connection, $this->recvBuffers[$fd]['remain_len']))) { // 判断是否是链接断开 if (!feof($connection)) { return; } // 如果该链接对应的buffer有数据,说明发生错误 if (!empty($this->recvBuffers[$fd]['buf'])) { $this->statusInfo['send_fail']++; } // 关闭链接 $this->closeInnerClient($fd); $this->notice("CLIENT:" . $this->getRemoteIp() . " CLOSE INNER_CONNECTION\n"); if ($this->workerStatus == self::STATUS_SHUTDOWN) { $this->stop(); } return; } $this->recvBuffers[$fd]['buf'] .= $buffer; $remain_len = $this->dealInnerInput($this->recvBuffers[$fd]['buf']); // 包接收完毕 if (0 === $remain_len) { // 内部通讯业务处理 $this->innerDealProcess($this->recvBuffers[$fd]['buf']); $this->recvBuffers[$fd] = array('buf' => '', 'remain_len' => GatewayProtocol::HEAD_LEN); } else { if (false === $remain_len) { // 出错 $this->statusInfo['packet_err']++; $this->notice("INNER_PACKET_ERROR and CLOSE_INNER_CONNECTION\nCLIENT_IP:" . $this->getRemoteIp() . "\nBUFFER:[" . bin2hex($this->recvBuffers[$fd]['buf']) . "]\n"); $this->closeInnerClient($fd); } else { $this->recvBuffers[$fd]['remain_len'] = $remain_len; } } // 检查是否是关闭状态或者是否到达请求上限 if ($this->workerStatus == self::STATUS_SHUTDOWN) { // 停止服务 $this->stop(); // EXIT_WAIT_TIME秒后退出进程 pcntl_alarm(self::EXIT_WAIT_TIME); } }
/** * Test seeding * * @return void */ public function testSeeding() { $this->torrent_file = $this->createTorrentFile(); $this->download_destination = $this->createDownloadDestination(); $this->seed_server_pid = $this->startSeedServer(); $this->torrent_client_pid = $this->startTorrentClient(); // We don't want to wait forever. $self = $this; pcntl_signal(SIGALRM, function () use($self) { $self->fail('Test timed out.'); }); pcntl_alarm(self::TEST_TIMEOUT); $pid_exit = pcntl_wait($status); switch ($pid_exit) { case -1: $this->fail('Error in child processes.'); break; case $this->seed_server_pid: unset($this->seed_server_pid); $this->fail('Seed server exited.'); break; case $this->torrent_client_pid: unset($this->torrent_client_pid); break; } $download_path = $this->download_destination . '/' . self::FILE_TO_DOWNLOAD; $this->assertFileExists($download_path); $downloaded_hash = sha1_file($download_path); $expected_hash = sha1_file(dirname(__FILE__) . '/../Fixtures/' . self::FILE_TO_DOWNLOAD); $this->assertEquals($expected_hash, $downloaded_hash); }
public function forExactly($duration) { // what are we doing? $log = usingLog()->startAction("run for exactly '{$duration}'"); // remember the duration // // the $action callback can then make use of it $this->duration = $duration; // convert the duration into seconds $interval = new DateInterval($duration); $seconds = $interval->getTotalSeconds(); // set the alarm pcntl_signal(SIGALRM, array($this, "handleSigAlarm"), FALSE); $log->addStep("setting SIGALRM for '{$seconds}' seconds", function () use($seconds) { pcntl_alarm($seconds); }); declare (ticks=1); $callback = $this->action; $returnVal = $callback($this); // all done $log->endAction(); return $returnVal; }
/** * Обработка сигналов Мастером. * * @param int $signo Код сигнала * @param mixed $pid Идентификатор процесса для обработки * @param mixed $status Статус завершения процесса * @throws \Ganzal\Lulz\Pinger\Assets\Exceptions\Master_Bad_Signal_Exception * @return void * @access protected * @static */ protected static function masterSignalHandler($signo, $pid = null, $status = null) { DEBUG && printf("Master::masterSignalHandler(%s, %s, %s): begin\n", var_export($signo, true), var_export($pid, true), var_export($status, true)); switch ($signo) { case SIGHUP: case SIGINT: case SIGQUIT: case SIGTERM: case SIGABRT: DEBUG && (print "Master::masterSignalHandler(): master_loop = false\n"); static::$master_loop = false; pcntl_signal_dispatch(); break; case SIGALRM: case SIGCHLD: case SIGCLD: // Пид не указан - сигнал от системы. Уточняем кто умер. if (!$pid) { $pid = pcntl_waitpid(-1, $status, WNOHANG); } // Дожидаемся окончания очереди умерших. while ($pid > 0) { if ($pid && isset(static::$jobs[$pid])) { DEBUG && (print "Master::masterSignalHandler(): ((**))\n"); $exitCode = pcntl_wexitstatus($status); if ($exitCode != 0) { DEBUG && printf("Master::masterSignalHandler(): %d exited with status %d\n", $pid, $exitCode); } // этот демон не требует возврата слотов в пул //array_push(static::$pid_slots, static::$jobs[$pid]); // удаление завершенного процесса из списка unset(static::$jobs[$pid]); } elseif ($pid) { DEBUG && (print "Master::masterSignalHandler(): (())\n"); // пид указан, но не в нашем списке детишек! // запишем в очередь сигналов, вдруг чо DEBUG && printf("Master::masterSignalHandler(): adding %d to the signal queue \n", $pid); static::$sig_queue[$pid] = $status; } $pid = pcntl_waitpid(-1, $status, WNOHANG); } pcntl_alarm(10); break; default: throw new Exceptions\Master_Bad_Signal_Exception($signo); } // switch($signo) DEBUG && (print "Master::masterSignalHandler(): end\n\n"); }
/** * Register the worker timeout handler (PHP 7.1+). * * @param \Illuminate\Contracts\Queue\Job|null $job * @param WorkerOptions $options * @return void */ protected function registerTimeoutHandler($job, WorkerOptions $options) { if (version_compare(PHP_VERSION, '7.1.0') < 0 || !extension_loaded('pcntl')) { return; } $timeout = $job && !is_null($job->timeout()) ? $job->timeout() : $options->timeout; pcntl_async_signals(true); pcntl_signal(SIGALRM, function () { $this->exceptions->report(new TimeoutException('A queue worker timed out while processing a job.')); exit(1); }); pcntl_alarm($timeout + $options->sleep); }
/** * 处理受到的数据 * @param event_buffer $event_buffer * @param int $fd * @return void */ public function dealInputBase($connection, $length, $buffer, $fd = null) { $this->currentDealFd = $fd; // 出错了 if ($length == 0) { if (feof($connection)) { // 客户端提前断开链接 $this->statusInfo['client_close']++; } else { // 超时了 $this->statusInfo['recv_timeout']++; } $this->closeClient($fd); if ($this->workerStatus == self::STATUS_SHUTDOWN) { $this->stopServe(); } return; } if (isset($this->recvBuffers[$fd])) { $buffer = $this->recvBuffers[$fd] . $buffer; } $remain_len = $this->dealInput($buffer); // 包接收完毕 if (0 === $remain_len) { // 逻辑超时处理,逻辑只能执行xxs,xxs后超时放弃当前请求处理下一个请求 pcntl_alarm(ceil($this->processTimeout / 1000)); // 执行处理 try { declare (ticks=1); // 业务处理 $this->dealProcess($buffer); // 关闭闹钟 pcntl_alarm(0); } catch (Exception $e) { // 关闭闹钟 pcntl_alarm(0); if ($e->getCode() != self::CODE_PROCESS_TIMEOUT) { $this->notice($e->getMessage() . ":\n" . $e->getTraceAsString()); $this->statusInfo['throw_exception']++; $this->sendToClient($e->getMessage()); } } // 是否是长连接 if ($this->isPersistentConnection) { // 清空缓冲buffer unset($this->recvBuffers[$fd]); } else { // 关闭链接 $this->closeClient($fd); } } else { if (false === $remain_len) { // 出错 $this->statusInfo['packet_err']++; $this->sendToClient('packet_err:' . $buffer); $this->notice('packet_err:' . $buffer); $this->closeClient($fd); } else { $this->recvBuffers[$fd] = $buffer; } } // 检查是否到达请求上限或者服务是否是关闭状态 if ($this->statusInfo['total_request'] >= $this->maxRequests || $this->workerStatus == self::STATUS_SHUTDOWN) { // 停止服务 $this->stopServe(); // 5秒后退出进程 pcntl_alarm(self::EXIT_WAIT_TIME); } }
static function setAlarm($f = SIG_IGN, $seconds = 0) { pcntl_signal(SIGALRM, $f); return pcntl_alarm($seconds); }
/** * 设置server信号处理函数 * @param null $null * @param int $signal */ public function signalHandler($signal, $null = null, $null = null) { switch ($signal) { // 时钟处理函数 case SIGALRM: // 停止服务后EXIT_WAIT_TIME秒还没退出则强制退出 if ($this->workerStatus == self::STATUS_SHUTDOWN) { exit(0); } break; // 停止该进程 // 停止该进程 case SIGINT: $this->stop(); // EXIT_WAIT_TIME秒后退出进程 pcntl_alarm(self::EXIT_WAIT_TIME); break; // 平滑重启 // 平滑重启 case SIGHUP: $this->onReload(); // 如果配置了no_reload则不重启该进程 if (\Man\Core\Lib\Config::get($this->workerName . '.no_reload')) { return; } $this->stop(); // EXIT_WAIT_TIME秒后退出进程 pcntl_alarm(self::EXIT_WAIT_TIME); break; // 报告进程状态 // 报告进程状态 case SIGUSR1: $this->writeStatusToQueue(); break; // 报告进程使用的php文件 // 报告进程使用的php文件 case SIGUSR2: $this->writeFilesListToQueue(); break; // FileMonitor检测到终端已经关闭,向此进程发送SIGTTOU信号,关闭此进程的标准输入输出 // FileMonitor检测到终端已经关闭,向此进程发送SIGTTOU信号,关闭此进程的标准输入输出 case SIGTTOU: $this->resetFd(); break; } }
function commandStart() { global $_CONFIG, $_STATE; // Set up the descriptors for the process $descriptors = array(0 => array('pipe', 'r'), 1 => array('file', $_CONFIG['Stdout'], 'a'), 2 => array('file', $_CONFIG['Stderr'], 'a')); // Set the current working directory $cwd = $_CONFIG['WorkingDirectory']; // Set up the environment variables $env = $_CONFIG['Environment']; // Set the effective uid/gid so we spawn the process as the correct user. posix_setegid($_CONFIG['GID']); posix_seteuid($_CONFIG['UID']); $_STATE['ProcessHandle'] = proc_open($_CONFIG['Command'], $descriptors, $_STATE['Descriptors'], $cwd, $env); if (!isset($_STATE['ProcessHandle']) || !is_resource($_STATE['ProcessHandle'])) { throw new Exception("Could not start command."); } // Reset the effective uid/gid posix_setegid(0); posix_seteuid(0); $_STATE['Status'] = proc_get_status($_STATE['ProcessHandle']); if (isset($_CONFIG['Command_Pidfile'])) { file_put_contents($_CONFIG['Command_Pidfile'], $_STATE['Status']['pid']); } if (isset($_CONFIG['AlarmInterval'])) { pcntl_alarm($_CONFIG['AlarmInterval']); } }
/** * @return PromiseInterface */ protected function startup() { pcntl_alarm(30); $this->registerSigHandlers(); $this->pruneDeadWorkers(); return $this->registerWorker(); }
/** * Выполняет работу воркера * * @return void */ private function worker() { $this->iPID = getmypid(); $this->aMeta['time']['start'] = round(microtime(true), 2); // ключ для вызова в функции пользовательской $this->mInstanceData = (bool) $this->aWorkersData ? $this->aWorkersData[$this->iNumberWorker - 1] : null; if (function_exists('cli_set_process_title') && isset($_SERVER['TERM_PROGRAM']) && $_SERVER['TERM_PROGRAM'] !== 'Apple_Terminal') { cli_set_process_title($this->sFilesBasename . '.' . $this->sInstanceName); } $this->setStreamsIO($this->sInstanceName); if ($this->bVerbose) { $this->writeInLog($this->rVerboseStream, 'PID: ' . $this->iPID); } // установка приоритета на процесс if ($this->iWorkerPriority) { if ($this->bVerbose) { $this->writeInLog($this->rVerboseStream, 'Set process priority equal ' . $this->iWorkerPriority); } $this->setPriority($this->iWorkerPriority); } // лимит на время исполнения дочернего процесса if ($this->iMaxExecutionTimeInSeconds) { if ($this->bVerbose) { $this->writeInLog($this->rVerboseStream, 'Set process lifetime ' . $this->iMaxExecutionTimeInSeconds . ' seconds'); } // считает время работы скрипта set_time_limit($this->iMaxExecutionTimeInSeconds); // считает время исполнения вне скрипта (потоки, коннекты, sleep, etc) pcntl_alarm($this->iMaxExecutionTimeInSeconds); } if ($this->bVerbose) { $this->writeInLog($this->rVerboseStream, 'Started'); } // перехватываем вывод ob_start(); // запускаем функцию if ($this->cExecutingFunction) { call_user_func($this->cExecutingFunction, $this->mInstanceData); // успешно выходим exit(0); } }
/** * Entry-point for Erebot. * * \return * This method never returns. * Instead, the program exits with an appropriate * return code when Erebot is stopped. */ public static function run() { // Apply patches. \Erebot\Patches::patch(); // Load the configuration for the Dependency Injection Container. $dic = new \Symfony\Component\DependencyInjection\ContainerBuilder(); $dic->setParameter('Erebot.src_dir', __DIR__); $loader = new \Symfony\Component\DependencyInjection\Loader\XmlFileLoader($dic, new \Symfony\Component\Config\FileLocator(getcwd())); $dicConfig = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'defaults.xml'; $dicCwdConfig = getcwd() . DIRECTORY_SEPARATOR . 'defaults.xml'; if (!strncasecmp(__FILE__, 'phar://', 7)) { if (!file_exists($dicCwdConfig)) { copy($dicConfig, $dicCwdConfig); } $dicConfig = $dicCwdConfig; } elseif (file_exists($dicCwdConfig)) { $dicConfig = $dicCwdConfig; } $loader->load($dicConfig); // Determine availability of PHP extensions // needed by some of the command-line options. $hasPosix = in_array('posix', get_loaded_extensions()); $hasPcntl = in_array('pcntl', get_loaded_extensions()); $logger = $dic->get('logging'); $localeGetter = $dic->getParameter('i18n.default_getter'); $coreTranslatorCls = $dic->getParameter('core.classes.i18n'); $translator = new $coreTranslatorCls("Erebot\\Core"); $categories = array('LC_MESSAGES', 'LC_MONETARY', 'LC_TIME', 'LC_NUMERIC'); foreach ($categories as $category) { $locales = call_user_func($localeGetter); $locales = empty($locales) ? array() : array($locales); $localeSources = array('LANGUAGE' => true, 'LC_ALL' => false, $category => false, 'LANG' => false); foreach ($localeSources as $source => $multiple) { if (!isset($_SERVER[$source])) { continue; } if ($multiple) { $locales = explode(':', $_SERVER[$source]); } else { $locales = array($_SERVER[$source]); } break; } $translator->setLocale($translator->nameToCategory($category), $locales); } // Also, include some information about the version // of currently loaded PHAR modules, if any. $version = 'dev-master'; if (!strncmp(__FILE__, 'phar://', 7)) { $phar = new \Phar(\Phar::running(true)); $md = $phar->getMetadata(); $version = $md['version']; } if (defined('Erebot_PHARS')) { $phars = unserialize(Erebot_PHARS); ksort($phars); foreach ($phars as $module => $metadata) { if (strncasecmp($module, 'Erebot_Module_', 14)) { continue; } $version .= "\n with {$module} version {$metadata['version']}"; } } \Console_CommandLine::registerAction('StoreProxy', '\\Erebot\\Console\\StoreProxyAction'); $parser = new \Console_CommandLine(array('name' => 'Erebot', 'description' => $translator->gettext('A modular IRC bot written in PHP'), 'version' => $version, 'add_help_option' => true, 'add_version_option' => true, 'force_posix' => false)); $parser->accept(new \Erebot\Console\MessageProvider()); $parser->renderer->options_on_different_lines = true; $defaultConfigFile = getcwd() . DIRECTORY_SEPARATOR . 'Erebot.xml'; $parser->addOption('config', array('short_name' => '-c', 'long_name' => '--config', 'description' => $translator->gettext('Path to the configuration file to use instead ' . 'of "Erebot.xml", relative to the current ' . 'directory.'), 'help_name' => 'FILE', 'action' => 'StoreString', 'default' => $defaultConfigFile)); $parser->addOption('daemon', array('short_name' => '-d', 'long_name' => '--daemon', 'description' => $translator->gettext('Run the bot in the background (daemon).' . ' [requires the POSIX and pcntl extensions]'), 'action' => 'StoreTrue')); $noDaemon = new \Erebot\Console\ParallelOption('no_daemon', array('short_name' => '-n', 'long_name' => '--no-daemon', 'description' => $translator->gettext('Do not run the bot in the background. ' . 'This is the default, unless the -d option ' . 'is used or the bot is configured otherwise.'), 'action' => 'StoreProxy', 'action_params' => array('option' => 'daemon'))); $parser->addOption($noDaemon); $parser->addOption('pidfile', array('short_name' => '-p', 'long_name' => '--pidfile', 'description' => $translator->gettext("Store the bot's PID in this file."), 'help_name' => 'FILE', 'action' => 'StoreString', 'default' => null)); $parser->addOption('group', array('short_name' => '-g', 'long_name' => '--group', 'description' => $translator->gettext('Set group identity to this GID/group during ' . 'startup. The default is to NOT change group ' . 'identity, unless configured otherwise.' . ' [requires the POSIX extension]'), 'help_name' => 'GROUP/GID', 'action' => 'StoreString', 'default' => null)); $parser->addOption('user', array('short_name' => '-u', 'long_name' => '--user', 'description' => $translator->gettext('Set user identity to this UID/username during ' . 'startup. The default is to NOT change user ' . 'identity, unless configured otherwise.' . ' [requires the POSIX extension]'), 'help_name' => 'USER/UID', 'action' => 'StoreString', 'default' => null)); try { $parsed = $parser->parse(); } catch (\Exception $exc) { $parser->displayError($exc->getMessage()); exit(1); } // Parse the configuration file. $config = new \Erebot\Config\Main($parsed->options['config'], \Erebot\Config\Main::LOAD_FROM_FILE, $translator); $coreCls = $dic->getParameter('core.classes.core'); $bot = new $coreCls($config, $translator); $dic->set('bot', $bot); // Use values from the XML configuration file // if there is no override from the command line. $overrides = array('daemon' => 'mustDaemonize', 'group' => 'getGroupIdentity', 'user' => 'getUserIdentity', 'pidfile' => 'getPidfile'); foreach ($overrides as $option => $func) { if ($parsed->options[$option] === null) { $parsed->options[$option] = $config->{$func}(); } } /* Handle daemonization. * See also: * - http://www.itp.uzh.ch/~dpotter/howto/daemonize * - http://andytson.com/blog/2010/05/daemonising-a-php-cli-script */ if ($parsed->options['daemon']) { if (!$hasPosix) { $logger->error($translator->gettext('The posix extension is required in order ' . 'to start the bot in the background')); exit(1); } if (!$hasPcntl) { $logger->error($translator->gettext('The pcntl extension is required in order ' . 'to start the bot in the background')); exit(1); } foreach (array('SIGCHLD', 'SIGUSR1', 'SIGALRM') as $signal) { if (defined($signal)) { pcntl_signal(constant($signal), array(__CLASS__, 'startupSighandler')); } } $logger->info($translator->gettext('Starting the bot in the background...')); $pid = pcntl_fork(); if ($pid < 0) { $logger->error($translator->gettext('Could not start in the background (unable to fork)')); exit(1); } if ($pid > 0) { pcntl_wait($dummy, WUNTRACED); pcntl_alarm(2); pcntl_signal_dispatch(); exit(1); } $parent = posix_getppid(); // Ignore some of the signals. foreach (array('SIGTSTP', 'SIGTOU', 'SIGTIN', 'SIGHUP') as $signal) { if (defined($signal)) { pcntl_signal(constant($signal), SIG_IGN); } } // Restore the signal handlers we messed with. foreach (array('SIGCHLD', 'SIGUSR1', 'SIGALRM') as $signal) { if (defined($signal)) { pcntl_signal(constant($signal), SIG_DFL); } } umask(0); if (umask() != 0) { $logger->warning($translator->gettext('Could not change umask')); } if (posix_setsid() == -1) { $logger->error($translator->gettext('Could not start in the background (unable to setsid)')); exit(1); } // Prevent the child from ever acquiring a controlling terminal. // Not required under Linux, but required by at least System V. $pid = pcntl_fork(); if ($pid < 0) { $logger->error($translator->gettext('Could not start in the background (unable to fork)')); exit(1); } if ($pid > 0) { exit(0); } // Avoid locking up the current directory. if (!chdir(DIRECTORY_SEPARATOR)) { $logger->error($translator->gettext('Could not chdir to "%(path)s"'), array('path' => DIRECTORY_SEPARATOR)); } // Explicitly close the magic stream-constants (just in case). foreach (array('STDIN', 'STDOUT', 'STDERR') as $stream) { if (defined($stream)) { fclose(constant($stream)); } } // Re-open them with the system's blackhole. /** * \todo * should be made portable, but the requirement on the POSIX * extension prevents this, so this is okay for now. */ $stdin = fopen('/dev/null', 'r'); $stdout = fopen('/dev/null', 'w'); $stderr = fopen('/dev/null', 'w'); if (defined('SIGUSR1')) { posix_kill($parent, SIGUSR1); } $logger->info($translator->gettext('Successfully started in the background')); } try { /// @TODO: Check the interface or something like that. $identd = $dic->get('identd'); } catch (\InvalidArgumentException $e) { $identd = null; } try { /// @TODO: Check the interface or something like that. $prompt = $dic->get('prompt'); } catch (\InvalidArgumentException $e) { $prompt = null; } // Change group identity if necessary. if ($parsed->options['group'] !== null && $parsed->options['group'] != '') { if (!$hasPosix) { $logger->warning($translator->gettext('The posix extension is needed in order ' . 'to change group identity.')); } elseif (posix_getuid() !== 0) { $logger->warning($translator->gettext('Only root can change group identity! ' . 'Your current UID is %(uid)d'), array('uid' => posix_getuid())); } else { if (ctype_digit($parsed->options['group'])) { $info = posix_getgrgid((int) $parsed->options['group']); } else { $info = posix_getgrnam($parsed->options['group']); } if ($info === false) { $logger->error($translator->gettext('No such group "%(group)s"'), array('group' => $parsed->options['group'])); exit(1); } if (!posix_setgid($info['gid'])) { $logger->error($translator->gettext('Could not set group identity ' . 'to "%(name)s" (%(id)d)'), array('id' => $info['gid'], 'name' => $info['name'])); exit(1); } $logger->debug($translator->gettext('Successfully changed group identity ' . 'to "%(name)s" (%(id)d)'), array('name' => $info['name'], 'id' => $info['gid'])); } } // Change user identity if necessary. if ($parsed->options['user'] !== null || $parsed->options['user'] != '') { if (!$hasPosix) { $logger->warning($translator->gettext('The posix extension is needed in order ' . 'to change user identity.')); } elseif (posix_getuid() !== 0) { $logger->warning($translator->gettext('Only root can change user identity! ' . 'Your current UID is %(uid)d'), array('uid' => posix_getuid())); } else { if (ctype_digit($parsed->options['user'])) { $info = posix_getpwuid((int) $parsed->options['user']); } else { $info = posix_getpwnam($parsed->options['user']); } if ($info === false) { $logger->error($translator->gettext('No such user "%(user)s"'), array('user' => $parsed->options['user'])); exit(1); } if (!posix_setuid($info['uid'])) { $logger->error($translator->gettext('Could not set user identity ' . 'to "%(name)s" (%(id)d)'), array('name' => $info['name'], 'id' => $info['uid'])); exit(1); } $logger->debug($translator->gettext('Successfully changed user identity ' . 'to "%(name)s" (%(id)d)'), array('name' => $info['name'], 'id' => $info['uid'])); } } // Write new pidfile. if ($parsed->options['pidfile'] !== null && $parsed->options['pidfile'] != '') { $pid = @file_get_contents($parsed->options['pidfile']); // If the file already existed, the bot may already be started // or it may contain data not related to Erebot at all. if ($pid !== false) { $pid = (int) rtrim($pid); if (!$pid) { $logger->error($translator->gettext('The pidfile (%(pidfile)s) contained garbage. ' . 'Exiting'), array('pidfile' => $parsed->options['pidfile'])); exit(1); } else { posix_kill($pid, 0); $res = posix_errno(); switch ($res) { case 0: // No error. $logger->error($translator->gettext('Erebot is already running ' . 'with PID %(pid)d'), array('pid' => $pid)); exit(1); case 3: // ESRCH. $logger->warning($translator->gettext('Found stalled PID %(pid)d in pidfile ' . '"%(pidfile)s". Removing it'), array('pidfile' => $parsed->options['pidfile'], 'pid' => $pid)); @unlink($parsed->options['pidfile']); break; case 1: // EPERM. $logger->error($translator->gettext('Found another program\'s PID %(pid)d in ' . 'pidfile "%(pidfile)s". Exiting'), array('pidfile' => $parsed->options['pidfile'], 'pid' => $pid)); exit(1); default: $logger->error($translator->gettext('Unknown error while checking for ' . 'the existence of another running ' . 'instance of Erebot (%(error)s)'), array('error' => posix_get_last_error())); exit(1); } } } $pidfile = fopen($parsed->options['pidfile'], 'wt'); flock($pidfile, LOCK_EX | LOCK_NB, $wouldBlock); if ($wouldBlock) { $logger->error($translator->gettext('Could not lock pidfile (%(pidfile)s). ' . 'Is the bot already running?'), array('pidfile' => $parsed->options['pidfile'])); exit(1); } $pid = sprintf("%u\n", getmypid()); $res = fwrite($pidfile, $pid); if ($res !== strlen($pid)) { $logger->error($translator->gettext('Unable to write PID to pidfile (%(pidfile)s)'), array('pidfile' => $parsed->options['pidfile'])); exit(1); } $logger->debug($translator->gettext('PID (%(pid)d) written into %(pidfile)s'), array('pidfile' => $parsed->options['pidfile'], 'pid' => getmypid())); // Register a callback to remove the pidfile upon exit. register_shutdown_function(array(__CLASS__, 'cleanupPidfile'), $pidfile, $parsed->options['pidfile']); } // Display a desperate warning when run as user root. if ($hasPosix && posix_getuid() === 0) { $logger->warning($translator->gettext('You SHOULD NOT run Erebot as root!')); } if ($identd !== null) { $identd->connect(); } if ($prompt !== null) { $prompt->connect(); } // This doesn't return until we purposely // make the bot drop all active connections. $bot->start($dic->get('factory.connection')); exit(0); }
/** * Alarm * * @param int $seconds The number of seconds to wait. If seconds is zero, no new alarm is created. * * @return int The time in seconds that any previously scheduled alarm had remaining before it was to be delivered, or 0 if there was no previously scheduled alarm. */ public function alarm($seconds) { return pcntl_alarm($seconds); }
/** * Update a feed batch. * Used by daemons to update n feeds by run. * Only update feed needing a update, and not being processed * by another process. * * @param mixed $link Database link * @param integer $limit Maximum number of feeds in update batch. Default to DAEMON_FEED_LIMIT. * @param boolean $from_http Set to true if you call this function from http to disable cli specific code. * @param boolean $debug Set to false to disable debug output. Default to true. * @return void */ function update_daemon_common($link, $limit = DAEMON_FEED_LIMIT, $from_http = false, $debug = true) { // Process all other feeds using last_updated and interval parameters // Test if the user has loggued in recently. If not, it does not update its feeds. if (DAEMON_UPDATE_LOGIN_LIMIT > 0) { if (DB_TYPE == "pgsql") { $login_thresh_qpart = "AND ttrss_users.last_login >= NOW() - INTERVAL '" . DAEMON_UPDATE_LOGIN_LIMIT . " days'"; } else { $login_thresh_qpart = "AND ttrss_users.last_login >= DATE_SUB(NOW(), INTERVAL " . DAEMON_UPDATE_LOGIN_LIMIT . " DAY)"; } } else { $login_thresh_qpart = ""; } // Test if the feed need a update (update interval exceded). if (DB_TYPE == "pgsql") { $update_limit_qpart = "AND ((\n\t\t\t\t\tttrss_feeds.update_interval = 0\n\t\t\t\t\tAND ttrss_feeds.last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL)\n\t\t\t\t) OR (\n\t\t\t\t\tttrss_feeds.update_interval > 0\n\t\t\t\t\tAND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL)\n\t\t\t\t) OR ttrss_feeds.last_updated IS NULL)"; } else { $update_limit_qpart = "AND ((\n\t\t\t\t\tttrss_feeds.update_interval = 0\n\t\t\t\t\tAND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE)\n\t\t\t\t) OR (\n\t\t\t\t\tttrss_feeds.update_interval > 0\n\t\t\t\t\tAND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE)\n\t\t\t\t) OR ttrss_feeds.last_updated IS NULL)"; } // Test if feed is currently being updated by another process. if (DB_TYPE == "pgsql") { $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < NOW() - INTERVAL '120 seconds')"; } else { $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 120 SECOND))"; } // Test if there is a limit to number of updated feeds $query_limit = ""; if ($limit) { $query_limit = sprintf("LIMIT %d", $limit); } $random_qpart = sql_random_function(); // We search for feed needing update. $result = db_query($link, "SELECT ttrss_feeds.feed_url,ttrss_feeds.id, ttrss_feeds.owner_uid,\n\t\t\t\t" . SUBSTRING_FOR_DATE . "(ttrss_feeds.last_updated,1,19) AS last_updated,\n\t\t\t\tttrss_feeds.update_interval \n\t\t\tFROM \n\t\t\t\tttrss_feeds, ttrss_users, ttrss_user_prefs\n\t\t\tWHERE\n\t\t\t\tttrss_feeds.owner_uid = ttrss_users.id\n\t\t\t\tAND ttrss_users.id = ttrss_user_prefs.owner_uid\n\t\t\t\tAND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'\n\t\t\t\t{$login_thresh_qpart} {$update_limit_qpart}\n\t\t\t {$updstart_thresh_qpart}\n\t\t\tORDER BY {$random_qpart} {$query_limit}"); $user_prefs_cache = array(); if ($debug) { _debug(sprintf("Scheduled %d feeds to update...\n", db_num_rows($result))); } // Here is a little cache magic in order to minimize risk of double feed updates. $feeds_to_update = array(); while ($line = db_fetch_assoc($result)) { $feeds_to_update[$line['id']] = $line; } // We update the feed last update started date before anything else. // There is no lag due to feed contents downloads // It prevent an other process to update the same feed. $feed_ids = array_keys($feeds_to_update); if ($feed_ids) { db_query($link, sprintf("UPDATE ttrss_feeds SET last_update_started = NOW()\n\t\t\t\tWHERE id IN (%s)", implode(',', $feed_ids))); } // For each feed, we call the feed update function. while ($line = array_pop($feeds_to_update)) { if ($debug) { _debug("Feed: " . $line["feed_url"] . ", " . $line["last_updated"]); } // We setup a alarm to alert if the feed take more than 300s to update. // => HANG alarm. if (!$from_http && function_exists('pcntl_alarm')) { pcntl_alarm(300); } update_rss_feed($link, $line["id"], true); // Cancel the alarm (the update went well) if (!$from_http && function_exists('pcntl_alarm')) { pcntl_alarm(0); } sleep(1); // prevent flood (FIXME make this an option?) } // Send feed digests by email if needed. if (DAEMON_SENDS_DIGESTS) { send_headlines_digests($link); } purge_orphans($link); }
/** * 设置server信号处理函数 * @param null $null * @param int $signal */ public function signalHandler($signal, $null = null, $null = null) { switch ($signal) { // 时钟处理函数 case SIGALRM: // 停止服务后EXIT_WAIT_TIME秒还没退出则强制退出 if ($this->workerStatus == self::STATUS_SHUTDOWN) { exit(0); } break; // 停止该进程 // 停止该进程 case SIGINT: // 平滑重启 // 平滑重启 case SIGHUP: $this->stop(); // EXIT_WAIT_TIME秒后退出进程 pcntl_alarm(self::EXIT_WAIT_TIME); break; // 报告进程状态 // 报告进程状态 case SIGUSR1: $this->writeStatusToQueue(); break; // 报告进程使用的php文件 // 报告进程使用的php文件 case SIGUSR2: $this->writeFilesListToQueue(); break; } }
/** * 当gateway转发来数据时 * @param TcpConnection $connection * @param mixed $data */ public function onGatewayMessage($connection, $data) { // 上下文数据 Context::$client_ip = $data['client_ip']; Context::$client_port = $data['client_port']; Context::$local_ip = $data['local_ip']; Context::$local_port = $data['local_port']; Context::$connection_id = $data['connection_id']; Context::$client_id = Context::addressToClientId($data['local_ip'], $data['local_port'], $data['connection_id']); // $_SERVER变量 $_SERVER = array('REMOTE_ADDR' => long2ip($data['client_ip']), 'REMOTE_PORT' => $data['client_port'], 'GATEWAY_ADDR' => long2ip($data['local_ip']), 'GATEWAY_PORT' => $data['gateway_port'], 'GATEWAY_CLIENT_ID' => Context::$client_id); // 尝试解析session if ($data['ext_data'] != '') { $_SESSION = Context::sessionDecode($data['ext_data']); } else { $_SESSION = null; } // 备份一次$data['ext_data'],请求处理完毕后判断session是否和备份相等,不相等就更新session $session_str_copy = $data['ext_data']; $cmd = $data['cmd']; if ($this->processTimeout) { pcntl_alarm($this->processTimeout); } // 尝试执行Event::onConnection、Event::onMessage、Event::onClose switch ($cmd) { case GatewayProtocol::CMD_ON_CONNECTION: if ($this->_eventOnConnect) { call_user_func($this->_eventOnConnect, Context::$client_id); } break; case GatewayProtocol::CMD_ON_MESSAGE: if ($this->_eventOnMessage) { call_user_func($this->_eventOnMessage, Context::$client_id, $data['body']); } break; case GatewayProtocol::CMD_ON_CLOSE: if ($this->_eventOnClose) { call_user_func($this->_eventOnClose, Context::$client_id); } break; } if ($this->processTimeout) { pcntl_alarm(0); } // 判断session是否被更改 $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : ''; if ($session_str_copy != $session_str_now) { \GatewayWorker\Lib\Gateway::updateSocketSession(Context::$client_id, $session_str_now); } Context::clear(); }