本篇主要對Worker類進行中文注釋方式學習Workerman
啟動文件:http_test.php
<?php use Workerman\Worker; require_once __DIR__ . '/wk/Autoloader.php'; // 創建一個Worker監聽2345端口,使用http協議通訊 $http_worker = new Worker("http://0.0.0.0:2345"); // 啟動4個進程對外提供服務 $http_worker->count = 4; // 接收到瀏覽器發送的數據時回復hello world給瀏覽器 $http_worker->onMessage = function($connection, $data) { // 向瀏覽器發送hello world $connection->send('hello world'); }; // 運行worker Worker::runAll();
Worker類:
初始化 __construct()
/** * Construct. * * @param string $socket_name * @param array $context_option */ public function __construct($socket_name = '', array $context_option = array()) { // Save all worker instances. //返回當前對象的hash id $this->workerId = \spl_object_hash($this); //把當前對象加入到$__workers屬性 static::$_workers[$this->workerId] = $this; //設置PID數組,用於區分所在的對象 static::$_pidMap[$this->workerId] = array(); // Get autoload root path. /*產生一條回溯跟蹤 Array ( [0] => Array ( [file] => /root/test_wk/http_test.php [line] => 5 [function] => __construct [class] => Workerman\Worker [object] => Workerman\Worker Object ( [id] => 0 [name] => none [count] => 1 [user] => [group] => [reloadable] => 1 [reusePort] => [onWorkerStart] => [onConnect] => [onMessage] => [onClose] => [onError] => [onBufferFull] => [onBufferDrain] => [onWorkerStop] => [onWorkerReload] => [transport] => tcp [connections] => Array ( ) [protocol] => [_autoloadRootPath:protected] => [_pauseAccept:protected] => 1 [stopping] => [_mainSocket:protected] => [_socketName:protected] => [_localSocket:protected] => [_context:protected] => [workerId] => 0000000016887ec6000000004cc8efd4 ) [type] => -> [args] => Array ( [0] => http://0.0.0.0:2345 ) ) ) */ $backtrace = \debug_backtrace(); //設置自動加載根目錄 本機:/root/test_wk $this->_autoloadRootPath = \dirname($backtrace[0]['file']); Autoloader::setRootPath($this->_autoloadRootPath); // Context for socket. if ($socket_name) { $this->_socketName = $socket_name; //設置內核接受緩存器隊列的大小 if (!isset($context_option['socket']['backlog'])) { $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; } //創建一個資源流,流的概念還在理解當中,等理解透徹了再補上^_^ $this->_context = \stream_context_create($context_option); } // Turn reusePort on. //開啟reusePort,開啟的目的是為了避免驚群效應,提升多進程短連接應用的性能 //不了解的可以參考這篇文章,說的很清楚 https://www.jianshu.com/p/97cc8c52d47a if (static::$_OS === \OS_TYPE_LINUX // if linux && \version_compare(\PHP_VERSION,'7.0.0', 'ge') // if php >= 7.0.0 && \strtolower(\php_uname('s')) !== 'darwin' // if not Mac OS && $this->transport !== 'unix') { // if not unix socket $this->reusePort = true; } }
runAll() 初始化worker並運行環境服務
/** * Run all worker instances. * * @return void */ public static function runAll() { static::checkSapiEnv();//判斷腳本啟動方式 static::init();//初始化 static::lock();//鎖定當前文件 static::parseCommand();//cli終端命令解析 static::daemonize();//以守護進程方式運行 static::initWorkers();//初始化worker static::installSignal();//安裝各類信號 static::saveMasterPid();//將當前進程ID寫入文件 static::unlock();//釋放當前文件鎖 static::displayUI();//啟動后終端顯示內容 static::forkWorkers();//創建當前進程的子進程 static::resetStd();//重定向輸入和輸出標准 static::monitorWorkers();//監聽子進程狀態 }
checkSapiEnv() 判斷腳本啟動方式
protected static function checkSapiEnv() { // Only for cli. if (\PHP_SAPI !== 'cli') { exit("Only run in command line mode \n"); } if (\DIRECTORY_SEPARATOR === '\\') { self::$_OS = \OS_TYPE_WINDOWS; } }
init()
protected static function init() { //設置用戶自定義的函數來處理腳本中出現的錯誤 \set_error_handler(function($code, $msg, $file, $line){ Worker::safeEcho("$msg in file $file on line $line\n"); }); // Start file. /*產生一條回溯跟蹤,打印結果如下 Array ( [0] => Array ( [file] => /root/test_wk/Workerman/Worker.php [line] => 537 [function] => init [class] => Workerman\Worker [type] => :: [args] => Array ( ) ) [1] => Array ( [file] => /root/test_wk/http_test.php [line] => 14 [function] => runAll [class] => Workerman\Worker [type] => :: [args] => Array ( ) ) ) */ $backtrace = \debug_backtrace(); //獲取啟動文件的絕對路徑 本機:/root/test_wk/http_test.php static::$_startFile = $backtrace[\count($backtrace) - 1]['file']; //啟動文件/替換為_ 本機:_root_test_wk_http_test.php $unique_prefix = \str_replace('/', '_', static::$_startFile); // Pid file. //設置PID路徑,本機:/root/test_wk/Workerman/../_root_test_wk_http_test.php.pid if (empty(static::$pidFile)) { static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; } // Log file. //設置log路徑並創建文件 本機:/root/test_wk/Workerman/../workerman.log if (empty(static::$logFile)) { static::$logFile = __DIR__ . '/../workerman.log'; } $log_file = (string)static::$logFile; if (!\is_file($log_file)) { \touch($log_file); \chmod($log_file, 0622); } // State. //設置默認狀態值 static::$_status = static::STATUS_STARTING; // For statistics. //獲取服務啟動時間 static::$_globalStatistics['start_timestamp'] = \time(); //設置啟動狀態的文件 本機:/tmp/_root_test_wk_http_test.php.status static::$_statisticsFile = \sys_get_temp_dir() . "/$unique_prefix.status"; // Process title. //設置進程名稱 本機:WorkerMan: master process start_file=/root/test_wk/http_test.php static::setProcessTitle(static::$processTitle . ': master process start_file=' . static::$_startFile); // Init data for worker id. static::initId(); // Timer init. Timer::init(); }
setProcessTitle
protected static function setProcessTitle($title) { //設置用戶自定義的函數來處理腳本中出現的錯誤 \set_error_handler(function(){}); // >=php 5.5 if (\function_exists('cli_set_process_title')) { //在cli模式下設置進程名稱 \cli_set_process_title($title); } // Need proctitle when php<=5.5 . elseif (\extension_loaded('proctitle') && \function_exists('setproctitle')) { \setproctitle($title); } \restore_error_handler(); }
initId() 初始化進程數量
protected static function initId() { /* 初始化進程數量 Array ( [0] => 0 [1] => 0 [2] => 0 [3] => 0 ) */ foreach (static::$_workers as $worker_id => $worker) { $new_id_map = array(); $worker->count = $worker->count < 1 ? 1 : $worker->count; for($key = 0; $key < $worker->count; $key++) { $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; } static::$_idMap[$worker_id] = $new_id_map; } }
lock();//鎖定當前文件
protected static function lock() { //用讀方式打開當前啟動文件 $fd = \fopen(static::$_startFile, 'r'); //鎖定當前文件 if ($fd && !flock($fd, LOCK_EX)) { static::log('Workerman['.static::$_startFile.'] already running.'); exit; } }
parseCommand();//cli終端命令解析
protected static function parseCommand() { //判斷當前系統是否是linux,不是的話退出 if (static::$_OS !== \OS_TYPE_LINUX) { return; } //獲取命令行參數 global $argv; // Check argv; $start_file = $argv[0];//執行文件 本機:http_test.php $available_commands = array( 'start', 'stop', 'restart', 'reload', 'status', 'connections', ); $usage = "Usage: php yourfile <command> [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; //驗證執行文件后跟的命令參數是否正確 if (!isset($argv[1]) || !\in_array($argv[1], $available_commands)) { if (isset($argv[1])) { static::safeEcho('Unknown command: ' . $argv[1] . "\n"); } exit($usage); } // Get command. $command = \trim($argv[1]); $command2 = isset($argv[2]) ? $argv[2] : ''; // Start command. //如果是守護進程方式啟動,寫入日志 $mode = ''; if ($command === 'start') { if ($command2 === '-d' || static::$daemonize) { $mode = 'in DAEMON mode'; } else { $mode = 'in DEBUG mode'; } } static::log("Workerman[$start_file] $command $mode"); // Get master process PID. //獲取當前主進程,第一次啟動時獲取不到 $master_pid = \is_file(static::$pidFile) ? \file_get_contents(static::$pidFile) : 0; //posix_kill($master_pid, 0)是檢查進程是否存在 //當前進程和主進程進行比對,如果一致則不能重新start,如果不一致只能使用start和restart命令 $master_is_alive = $master_pid && \posix_kill($master_pid, 0) && \posix_getpid() !== $master_pid; // Master is still alive? if ($master_is_alive) { if ($command === 'start') { static::log("Workerman[$start_file] already running"); exit; } } elseif ($command !== 'start' && $command !== 'restart') { static::log("Workerman[$start_file] not run"); exit; } // execute command. switch ($command) { case 'start': if ($command2 === '-d') { //設置要使用守護進程啟動 static::$daemonize = true; } break; case 'status': while (1) { if (\is_file(static::$_statisticsFile)) { @\unlink(static::$_statisticsFile); } // Master process will send SIGUSR2 signal to all child processes. \posix_kill($master_pid, SIGUSR2); // Sleep 1 second. \sleep(1); // Clear terminal. if ($command2 === '-d') { static::safeEcho("\33[H\33[2J\33(B\33[m", true); } // Echo status data. static::safeEcho(static::formatStatusData()); if ($command2 !== '-d') { exit(0); } static::safeEcho("\nPress Ctrl+C to quit.\n\n"); } exit(0); case 'connections': if (\is_file(static::$_statisticsFile) && \is_writable(static::$_statisticsFile)) { \unlink(static::$_statisticsFile); } // Master process will send SIGIO signal to all child processes. \posix_kill($master_pid, SIGIO); // Waiting amoment. \usleep(500000); // Display statisitcs data from a disk file. if(\is_readable(static::$_statisticsFile)) { \readfile(static::$_statisticsFile); } exit(0); case 'restart': case 'stop': if ($command2 === '-g') { static::$_gracefulStop = true; $sig = \SIGTERM; static::log("Workerman[$start_file] is gracefully stopping ..."); } else { static::$_gracefulStop = false; $sig = \SIGINT; static::log("Workerman[$start_file] is stopping ..."); } // Send stop signal to master process. $master_pid && \posix_kill($master_pid, $sig); // Timeout. $timeout = 5; $start_time = \time(); // Check master process is still alive? while (1) { $master_is_alive = $master_pid && \posix_kill($master_pid, 0); if ($master_is_alive) { // Timeout? if (!static::$_gracefulStop && \time() - $start_time >= $timeout) { static::log("Workerman[$start_file] stop fail"); exit; } // Waiting amoment. \usleep(10000); continue; } // Stop success. static::log("Workerman[$start_file] stop success"); if ($command === 'stop') { exit(0); } if ($command2 === '-d') { static::$daemonize = true; } break; } break; case 'reload': if($command2 === '-g'){ $sig = \SIGQUIT; }else{ $sig = \SIGUSR1; } \posix_kill($master_pid, $sig); exit; default : if (isset($command)) { static::safeEcho('Unknown command: ' . $command . "\n"); } exit($usage); } }
daemonize();//以守護進程方式運行
protected static function daemonize() { //如果設置不是守護進程或不是linux系統,則退出 if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { return; } //umask的作用是在創建新文件或目錄時 屏蔽掉新文件或目錄不應有的訪問允許權限 //umask(0)是將默認權限掩碼修改為0,意味着即將要創建的文件的權限都是777 \umask(0); //創建一個子進程 $pid = \pcntl_fork(); //-1代表創建失敗 if (-1 === $pid) { throw new Exception('Fork fail'); //子進程創建成功,當前進程退出 //假如當前進程是1,子進程是2000,那么退出的是當前進程:1 } elseif ($pid > 0) { exit(0); } //以下內容是從其他地方摘抄 //創建一個會話領導者,徹底脫離控制終端,當前進程為會話首領進程 /** 1、此進程【不可以是組長進程】將是新會話期的會話期首進程【session leader】,會話期首進程是創建該會話期的進程,此進程是新會話期的唯一進程。 2、此進程將是新進程組的組長進程,新進程組的id是此調用進程的進程id. 3、此進程沒有控制終端。 在調用setsid之前即使有控制終端,調用后它們的聯系將解除 4、一個會話是多個進程組的集合,並且只能有一個前台進程組。 一般來說先調用fork創建兩個進程,讓父進程退出,子進程來創建會話,因為子進程是繼承了父進程的進程組id,其進程是新分配的,不會等於進程組id,保證了創建會話的不是進程組組長進程。 **/ if (-1 === \posix_setsid()) { throw new Exception("Setsid fail"); } // Fork again avoid SVR4 system regain the control of terminal. //此進程已經脫離了原來的會話終端,不在繼承原來的會話了 //再次創建一個子進程為孫子進程 //該孫子進程負責繼續運行后面的代碼 $pid = \pcntl_fork(); if (-1 === $pid) { throw new Exception("Fork fail"); } elseif (0 !== $pid) { exit(0);//非孫子進程全部退出 } }
initWorkers();//初始化worker
protected static function initWorkers() { //當前非linux系統退出 if (static::$_OS !== \OS_TYPE_LINUX) { return; } foreach (static::$_workers as $worker) { // Worker name. //設置worker實例的名字 if (empty($worker->name)) { $worker->name = 'none'; } // Get unix user of the worker process. //獲取當前用戶名 if (empty($worker->user)) { $worker->user = static::getCurrentUser(); } else { if (\posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) { static::log('Warning: You must have the root privileges to change uid and gid.'); } } // Socket name. $worker->socket = $worker->getSocketName(); // Status name. $worker->status = '<g> [OK] </g>'; // Get column mapping for UI foreach(static::getUiColumns() as $column_name => $prop){ !isset($worker->{$prop}) && $worker->{$prop} = 'NNNN'; $prop_length = \strlen($worker->{$prop}); $key = '_max' . \ucfirst(\strtolower($column_name)) . 'NameLength'; static::$$key = \max(static::$$key, $prop_length); } // Listen. //reusePort屬性已開啟,不在當前進程統一監聽,而是到各個子進程下進行監聽 if (!$worker->reusePort) { $worker->listen(); } } }
protected static function getCurrentUser() { $user_info = \posix_getpwuid(\posix_getuid()); return $user_info['name']; }
installSignal();//安裝各類信號
protected static function installSignal() { if (static::$_OS !== \OS_TYPE_LINUX) { return; } $signalHandler = '\Workerman\Worker::signalHandler'; // stop \pcntl_signal(\SIGINT, $signalHandler, false);//前台進程結束,比如ctrl+c // graceful stop \pcntl_signal(\SIGTERM, $signalHandler, false);//停止進程 // reload \pcntl_signal(\SIGUSR1, $signalHandler, false);// // graceful reload \pcntl_signal(\SIGQUIT, $signalHandler, false); // status \pcntl_signal(\SIGUSR2, $signalHandler, false); // connection status \pcntl_signal(\SIGIO, $signalHandler, false); // ignore \pcntl_signal(\SIGPIPE, \SIG_IGN, false); }
saveMasterPid();//將當前進程ID寫入文件
protected static function saveMasterPid() { //判斷終端系統 if (static::$_OS !== \OS_TYPE_LINUX) { return; } //獲取當前進程ID static::$_masterPid = \posix_getpid(); //將進程ID寫入文件 if (false === \file_put_contents(static::$pidFile, static::$_masterPid)) { throw new Exception('can not save pid to ' . static::$pidFile); } }
unlock();//釋放當前文件鎖
protected static function unlock() { $fd = \fopen(static::$_startFile, 'r'); $fd && flock($fd, \LOCK_UN); }
forkWorkers();//創建當前進程的子進程
protected static function forkWorkersForLinux() { foreach (static::$_workers as $worker) { //設置worker的名字和長度 if (static::$_status === static::STATUS_STARTING) { if (empty($worker->name)) { $worker->name = $worker->getSocketName(); } $worker_name_length = \strlen($worker->name); if (static::$_maxWorkerNameLength < $worker_name_length) { static::$_maxWorkerNameLength = $worker_name_length; } } //循環創建子進程 while (\count(static::$_pidMap[$worker->workerId]) < $worker->count) { static::forkOneWorkerForLinux($worker); } } }
protected static function forkOneWorkerForLinux(self $worker) { // Get available worker id. //獲取子進程數組里的pid,沒有創建時都是0 //static::$_idMap[$worker_id] $id = static::getId($worker->workerId, 0); if ($id === false) { return; } //創建子進程 $pid = \pcntl_fork(); // For master process. //創建子進程成功,在父進程中將子進程pid加入到_pidMap和_idMap if ($pid > 0) { static::$_pidMap[$worker->workerId][$pid] = $pid; static::$_idMap[$worker->workerId][$id] = $pid; } // For child processes. //創建子進程成功,在子進程中... elseif (0 === $pid) { \srand(); \mt_srand(); //在子進程中創建socket監聽,這是reusePort的優勢 if ($worker->reusePort) { $worker->listen(); } if (static::$_status === static::STATUS_STARTING) { static::resetStd();//重定向輸入和輸出標准 } static::$_pidMap = array(); // Remove other listener. //刪除其他無用監聽 foreach(static::$_workers as $key => $one_worker) { if ($one_worker->workerId !== $worker->workerId) { $one_worker->unlisten(); unset(static::$_workers[$key]); } } //停止鬧鍾信號 Timer::delAll(); //設置子進程名稱 static::setProcessTitle(self::$processTitle . ': worker process ' . $worker->name . ' ' . $worker->getSocketName()); //設置當前進程的UID和GID $worker->setUserAndGroup(); $worker->id = $id; //停止運行,成為僵屍進程,否則會在while循環中一直運行 //參考https://www.iminho.me/wiki/blog-15.html $worker->run(); if (strpos(static::$eventLoopClass, 'Workerman\Events\Swoole') !== false) { exit(0); } $err = new Exception('event-loop exited'); static::log($err); exit(250); } else { throw new Exception("forkOneWorker fail"); } }
protected static function getId($worker_id, $pid) { return \array_search($pid, static::$_idMap[$worker_id]); }
listen()
public function listen() { if (!$this->_socketName) { return; } // Autoload. Autoloader::setRootPath($this->_autoloadRootPath); if (!$this->_mainSocket) { //獲取socket和address $local_socket = $this->parseSocketAddress(); // Flag. $flags = $this->transport === 'udp' ? \STREAM_SERVER_BIND : \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN; $errno = 0; $errmsg = ''; // SO_REUSEPORT. if ($this->reusePort) { //設置流端口復用 \stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); } // Create an Internet or Unix domain server socket. //創建一個Internet或Unix域服務器套接字 //使用stream_socket_accept()開始接受連接,這個在后面 $this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); if (!$this->_mainSocket) { throw new Exception($errmsg); } //暫時忽略設置ssl if ($this->transport === 'ssl') { \stream_socket_enable_crypto($this->_mainSocket, false); } elseif ($this->transport === 'unix') { $socket_file = \substr($local_socket, 7); if ($this->user) { \chown($socket_file, $this->user); } if ($this->group) { \chgrp($socket_file, $this->group); } } // Try to open keepalive for tcp and disable Nagle algorithm. if (\function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { \set_error_handler(function(){}); //是tcp傳輸層協議的話導入創建好的流構建socket $socket = \socket_import_stream($this->_mainSocket); //設置tcp選項,選項還可以通過linux進行修改配置 //心跳設置 \socket_set_option($socket, \SOL_SOCKET, \SO_KEEPALIVE, 1); \socket_set_option($socket, \SOL_TCP, \TCP_NODELAY, 1); \restore_error_handler(); } // Non blocking. //設置為非阻塞模式 \stream_set_blocking($this->_mainSocket, false); } $this->resumeAccept(); }
protected function parseSocketAddress() { if (!$this->_socketName) { return; } // Get the application layer communication protocol and listening address. list($scheme, $address) = \explode(':', $this->_socketName, 2); // Check application layer protocol class. //設置協議類 if (!isset(static::$_builtinTransports[$scheme])) { $scheme = \ucfirst($scheme); $this->protocol = \substr($scheme,0,1)==='\\' ? $scheme : 'Protocols\\' . $scheme; if (!\class_exists($this->protocol)) { $this->protocol = "Workerman\\Protocols\\$scheme"; if (!\class_exists($this->protocol)) { throw new Exception("class \\Protocols\\$scheme not exist"); } } if (!isset(static::$_builtinTransports[$this->transport])) { throw new Exception('Bad worker->transport ' . \var_export($this->transport, true)); } } else { $this->transport = $scheme; } //local socket //返回socket和address return static::$_builtinTransports[$this->transport] . ":" . $address; }
public function setUserAndGroup() { // Get uid. /* 根據用戶名獲取用戶信息 當前用戶名是root array(7) { ["name"]=> string(4) "root" ["passwd"]=> string(1) "x" ["uid"]=> int(0) ["gid"]=> int(0) ["gecos"]=> string(4) "root" ["dir"]=> string(5) "/root" ["shell"]=> string(9) "/bin/bash" } */ $user_info = \posix_getpwnam($this->user); if (!$user_info) { static::log("Warning: User {$this->user} not exsits"); return; } $uid = $user_info['uid']; // Get gid. //獲取用戶組ID if ($this->group) { $group_info = \posix_getgrnam($this->group); if (!$group_info) { static::log("Warning: Group {$this->group} not exsits"); return; } $gid = $group_info['gid']; } else { $gid = $user_info['gid']; } // Set uid and gid. //設置當前進程的組ID和UID,並將用戶名加入到組訪問列表中 if ($uid !== \posix_getuid() || $gid !== \posix_getgid()) { if (!\posix_setgid($gid) || !\posix_initgroups($user_info['name'], $gid) || !\posix_setuid($uid)) { static::log("Warning: change gid or uid fail."); } } }
protected static function monitorWorkersForLinux() { static::$_status = static::STATUS_RUNNING; while (1) { // Calls signal handlers for pending signals. //調用等待信號的處理器 //檢查是否有未處理的信號 \pcntl_signal_dispatch(); // Suspends execution of the current process until a child has exited, or until a signal is delivered $status = 0; //等待或返回fork的子進程狀態 $pid = \pcntl_wait($status, \WUNTRACED); // Calls signal handlers for pending signals again. \pcntl_signal_dispatch(); // If a child has already exited. if ($pid > 0) { // Find out which worker process exited. foreach (static::$_pidMap as $worker_id => $worker_pid_array) { if (isset($worker_pid_array[$pid])) { $worker = static::$_workers[$worker_id]; // Exit status. if ($status !== 0) { static::log("worker[" . $worker->name . ":$pid] exit with status $status"); } // For Statistics. if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; } ++static::$_globalStatistics['worker_exit_info'][$worker_id][$status]; // Clear process data. unset(static::$_pidMap[$worker_id][$pid]); // Mark id is available. $id = static::getId($worker_id, $pid); static::$_idMap[$worker_id][$id] = 0; break; } } // Is still running state then fork a new worker process. if (static::$_status !== static::STATUS_SHUTDOWN) { static::forkWorkers(); // If reloading continue. if (isset(static::$_pidsToRestart[$pid])) { unset(static::$_pidsToRestart[$pid]); static::reload(); } } } // If shutdown state and all child processes exited then master process exit. if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { static::exitAndClearAll(); } } }