Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
37.28% covered (danger)
37.28%
390 / 1046
17.39% covered (danger)
17.39%
12 / 69
CRAP
0.00% covered (danger)
0.00%
0 / 1
Worker
37.28% covered (danger)
37.28%
390 / 1046
17.39% covered (danger)
17.39%
12 / 69
46680.93
0.00% covered (danger)
0.00%
0 / 1
 __construct
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 runAll
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
2.05
 checkSapiEnv
88.10% covered (warning)
88.10%
37 / 42
0.00% covered (danger)
0.00%
0 / 1
6.06
 initStdOut
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
6.97
 hasColorSupport
40.00% covered (danger)
40.00%
4 / 10
0.00% covered (danger)
0.00%
0 / 1
21.82
 init
78.12% covered (warning)
78.12%
25 / 32
0.00% covered (danger)
0.00%
0 / 1
13.51
 initGlobalEvent
50.00% covered (danger)
50.00%
6 / 12
0.00% covered (danger)
0.00%
0 / 1
10.50
 lock
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
6.02
 initWorkers
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
13.00
 getAllWorkers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEventLoop
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMainSocket
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initId
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getCurrentUser
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 displayUI
78.38% covered (warning)
78.38%
29 / 37
0.00% covered (danger)
0.00%
0 / 1
15.98
 getVersionLine
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getUiColumns
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getSingleLineTotalLength
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 parseCommand
34.04% covered (danger)
34.04%
32 / 94
0.00% covered (danger)
0.00%
0 / 1
429.82
 getArgv
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 formatProcessStatusData
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
182
 formatConnectionStatusData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 installSignal
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 reinstallSignal
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 signalHandler
12.00% covered (danger)
12.00%
3 / 25
0.00% covered (danger)
0.00%
0 / 1
110.13
 getSignalName
20.00% covered (danger)
20.00%
2 / 10
0.00% covered (danger)
0.00%
0 / 1
72.95
 daemonize
13.33% covered (danger)
13.33%
2 / 15
0.00% covered (danger)
0.00%
0 / 1
49.66
 resetStd
11.11% covered (danger)
11.11%
2 / 18
0.00% covered (danger)
0.00%
0 / 1
65.89
 saveMasterPid
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 getAllWorkerPids
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 forkWorkers
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 forkWorkersForLinux
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
5.12
 forkWorkersForWindows
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
90
 getStartFilesForWindows
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 forkOneWorkerForWindows
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 checkWorkerStatusForWindows
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 forkOneWorkerForLinux
71.79% covered (warning)
71.79%
28 / 39
0.00% covered (danger)
0.00%
0 / 1
10.82
 getId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUserAndGroup
40.00% covered (danger)
40.00%
6 / 15
0.00% covered (danger)
0.00%
0 / 1
26.50
 setProcessTitle
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 monitorWorkers
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 monitorWorkersForLinux
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
210
 monitorWorkersForWindows
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 exitAndClearAll
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 reload
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
210
 stopAll
15.62% covered (danger)
15.62%
5 / 32
0.00% covered (danger)
0.00%
0 / 1
285.90
 checkIfChildRunning
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGracefulStop
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeStatisticsToStatusFile
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
132
 getUiColumnLength
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeConnectionsStatisticsToStatusFile
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
272
 checkErrors
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
90
 getErrorType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 log
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 safeEcho
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
4.25
 listen
71.88% covered (warning)
71.88%
23 / 32
0.00% covered (danger)
0.00%
0 / 1
20.01
 unlisten
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 checkPortAvailable
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
10.02
 parseSocketAddress
66.67% covered (warning)
66.67%
10 / 15
0.00% covered (danger)
0.00%
0 / 1
10.37
 pauseAccept
33.33% covered (danger)
33.33%
1 / 3
0.00% covered (danger)
0.00%
0 / 1
8.74
 resumeAccept
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
5.20
 getSocketName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 run
23.08% covered (danger)
23.08%
3 / 13
0.00% covered (danger)
0.00%
0 / 1
29.30
 stop
23.08% covered (danger)
23.08%
3 / 13
0.00% covered (danger)
0.00%
0 / 1
37.13
 acceptTcpConnection
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
4.02
 acceptUdpConnection
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
156
 checkMasterIsAlive
15.38% covered (danger)
15.38%
2 / 13
0.00% covered (danger)
0.00%
0 / 1
36.69
 isRunning
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This file is part of workerman.
4 *
5 * Licensed under The MIT License
6 * For full copyright and license information, please see the MIT-LICENSE.txt
7 * Redistributions of files must retain the above copyright notice.
8 *
9 * @author    walkor<walkor@workerman.net>
10 * @copyright walkor<walkor@workerman.net>
11 * @link      http://www.workerman.net/
12 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
13 */
14
15declare(strict_types=1);
16
17namespace Workerman;
18
19use AllowDynamicProperties;
20use Exception;
21use RuntimeException;
22use stdClass;
23use Stringable;
24use Throwable;
25use Workerman\Connection\ConnectionInterface;
26use Workerman\Connection\TcpConnection;
27use Workerman\Connection\UdpConnection;
28use Workerman\Coroutine;
29use Workerman\Events\Event;
30use Workerman\Events\EventInterface;
31use Workerman\Events\Fiber;
32use Workerman\Events\Select;
33use Workerman\Events\Swoole;
34use Workerman\Events\Swow;
35use function defined;
36use function function_exists;
37use function is_resource;
38use function method_exists;
39use function restore_error_handler;
40use function set_error_handler;
41use function stream_socket_accept;
42use function stream_socket_recvfrom;
43use function substr;
44use function array_walk;
45use function get_class;
46use const DIRECTORY_SEPARATOR;
47use const PHP_SAPI;
48use const PHP_VERSION;
49use const STDOUT;
50
51/**
52 * Worker class
53 * A container for listening ports
54 */
55#[AllowDynamicProperties]
56class Worker
57{
58    /**
59     * Version.
60     *
61     * @var string
62     */
63    final public const VERSION = '5.1.2';
64
65    /**
66     * Status initial.
67     *
68     * @var int
69     */
70    public const STATUS_INITIAL = 0;
71
72    /**
73     * Status starting.
74     *
75     * @var int
76     */
77    public const STATUS_STARTING = 1;
78
79    /**
80     * Status running.
81     *
82     * @var int
83     */
84    public const STATUS_RUNNING = 2;
85
86    /**
87     * Status shutdown.
88     *
89     * @var int
90     */
91    public const STATUS_SHUTDOWN = 4;
92
93    /**
94     * Status reloading.
95     *
96     * @var int
97     */
98    public const STATUS_RELOADING = 8;
99
100    /**
101     * Default backlog. Backlog is the maximum length of the queue of pending connections.
102     *
103     * @var int
104     */
105    public const DEFAULT_BACKLOG = 102400;
106
107    /**
108     * The safe distance for columns adjacent
109     *
110     * @var int
111     */
112    public const UI_SAFE_LENGTH = 4;
113
114    /**
115     * Worker id.
116     *
117     * @var int
118     */
119    public int $id = 0;
120
121    /**
122     * Name of the worker processes.
123     *
124     * @var string
125     */
126    public string $name = 'none';
127
128    /**
129     * Number of worker processes.
130     *
131     * @var int
132     */
133    public int $count = 1;
134
135    /**
136     * Unix user of processes, needs appropriate privileges (usually root).
137     *
138     * @var string
139     */
140    public string $user = '';
141
142    /**
143     * Unix group of processes, needs appropriate privileges (usually root).
144     *
145     * @var string
146     */
147    public string $group = '';
148
149    /**
150     * reloadable.
151     *
152     * @var bool
153     */
154    public bool $reloadable = true;
155
156    /**
157     * reuse port.
158     *
159     * @var bool
160     */
161    public bool $reusePort = false;
162
163    /**
164     * Emitted when worker processes is starting.
165     *
166     * @var ?callable
167     */
168    public $onWorkerStart = null;
169
170    /**
171     * Emitted when a socket connection is successfully established.
172     *
173     * @var ?callable
174     */
175    public $onConnect = null;
176
177    /**
178     * Emitted before websocket handshake (Only works when protocol is ws).
179     *
180     * @var ?callable
181     */
182    public $onWebSocketConnect = null;
183
184    /**
185     * Emitted after websocket handshake (Only works when protocol is ws).
186     *
187     * @var ?callable
188     */
189    public $onWebSocketConnected = null;
190
191    /**
192     * Emitted when data is received.
193     *
194     * @var ?callable
195     */
196    public $onMessage = null;
197
198    /**
199     * Emitted when the other end of the socket sends a FIN packet.
200     *
201     * @var ?callable
202     */
203    public $onClose = null;
204
205    /**
206     * Emitted when an error occurs with connection.
207     *
208     * @var ?callable
209     */
210    public $onError = null;
211
212    /**
213     * Emitted when the send buffer becomes full.
214     *
215     * @var ?callable
216     */
217    public $onBufferFull = null;
218
219    /**
220     * Emitted when the send buffer becomes empty.
221     *
222     * @var ?callable
223     */
224    public $onBufferDrain = null;
225
226    /**
227     * Emitted when worker processes has stopped.
228     *
229     * @var ?callable
230     */
231    public $onWorkerStop = null;
232
233    /**
234     * Emitted when worker processes receives reload signal.
235     *
236     * @var ?callable
237     */
238    public $onWorkerReload = null;
239
240    /**
241     * Transport layer protocol.
242     *
243     * @var string
244     */
245    public string $transport = 'tcp';
246
247    /**
248     * Store all connections of clients.
249     *
250     * @var TcpConnection[]
251     */
252    public array $connections = [];
253
254    /**
255     * Application layer protocol.
256     *
257     * @var ?string
258     */
259    public ?string $protocol = null;
260
261    /**
262     * Pause accept new connections or not.
263     *
264     * @var bool
265     */
266    protected bool $pauseAccept = true;
267
268    /**
269     * Is worker stopping ?
270     *
271     * @var bool
272     */
273    public bool $stopping = false;
274
275    /**
276     * EventLoop class.
277     *
278     * @var ?string
279     */
280    public ?string $eventLoop = null;
281
282    /**
283     * Daemonize.
284     *
285     * @var bool
286     */
287    public static bool $daemonize = false;
288
289    /**
290     * Standard output stream
291     *
292     * @var resource
293     */
294    public static $outputStream;
295
296    /**
297     * Stdout file.
298     *
299     * @var string
300     */
301    public static string $stdoutFile = '/dev/null';
302
303    /**
304     * The file to store master process PID.
305     *
306     * @var string
307     */
308    public static string $pidFile = '';
309
310    /**
311     * The file used to store the master process status.
312     *
313     * @var string
314     */
315    public static string $statusFile = '';
316
317    /**
318     * Log file.
319     *
320     * @var string
321     */
322    public static string $logFile = '';
323
324    /**
325     * Global event loop.
326     *
327     * @var ?EventInterface
328     */
329    public static ?EventInterface $globalEvent = null;
330
331    /**
332     * Emitted when the master process get reload signal.
333     *
334     * @var ?callable
335     */
336    public static $onMasterReload = null;
337
338    /**
339     * Emitted when the master process terminated.
340     *
341     * @var ?callable
342     */
343    public static $onMasterStop = null;
344
345    /**
346     * Emitted when worker processes exited.
347     *
348     * @var ?callable
349     */
350    public static $onWorkerExit = null;
351
352    /**
353     * EventLoopClass
354     *
355     * @var ?class-string<EventInterface>
356     */
357    public static ?string $eventLoopClass = null;
358
359    /**
360     * After sending the stop command to the child process stopTimeout seconds,
361     * if the process is still living then forced to kill.
362     *
363     * @var int
364     */
365    public static int $stopTimeout = 2;
366
367    /**
368     * Command
369     *
370     * @var string
371     */
372    public static string $command = '';
373
374    /**
375     * The PID of master process.
376     *
377     * @var int
378     */
379    protected static int $masterPid = 0;
380
381    /**
382     * Listening socket.
383     *
384     * @var ?resource
385     */
386    protected $mainSocket = null;
387
388    /**
389     * Socket name. The format is like this http://0.0.0.0:80 .
390     *
391     * @var string
392     */
393    protected string $socketName = '';
394
395    /**
396     * Context of socket.
397     *
398     * @var resource
399     */
400    protected $socketContext = null;
401
402    /**
403     * @var stdClass
404     */
405    protected stdClass $context;
406
407    /**
408     * All worker instances.
409     *
410     * @var Worker[]
411     */
412    protected static array $workers = [];
413
414    /**
415     * All worker processes pid.
416     * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..]
417     *
418     * @var array
419     */
420    protected static array $pidMap = [];
421
422    /**
423     * All worker processes waiting for restart.
424     * The format is like this [pid=>pid, pid=>pid].
425     *
426     * @var array
427     */
428    protected static array $pidsToRestart = [];
429
430    /**
431     * Mapping from PID to worker process ID.
432     * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..].
433     *
434     * @var array
435     */
436    protected static array $idMap = [];
437
438    /**
439     * Current status.
440     *
441     * @var int
442     */
443    protected static int $status = self::STATUS_INITIAL;
444
445    /**
446     * UI data.
447     *
448     * @var array|int[]
449     */
450    protected static array $uiLengthData = [];
451
452    /**
453     * The file to store status info of current worker process.
454     *
455     * @var string
456     */
457    protected static string $statisticsFile = '';
458
459    /**
460     * The file to store status info of connections.
461     *
462     * @var string
463     */
464    protected static string $connectionsFile = '';
465
466    /**
467     * Start file.
468     *
469     * @var string
470     */
471    protected static string $startFile = '';
472
473    /**
474     * Processes for windows.
475     *
476     * @var array
477     */
478    protected static array $processForWindows = [];
479
480    /**
481     * Status info of current worker process.
482     *
483     * @var array
484     */
485    protected static array $globalStatistics = [
486        'start_timestamp' => 0,
487        'worker_exit_info' => []
488    ];
489
490    /**
491     * PHP built-in protocols.
492     *
493     * @var array<string, string>
494     */
495    public const BUILD_IN_TRANSPORTS = [
496        'tcp' => 'tcp',
497        'udp' => 'udp',
498        'unix' => 'unix',
499        'ssl' => 'tcp'
500    ];
501
502    /**
503     * PHP built-in error types.
504     *
505     * @var array<int, string>
506     */
507    public const ERROR_TYPE = [
508        E_ERROR => 'E_ERROR',
509        E_WARNING => 'E_WARNING',
510        E_PARSE => 'E_PARSE',
511        E_NOTICE => 'E_NOTICE',
512        E_CORE_ERROR => 'E_CORE_ERROR',
513        E_CORE_WARNING => 'E_CORE_WARNING',
514        E_COMPILE_ERROR => 'E_COMPILE_ERROR',
515        E_COMPILE_WARNING => 'E_COMPILE_WARNING',
516        E_USER_ERROR => 'E_USER_ERROR',
517        E_USER_WARNING => 'E_USER_WARNING',
518        E_USER_NOTICE => 'E_USER_NOTICE',
519        E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
520        E_DEPRECATED => 'E_DEPRECATED',
521        E_USER_DEPRECATED => 'E_USER_DEPRECATED'
522    ];
523
524    /**
525     * Graceful stop or not.
526     *
527     * @var bool
528     */
529    protected static bool $gracefulStop = false;
530
531    /**
532     * If $outputStream support decorated
533     *
534     * @var bool
535     */
536    protected static bool $outputDecorated;
537
538    /**
539     * Worker object's hash id(unique identifier).
540     *
541     * @var ?string
542     */
543    protected ?string $workerId = null;
544
545    /**
546     * Constructor.
547     *
548     * @param string|null $socketName
549     * @param array $socketContext
550     */
551    public function __construct(?string $socketName = null, array $socketContext = [])
552    {
553        // Save all worker instances.
554        $this->workerId = spl_object_hash($this);
555        $this->context = new stdClass();
556        static::$workers[$this->workerId] = $this;
557        static::$pidMap[$this->workerId] = [];
558
559        // Context for socket.
560        if ($socketName) {
561            $this->socketName = $socketName;
562            $socketContext['socket']['backlog'] ??= static::DEFAULT_BACKLOG;
563            $this->socketContext = stream_context_create($socketContext);
564        }
565
566        // Set an empty onMessage callback.
567        $this->onMessage = function () {
568            // Empty.
569        };
570
571    }
572
573    /**
574     * Run all worker instances.
575     *
576     * @return void
577     */
578    public static function runAll(): void
579    {
580        try {
581            static::checkSapiEnv();
582            static::initStdOut();
583            static::init();
584            static::parseCommand();
585            static::checkPortAvailable();
586            static::lock();
587            static::daemonize();
588            static::initWorkers();
589            static::installSignal();
590            static::saveMasterPid();
591            static::lock(LOCK_UN);
592            static::displayUI();
593            static::forkWorkers();
594            static::resetStd();
595            static::monitorWorkers();
596        } catch (Throwable $e) {
597            static::log($e);
598        }
599    }
600
601    /**
602     * Check sapi.
603     *
604     * @return void
605     */
606    protected static function checkSapiEnv(): void
607    {
608        // Only for cli and micro.
609        if (!in_array(PHP_SAPI, ['cli', 'micro'])) {
610            exit("Only run in command line mode" . PHP_EOL);
611        }
612        // Check pcntl and posix extension for unix.
613        if (DIRECTORY_SEPARATOR === '/') {
614            foreach (['pcntl', 'posix'] as $name) {
615                if (!extension_loaded($name)) {
616                    exit("Please install $name extension" . PHP_EOL);
617                }
618            }
619        }
620        // Check disable functions.
621        $disabledFunctions = explode(',', ini_get('disable_functions'));
622        $disabledFunctions = array_map('trim', $disabledFunctions);
623        $functionsToCheck = [
624            'stream_socket_server',
625            'stream_socket_accept',
626            'stream_socket_client',
627            'pcntl_signal_dispatch',
628            'pcntl_signal',
629            'pcntl_alarm',
630            'pcntl_fork',
631            'pcntl_wait',
632            'posix_getuid',
633            'posix_getpwuid',
634            'posix_kill',
635            'posix_setsid',
636            'posix_getpid',
637            'posix_getpwnam',
638            'posix_getgrnam',
639            'posix_getgid',
640            'posix_setgid',
641            'posix_initgroups',
642            'posix_setuid',
643            'posix_isatty',
644            'proc_open',
645            'proc_get_status',
646            'proc_close',
647            'shell_exec',
648            'exec',
649            'putenv',
650            'getenv',
651        ];
652        $disabled = array_intersect($functionsToCheck, $disabledFunctions);
653        if (!empty($disabled)) {
654            $iniFilePath = (string)php_ini_loaded_file();
655            exit('Notice: '. implode(',', $disabled) . " are disabled by disable_functions. " . PHP_EOL
656                . "Please remove them from disable_functions in $iniFilePath" . PHP_EOL);
657        }
658    }
659
660    /**
661     * Init stdout.
662     *
663     * @return void
664     */
665    protected static function initStdOut(): void
666    {
667        $defaultStream = fn () => defined('STDOUT') ? STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w'));
668        static::$outputStream ??= $defaultStream(); //@phpstan-ignore-line
669        if (!is_resource(self::$outputStream) || get_resource_type(self::$outputStream) !== 'stream') {
670            $type = get_debug_type(self::$outputStream);
671            static::$outputStream = $defaultStream();
672            throw new RuntimeException(sprintf('The $outputStream must to be a stream, %s given', $type));
673        }
674
675        static::$outputDecorated ??= self::hasColorSupport();
676    }
677
678    /**
679     * Borrowed from the symfony console
680     * @link https://github.com/symfony/console/blob/0d14a9f6d04d4ac38a8cea1171f4554e325dae92/Output/StreamOutput.php#L92
681     */
682    private static function hasColorSupport(): bool
683    {
684        // Follow https://no-color.org/
685        if (getenv('NO_COLOR') !== false) {
686            return false;
687        }
688
689        if (getenv('TERM_PROGRAM') === 'Hyper') {
690            return true;
691        }
692
693        if (DIRECTORY_SEPARATOR === '\\') {
694            return (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(self::$outputStream))
695                || getenv('ANSICON') !== false
696                || getenv('ConEmuANSI') === 'ON'
697                || getenv('TERM') === 'xterm';
698        }
699
700        return stream_isatty(self::$outputStream);
701    }
702
703    /**
704     * Init.
705     *
706     * @return void
707     */
708    protected static function init(): void
709    {
710        set_error_handler(static function (int $code, string $msg, string $file, int $line): bool {
711            static::safeEcho(sprintf("%s \"%s\" in file %s on line %d\n", static::getErrorType($code), $msg, $file, $line));
712            return true;
713        });
714
715        // $_SERVER.
716        $_SERVER['SERVER_SOFTWARE'] = 'Workerman/' . static::VERSION;
717        $_SERVER['SERVER_START_TIME'] = time();
718
719        // Start file.
720        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
721        static::$startFile = static::$startFile ?: end($backtrace)['file'];
722        $startFilePrefix = basename(static::$startFile);
723        $startFileDir = dirname(static::$startFile);
724        
725        // Compatible with older workerman versions for pid file.
726        if (empty(static::$pidFile)) {
727            $unique_prefix = \str_replace('/', '_', static::$startFile);
728            $file = __DIR__ . "/../../$unique_prefix.pid";
729            if (is_file($file)) {
730                static::$pidFile = $file;
731            }
732        }
733
734        // Pid file.
735        static::$pidFile = static::$pidFile ?: sprintf('%s/workerman.%s.pid', $startFileDir, $startFilePrefix);
736
737        // Status file.
738        static::$statusFile = static::$statusFile ?: sprintf('%s/workerman.%s.status', $startFileDir, $startFilePrefix);
739        static::$statisticsFile = static::$statisticsFile ?: static::$statusFile;
740        static::$connectionsFile = static::$connectionsFile ?: static::$statusFile . '.connection';
741
742        // Log file.
743        static::$logFile = static::$logFile ?: sprintf('%s/workerman.log', $startFileDir);
744
745        if (static::$logFile !== '/dev/null' && !is_file(static::$logFile)) {
746            // if /runtime/logs  default folder not exists
747            if (!is_dir(dirname(static::$logFile))) {
748                mkdir(dirname(static::$logFile), 0777, true);
749            }
750            touch(static::$logFile);
751            chmod(static::$logFile, 0644);
752        }
753
754        // State.
755        static::$status = static::STATUS_STARTING;
756
757        // Init global event.
758        static::initGlobalEvent();
759
760        // For statistics.
761        static::$globalStatistics['start_timestamp'] = time();
762
763        // Process title.
764        static::setProcessTitle('WorkerMan: master process  start_file=' . static::$startFile);
765
766        // Init data for worker id.
767        static::initId();
768
769        // Timer init.
770        Timer::init();
771
772        restore_error_handler();
773    }
774
775    /**
776     * Init global event.
777     *
778     * @return void
779     */
780    protected static function initGlobalEvent(): void
781    {
782        if (static::$globalEvent !== null) {
783            static::$eventLoopClass = get_class(static::$globalEvent);
784            static::$globalEvent = null;
785            return;
786        }
787
788        if (!empty(static::$eventLoopClass)) {
789            if (!is_subclass_of(static::$eventLoopClass, EventInterface::class)) {
790                throw new RuntimeException(sprintf('%s::$eventLoopClass must implement %s', static::class, EventInterface::class));
791            }
792            return;
793        }
794
795        static::$eventLoopClass = match (true) {
796            extension_loaded('event') => Event::class,
797            default => Select::class,
798        };
799    }
800
801    /**
802     * Lock.
803     *
804     * @param int $flag
805     * @return void
806     */
807    protected static function lock(int $flag = LOCK_EX): void
808    {
809        static $fd;
810        if (DIRECTORY_SEPARATOR !== '/') {
811            return;
812        }
813        $lockFile = static::$pidFile . '.lock';
814        $fd = $fd ?: fopen($lockFile, 'a+');
815        if ($fd) {
816            flock($fd, $flag);
817            if ($flag === LOCK_UN) {
818                fclose($fd);
819                $fd = null;
820                clearstatcache();
821                if (is_file($lockFile)) {
822                    unlink($lockFile);
823                }
824            }
825        }
826    }
827
828    /**
829     * Init All worker instances.
830     *
831     * @return void
832     */
833    protected static function initWorkers(): void
834    {
835        if (DIRECTORY_SEPARATOR !== '/') {
836            return;
837        }
838
839        foreach (static::$workers as $worker) {
840            // Worker name.
841            if (empty($worker->name)) {
842                $worker->name = 'none';
843            }
844
845            // Get unix user of the worker process.
846            if (empty($worker->user)) {
847                $worker->user = static::getCurrentUser();
848            } else {
849                if (posix_getuid() !== 0 && $worker->user !== static::getCurrentUser()) {
850                    static::log('Warning: You must have the root privileges to change uid and gid.');
851                }
852            }
853
854            // Socket name.
855            $worker->context->statusSocket = $worker->getSocketName();
856
857            // Event-loop name.
858            $eventLoopName = $worker->eventLoop ?: static::$eventLoopClass;
859            $worker->context->eventLoopName = strtolower(substr($eventLoopName, strrpos($eventLoopName, '\\') + 1));
860
861            // Status name.
862            $worker->context->statusState = '<g> [OK] </g>';
863
864            // Get column mapping for UI
865            foreach (static::getUiColumns() as $columnName => $prop) {
866                !isset($worker->$prop) && !isset($worker->context->$prop) && $worker->context->$prop = 'NNNN';
867                $propLength = strlen((string)($worker->$prop ?? $worker->context->$prop));
868                $key = 'max' . ucfirst(strtolower($columnName)) . 'NameLength';
869                static::$uiLengthData[$key] = max(static::$uiLengthData[$key] ?? 2 * static::UI_SAFE_LENGTH, $propLength);
870            }
871
872            // Listen.
873            if (!$worker->reusePort) {
874                $worker->listen();
875                $worker->pauseAccept();
876            }
877        }
878    }
879
880    /**
881     * Get all worker instances.
882     *
883     * @return Worker[]
884     */
885    public static function getAllWorkers(): array
886    {
887        return static::$workers;
888    }
889
890    /**
891     * Get global event-loop instance.
892     *
893     * @return EventInterface
894     */
895    public static function getEventLoop(): EventInterface
896    {
897        return static::$globalEvent;
898    }
899
900    /**
901     * Get main socket resource
902     *
903     * @return resource
904     */
905    public function getMainSocket(): mixed
906    {
907        return $this->mainSocket;
908    }
909
910    /**
911     * Init idMap.
912     *
913     * @return void
914     */
915    protected static function initId(): void
916    {
917        foreach (static::$workers as $workerId => $worker) {
918            $newIdMap = [];
919            $worker->count = max($worker->count, 1);
920            for ($key = 0; $key < $worker->count; $key++) {
921                $newIdMap[$key] = static::$idMap[$workerId][$key] ?? 0;
922            }
923            static::$idMap[$workerId] = $newIdMap;
924        }
925    }
926
927    /**
928     * Get unix user of current process.
929     *
930     * @return string
931     */
932    protected static function getCurrentUser(): string
933    {
934        $userInfo = posix_getpwuid(posix_getuid());
935        return $userInfo['name'] ?? 'unknown';
936    }
937
938    /**
939     * Display staring UI.
940     *
941     * @return void
942     */
943    protected static function displayUI(): void
944    {
945        $tmpArgv = static::getArgv();
946        if (in_array('-q', $tmpArgv)) {
947            return;
948        }
949
950
951        $lineVersion = static::getVersionLine();
952        // For windows
953        if (DIRECTORY_SEPARATOR !== '/') {
954            static::safeEcho("---------------------------------------------- WORKERMAN -----------------------------------------------\r\n");
955            static::safeEcho($lineVersion);
956            static::safeEcho("----------------------------------------------- WORKERS ------------------------------------------------\r\n");
957            static::safeEcho("worker                                          listen                              processes   status\r\n");
958            return;
959        }
960
961        // For unix
962        !defined('LINE_VERSION_LENGTH') && define('LINE_VERSION_LENGTH', strlen($lineVersion));
963        $totalLength = static::getSingleLineTotalLength();
964        $lineOne = '<n>' . str_pad('<w> WORKERMAN </w>', $totalLength + strlen('<w></w>'), '-', STR_PAD_BOTH) . '</n>' . PHP_EOL;
965        $lineTwo = str_pad('<w> WORKERS </w>', $totalLength + strlen('<w></w>'), '-', STR_PAD_BOTH) . PHP_EOL;
966        static::safeEcho($lineOne . $lineVersion . $lineTwo);
967
968        //Show title
969        $title = '';
970        foreach (static::getUiColumns() as $columnName => $prop) {
971            $key = 'max' . ucfirst(strtolower($columnName)) . 'NameLength';
972            //just keep compatible with listen name
973            $columnName === 'socket' && $columnName = 'listen';
974            $title .= "<w>$columnName</w>" . str_pad('', static::getUiColumnLength($key) + static::UI_SAFE_LENGTH - strlen($columnName));
975        }
976        $title && static::safeEcho($title . PHP_EOL);
977
978        //Show content
979        foreach (static::$workers as $worker) {
980            $content = '';
981            foreach (static::getUiColumns() as $columnName => $prop) {
982                $propValue = (string)($worker->$prop ?? $worker->context->$prop);
983                $key = 'max' . ucfirst(strtolower($columnName)) . 'NameLength';
984                preg_match_all("/(<n>|<\/n>|<w>|<\/w>|<g>|<\/g>)/i", $propValue, $matches);
985                $placeHolderLength = !empty($matches[0]) ? strlen(implode('', $matches[0])) : 0;
986                $content .= str_pad($propValue, static::getUiColumnLength($key) + static::UI_SAFE_LENGTH + $placeHolderLength);
987            }
988            $content && static::safeEcho($content . PHP_EOL);
989        }
990
991        //Show last line
992        $lineLast = str_pad('', static::getSingleLineTotalLength(), '-') . PHP_EOL;
993        !empty($content) && static::safeEcho($lineLast);
994
995        if (static::$daemonize) {
996            static::safeEcho('Input "php ' . basename(static::$startFile) . ' stop" to stop. Start success.' . "\n\n");
997        } else if (!empty(static::$command)) {
998            static::safeEcho("Start success.\n"); // Workerman used as library
999        } else {
1000            static::safeEcho("Press Ctrl+C to stop. Start success.\n");
1001        }
1002    }
1003
1004    /**
1005     * @return string
1006     */
1007    protected static function getVersionLine(): string
1008    {
1009        //Show version
1010        $jitStatus = function_exists('opcache_get_status') && (opcache_get_status()['jit']['on'] ?? false) === true ? 'on' : 'off';
1011        $version = str_pad('Workerman/' . static::VERSION, 24);
1012        $version .= str_pad('PHP/' . PHP_VERSION . ' (JIT ' . $jitStatus . ')', 30);
1013        $version .= php_uname('s') . '/' . php_uname('r') . PHP_EOL;
1014        return $version;
1015    }
1016
1017    /**
1018     * Get UI columns to be shown in terminal
1019     *
1020     * 1. $columnMap: ['ui_column_name' => 'clas_property_name']
1021     * 2. Consider move into configuration in future
1022     *
1023     * @return array
1024     */
1025    public static function getUiColumns(): array
1026    {
1027        return [
1028            'event-loop' => 'eventLoopName',
1029            'proto' => 'transport',
1030            'user' => 'user',
1031            'worker' => 'name',
1032            'socket' => 'statusSocket',
1033            'count' => 'count',
1034            'state' => 'statusState',
1035        ];
1036    }
1037
1038    /**
1039     * Get single line total length for ui
1040     *
1041     * @return int
1042     */
1043    public static function getSingleLineTotalLength(): int
1044    {
1045        $totalLength = 0;
1046
1047        foreach (static::getUiColumns() as $columnName => $prop) {
1048            $key = 'max' . ucfirst(strtolower($columnName)) . 'NameLength';
1049            $totalLength += static::getUiColumnLength($key) + static::UI_SAFE_LENGTH;
1050        }
1051
1052        //Keep beauty when show less columns
1053        !defined('LINE_VERSION_LENGTH') && define('LINE_VERSION_LENGTH', 0);
1054        $totalLength <= LINE_VERSION_LENGTH && $totalLength = LINE_VERSION_LENGTH;
1055
1056        return $totalLength;
1057    }
1058
1059    /**
1060     * Parse command.
1061     *
1062     * @return void
1063     */
1064    protected static function parseCommand(): void
1065    {
1066        if (DIRECTORY_SEPARATOR !== '/') {
1067            return;
1068        }
1069
1070        // Check argv;
1071        $startFile = basename(static::$startFile);
1072        $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";
1073        $availableCommands = [
1074            'start',
1075            'stop',
1076            'restart',
1077            'reload',
1078            'status',
1079            'connections',
1080        ];
1081        $availableMode = [
1082            '-d',
1083            '-g'
1084        ];
1085        $command = $mode = '';
1086        foreach (static::getArgv() as $value) {
1087            if (!$command && in_array($value, $availableCommands)) {
1088                $command = $value;
1089            }
1090            if (!$mode && in_array($value, $availableMode)) {
1091                $mode = $value;
1092            }
1093        }
1094
1095        if (!$command) {
1096            exit($usage);
1097        }
1098
1099        // Start command.
1100        $modeStr = '';
1101        if ($command === 'start') {
1102            if ($mode === '-d' || static::$daemonize) {
1103                $modeStr = 'in DAEMON mode';
1104            } else {
1105                $modeStr = 'in DEBUG mode';
1106            }
1107        }
1108        static::log("Workerman[$startFile$command $modeStr");
1109
1110        // Get master process PID.
1111        $masterPid = is_file(static::$pidFile) ? (int)file_get_contents(static::$pidFile) : 0;
1112        // Master is still alive?
1113        if (static::checkMasterIsAlive($masterPid)) {
1114            if ($command === 'start') {
1115                static::log("Workerman[$startFile] already running");
1116                exit;
1117            }
1118        } elseif ($command !== 'start' && $command !== 'restart') {
1119            static::log("Workerman[$startFile] not run");
1120            exit;
1121        }
1122
1123        // execute command.
1124        switch ($command) {
1125            case 'start':
1126                if ($mode === '-d') {
1127                    static::$daemonize = true;
1128                }
1129                break;
1130            case 'status':
1131                // Delete status file on shutdown
1132                register_shutdown_function(unlink(...), static::$statisticsFile);
1133                while (1) {
1134                    // Master process will send SIGIOT signal to all child processes.
1135                    posix_kill($masterPid, SIGIOT);
1136                    // Waiting a moment.
1137                    sleep(1);
1138                    // Clear terminal.
1139                    if ($mode === '-d') {
1140                        static::safeEcho("\33[H\33[2J\33(B\33[m", true);
1141                    }
1142                    // Echo status data.
1143                    static::safeEcho(static::formatProcessStatusData());
1144                    if ($mode !== '-d') {
1145                        exit(0);
1146                    }
1147                    static::safeEcho("\nPress Ctrl+C to quit.\n\n");
1148                }
1149            case 'connections':
1150                // Delete status file on shutdown
1151                register_shutdown_function(unlink(...), static::$connectionsFile);
1152                // Master process will send SIGIO signal to all child processes.
1153                posix_kill($masterPid, SIGIO);
1154                // Waiting a moment.
1155                usleep(500000);
1156                // Display statistics data from a disk file.
1157                static::safeEcho(static::formatConnectionStatusData());
1158                exit(0);
1159            case 'restart':
1160            case 'stop':
1161                if ($mode === '-g') {
1162                    static::$gracefulStop = true;
1163                    $sig = SIGQUIT;
1164                    static::log("Workerman[$startFile] is gracefully stopping ...");
1165                } else {
1166                    static::$gracefulStop = false;
1167                    $sig = SIGINT;
1168                    static::log("Workerman[$startFile] is stopping ...");
1169                }
1170                // Send stop signal to master process.
1171                $masterPid && posix_kill($masterPid, $sig);
1172                // Timeout.
1173                $timeout = static::$stopTimeout + 3;
1174                $startTime = time();
1175                // Check master process is still alive?
1176                while (1) {
1177                    $masterIsAlive = $masterPid && posix_kill($masterPid, 0);
1178                    if ($masterIsAlive) {
1179                        // Timeout?
1180                        if (!static::getGracefulStop() && time() - $startTime >= $timeout) {
1181                            static::log("Workerman[$startFile] stop fail");
1182                            exit;
1183                        }
1184                        // Waiting a moment.
1185                        usleep(10000);
1186                        continue;
1187                    }
1188                    // Stop success.
1189                    static::log("Workerman[$startFile] stop success");
1190                    if ($command === 'stop') {
1191                        exit(0);
1192                    }
1193                    if ($mode === '-d') {
1194                        static::$daemonize = true;
1195                    }
1196                    break;
1197                }
1198                break;
1199            case 'reload':
1200                if ($mode === '-g') {
1201                    $sig = SIGUSR2;
1202                } else {
1203                    $sig = SIGUSR1;
1204                }
1205                posix_kill($masterPid, $sig);
1206                exit;
1207            default :
1208                static::safeEcho('Unknown command: ' . $command . "\n");
1209                exit($usage);
1210        }
1211    }
1212
1213    /**
1214     * Get argv.
1215     *
1216     * @return array
1217     */
1218    public static function getArgv(): array
1219    {
1220        global $argv;
1221        return static::$command ? [...$argv, ...explode(' ', static::$command)] : $argv;
1222    }
1223
1224    /**
1225     * Format status data.
1226     *
1227     * @return string
1228     */
1229    protected static function formatProcessStatusData(): string
1230    {
1231        static $totalRequestCache = [];
1232        if (!is_readable(static::$statisticsFile)) {
1233            return '';
1234        }
1235        $info = file(static::$statisticsFile, FILE_IGNORE_NEW_LINES);
1236        if (!$info) {
1237            return '';
1238        }
1239        $statusStr = '';
1240        $currentTotalRequest = [];
1241        $workerInfo = [];
1242        try {
1243            $workerInfo = unserialize($info[0], ['allowed_classes' => false]);
1244        } catch (Throwable) {
1245            // do nothing
1246        }
1247        if (!is_array($workerInfo)) {
1248            $workerInfo = [];
1249        }
1250        ksort($workerInfo, SORT_NUMERIC);
1251        unset($info[0]);
1252        $dataWaitingSort = [];
1253        $readProcessStatus = false;
1254        $totalRequests = 0;
1255        $totalQps = 0;
1256        $totalConnections = 0;
1257        $totalFails = 0;
1258        $totalMemory = 0;
1259        $totalTimers = 0;
1260        $maxLen1 = max(static::getUiColumnLength('maxSocketNameLength'), 2 * static::UI_SAFE_LENGTH);
1261        $maxLen2 = max(static::getUiColumnLength('maxWorkerNameLength'), 2 * static::UI_SAFE_LENGTH);
1262        foreach ($info as $value) {
1263            if (!$readProcessStatus) {
1264                $statusStr .= $value . "\n";
1265                if (preg_match('/^pid.*?memory.*?listening/', $value)) {
1266                    $readProcessStatus = true;
1267                }
1268                continue;
1269            }
1270            if (preg_match('/^[0-9]+/', $value, $pidMath)) {
1271                $pid = $pidMath[0];
1272                $dataWaitingSort[$pid] = $value;
1273                if (preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) {
1274                    $totalMemory += (float)str_ireplace('M', '', $match[1]);
1275                    $maxLen1 = max($maxLen1, strlen($match[2]));
1276                    $maxLen2 = max($maxLen2, strlen($match[3]));
1277                    $totalConnections += (int)$match[4];
1278                    $totalFails += (int)$match[5];
1279                    $totalTimers += (int)$match[6];
1280                    $currentTotalRequest[$pid] = $match[7];
1281                    $totalRequests += (int)$match[7];
1282                }
1283            }
1284        }
1285        foreach ($workerInfo as $pid => $info) {
1286            if (!isset($dataWaitingSort[$pid])) {
1287                $statusStr .= "$pid\t" . str_pad('N/A', 7) . " "
1288                    . str_pad($info['listen'], $maxLen1) . " "
1289                    . str_pad((string)$info['name'], $maxLen2) . " "
1290                    . str_pad('N/A', 11) . " " . str_pad('N/A', 9) . " "
1291                    . str_pad('N/A', 7) . " " . str_pad('N/A', 13) . " N/A    [busy] \n";
1292                continue;
1293            }
1294            //$qps = isset($totalRequestCache[$pid]) ? $currentTotalRequest[$pid]
1295            if (!isset($totalRequestCache[$pid], $currentTotalRequest[$pid])) {
1296                $qps = 0;
1297            } else {
1298                $qps = $currentTotalRequest[$pid] - $totalRequestCache[$pid];
1299                $totalQps += $qps;
1300            }
1301            $statusStr .= $dataWaitingSort[$pid] . " " . str_pad((string)$qps, 6) . " [idle]\n";
1302        }
1303        $totalRequestCache = $currentTotalRequest;
1304        $statusStr .= "---------------------------------------------------PROCESS STATUS--------------------------------------------------------\n";
1305        $statusStr .= "Summary\t" . str_pad($totalMemory . 'M', 7) . " "
1306            . str_pad('-', $maxLen1) . " "
1307            . str_pad('-', $maxLen2) . " "
1308            . str_pad((string)$totalConnections, 11) . " " . str_pad((string)$totalFails, 9) . " "
1309            . str_pad((string)$totalTimers, 7) . " " . str_pad((string)$totalRequests, 13) . " "
1310            . str_pad((string)$totalQps, 6) . " [Summary] \n";
1311        return $statusStr;
1312    }
1313
1314    protected static function formatConnectionStatusData(): string
1315    {
1316        return file_get_contents(static::$connectionsFile);
1317    }
1318
1319    /**
1320     * Install signal handler.
1321     *
1322     * @return void
1323     */
1324    protected static function installSignal(): void
1325    {
1326        if (DIRECTORY_SEPARATOR !== '/') {
1327            return;
1328        }
1329        $signals = [SIGINT, SIGTERM, SIGHUP, SIGTSTP, SIGQUIT, SIGUSR1, SIGUSR2, SIGIOT, SIGIO];
1330        foreach ($signals as $signal) {
1331            pcntl_signal($signal, static::signalHandler(...), false);
1332        }
1333        // ignore
1334        pcntl_signal(SIGPIPE, SIG_IGN, false);
1335    }
1336
1337    /**
1338     * Reinstall signal handler.
1339     *
1340     * @return void
1341     */
1342    protected static function reinstallSignal(): void
1343    {
1344        if (DIRECTORY_SEPARATOR !== '/') {
1345            return;
1346        }
1347        $signals = [SIGINT, SIGTERM, SIGHUP, SIGTSTP, SIGQUIT, SIGUSR1, SIGUSR2, SIGIOT, SIGIO];
1348        foreach ($signals as $signal) {
1349            // Rewrite master process signal.
1350            static::$globalEvent->onSignal($signal, static::signalHandler(...));
1351        }
1352    }
1353
1354    /**
1355     * Signal handler.
1356     *
1357     * @param int $signal
1358     */
1359    protected static function signalHandler(int $signal): void
1360    {
1361        switch ($signal) {
1362            // Stop.
1363            case SIGINT:
1364            case SIGTERM:
1365            case SIGHUP:
1366            case SIGTSTP:
1367                static::$gracefulStop = false;
1368                static::stopAll(0, 'received signal ' . static::getSignalName($signal));
1369                break;
1370            // Graceful stop.
1371            case SIGQUIT:
1372                static::$gracefulStop = true;
1373                static::stopAll(0, 'received signal ' . static::getSignalName($signal));
1374                break;
1375            // Reload.
1376            case SIGUSR2:
1377            case SIGUSR1:
1378                if (static::$status === static::STATUS_RELOADING || static::$status === static::STATUS_SHUTDOWN) {
1379                    return;
1380                }
1381                static::$gracefulStop = $signal === SIGUSR2;
1382                static::$pidsToRestart = static::getAllWorkerPids();
1383                static::reload();
1384                break;
1385            // Show status.
1386            case SIGIOT:
1387                static::writeStatisticsToStatusFile();
1388                break;
1389            // Show connection status.
1390            case SIGIO:
1391                static::writeConnectionsStatisticsToStatusFile();
1392                break;
1393        }
1394    }
1395
1396    /**
1397     * Get signal name.
1398     *
1399     * @param int $signal
1400     * @return string
1401     */
1402    protected static function getSignalName(int $signal): string
1403    {
1404        return match ($signal) {
1405            SIGINT => 'SIGINT',
1406            SIGTERM => 'SIGTERM',
1407            SIGHUP => 'SIGHUP',
1408            SIGTSTP => 'SIGTSTP',
1409            SIGQUIT => 'SIGQUIT',
1410            SIGUSR1 => 'SIGUSR1',
1411            SIGUSR2 => 'SIGUSR2',
1412            SIGIOT => 'SIGIOT',
1413            SIGIO => 'SIGIO',
1414            default => $signal,
1415        };
1416    }
1417
1418    /**
1419     * Run as daemon mode.
1420     */
1421    protected static function daemonize(): void
1422    {
1423        if (!static::$daemonize || DIRECTORY_SEPARATOR !== '/') {
1424            return;
1425        }
1426        umask(0);
1427        $pid = pcntl_fork();
1428        if (-1 === $pid) {
1429            throw new RuntimeException('Fork fail');
1430        } elseif ($pid > 0) {
1431            exit(0);
1432        }
1433        if (-1 === posix_setsid()) {
1434            throw new RuntimeException("Setsid fail");
1435        }
1436        // Fork again avoid SVR4 system regain the control of terminal.
1437        $pid = pcntl_fork();
1438        if (-1 === $pid) {
1439            throw new RuntimeException("Fork fail");
1440        } elseif (0 !== $pid) {
1441            exit(0);
1442        }
1443    }
1444
1445    /**
1446     * Redirect standard output to stdoutFile.
1447     *
1448     * @return void
1449     */
1450    public static function resetStd(): void
1451    {
1452        if (!static::$daemonize || DIRECTORY_SEPARATOR !== '/') {
1453            return;
1454        }
1455
1456        if (is_resource(STDOUT)) {
1457            fclose(STDOUT);
1458        }
1459
1460        if (is_resource(STDERR)) {
1461            fclose(STDERR);
1462        }
1463
1464        if (is_resource(static::$outputStream)) {
1465            fclose(static::$outputStream);
1466        }
1467
1468        set_error_handler(static fn (): bool => true);
1469        $stdOutStream = fopen(static::$stdoutFile, 'a');
1470        restore_error_handler();
1471
1472        if ($stdOutStream === false) {
1473            return;
1474        }
1475
1476        static::$outputStream = $stdOutStream;
1477
1478        // Fix standard output cannot redirect of PHP 8.1.8's bug
1479        if (function_exists('posix_isatty') && posix_isatty(2)) {
1480            ob_start(function (string $string) {
1481                file_put_contents(static::$stdoutFile, $string, FILE_APPEND);
1482            }, 1);
1483        }
1484    }
1485
1486    /**
1487     * Save pid.
1488     */
1489    protected static function saveMasterPid(): void
1490    {
1491        if (DIRECTORY_SEPARATOR !== '/') {
1492            return;
1493        }
1494
1495        static::$masterPid = posix_getpid();
1496        if (false === file_put_contents(static::$pidFile, static::$masterPid)) {
1497            throw new RuntimeException('can not save pid to ' . static::$pidFile);
1498        }
1499    }
1500
1501    /**
1502     * Get all pids of worker processes.
1503     *
1504     * @return array
1505     */
1506    protected static function getAllWorkerPids(): array
1507    {
1508        $pidArray = [];
1509        foreach (static::$pidMap as $workerPidArray) {
1510            foreach ($workerPidArray as $workerPid) {
1511                $pidArray[$workerPid] = $workerPid;
1512            }
1513        }
1514        return $pidArray;
1515    }
1516
1517    /**
1518     * Fork some worker processes.
1519     *
1520     * @return void
1521     */
1522    protected static function forkWorkers(): void
1523    {
1524        if (DIRECTORY_SEPARATOR === '/') {
1525            static::forkWorkersForLinux();
1526        } else {
1527            static::forkWorkersForWindows();
1528        }
1529    }
1530
1531    /**
1532     * Fork some worker processes.
1533     *
1534     * @return void
1535     */
1536    protected static function forkWorkersForLinux(): void
1537    {
1538        foreach (static::$workers as $worker) {
1539            if (static::$status === static::STATUS_STARTING) {
1540                if (empty($worker->name)) {
1541                    $worker->name = $worker->getSocketName();
1542                }
1543            }
1544            while (count(static::$pidMap[$worker->workerId]) < $worker->count) {
1545                static::forkOneWorkerForLinux($worker);
1546            }
1547        }
1548    }
1549
1550    /**
1551     * Fork some worker processes.
1552     *
1553     * @return void
1554     */
1555    protected static function forkWorkersForWindows(): void
1556    {
1557        $files = static::getStartFilesForWindows();
1558        if (count($files) === 1 || in_array('-q', static::getArgv())) {
1559            if (count(static::$workers) > 1) {
1560                static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n");
1561                static::safeEcho("@@@ See https://www.workerman.net/doc/workerman/faq/multi-woker-for-windows.html @@@\r\n");
1562            } elseif (count(static::$workers) <= 0) {
1563                exit("@@@no worker inited@@@\r\n\r\n");
1564            }
1565
1566            reset(static::$workers);
1567            /** @var Worker $worker */
1568            $worker = current(static::$workers);
1569
1570            Timer::delAll();
1571
1572            //Update process state.
1573            static::$status = static::STATUS_RUNNING;
1574
1575            // Register shutdown function for checking errors.
1576            register_shutdown_function(static::checkErrors(...));
1577
1578            // Create a global event loop.
1579            if (static::$globalEvent === null) {
1580                static::$eventLoopClass = $worker->eventLoop ?: static::$eventLoopClass;
1581                static::$globalEvent = new static::$eventLoopClass();
1582                static::$globalEvent->setErrorHandler(function ($exception) {
1583                    static::stopAll(250, $exception);
1584                });
1585            }
1586
1587            // Reinstall signal.
1588            static::reinstallSignal();
1589
1590            // Init Timer.
1591            Timer::init(static::$globalEvent);
1592
1593            restore_error_handler();
1594
1595            // Add an empty timer to prevent the event-loop from exiting.
1596            Timer::add(1000000, function (){});
1597
1598            // Display UI.
1599            static::safeEcho(str_pad($worker->name, 48) . str_pad($worker->getSocketName(), 36) . str_pad('1', 10) . "  [ok]\n");
1600            $worker->listen();
1601            $worker->run();
1602            static::$globalEvent->run();
1603            if (static::$status !== self::STATUS_SHUTDOWN) {
1604                $err = new RuntimeException('event-loop exited');
1605                static::log($err);
1606                exit(250);
1607            }
1608            exit(0);
1609        }
1610
1611        static::$globalEvent = new Select();
1612        static::$globalEvent->setErrorHandler(function ($exception) {
1613            static::stopAll(250, $exception);
1614        });
1615        Timer::init(static::$globalEvent);
1616        foreach ($files as $startFile) {
1617            static::forkOneWorkerForWindows($startFile);
1618        }
1619    }
1620
1621    /**
1622     * Get start files for windows.
1623     *
1624     * @return array
1625     */
1626    public static function getStartFilesForWindows(): array
1627    {
1628        $files = [];
1629        foreach (static::getArgv() as $file) {
1630            if (is_file($file)) {
1631                $files[$file] = $file;
1632            }
1633        }
1634        return $files;
1635    }
1636
1637    /**
1638     * Fork one worker process.
1639     *
1640     * @param string $startFile
1641     */
1642    public static function forkOneWorkerForWindows(string $startFile): void
1643    {
1644        $startFile = realpath($startFile);
1645        $descriptorSpec = [STDIN, STDOUT, STDOUT];
1646        $pipes = [];
1647        $process = proc_open('"' . PHP_BINARY . '" ' . " \"$startFile\" -q", $descriptorSpec, $pipes, null, null, ['bypass_shell' => true]);
1648
1649        if (static::$globalEvent === null) {
1650            static::$globalEvent = new Select();
1651            static::$globalEvent->setErrorHandler(function ($exception) {
1652                static::stopAll(250, $exception);
1653            });
1654            Timer::init(static::$globalEvent);
1655        }
1656
1657        // 保存子进程句柄
1658        static::$processForWindows[$startFile] = [$process, $startFile];
1659    }
1660
1661    /**
1662     * check worker status for windows.
1663     *
1664     * @return void
1665     */
1666    protected static function checkWorkerStatusForWindows(): void
1667    {
1668        foreach (static::$processForWindows as $processData) {
1669            $process = $processData[0];
1670            $startFile = $processData[1];
1671            $status = proc_get_status($process);
1672            if (!$status['running']) {
1673                static::safeEcho("process $startFile terminated and try to restart\n");
1674                proc_close($process);
1675                static::forkOneWorkerForWindows($startFile);
1676            }
1677        }
1678    }
1679
1680    /**
1681     * Fork one worker process.
1682     *
1683     * @param self $worker
1684     */
1685    protected static function forkOneWorkerForLinux(self $worker): void
1686    {
1687        // Get available worker id.
1688        $id = static::getId($worker->workerId, 0);
1689        $pid = pcntl_fork();
1690        // For master process.
1691        if ($pid > 0) {
1692            static::$pidMap[$worker->workerId][$pid] = $pid;
1693            static::$idMap[$worker->workerId][$id] = $pid;
1694        } // For child processes.
1695        elseif (0 === $pid) {
1696            srand();
1697            mt_srand();
1698            static::$gracefulStop = false;
1699            if (static::$status === static::STATUS_STARTING) {
1700                static::resetStd();
1701            }
1702            static::$pidsToRestart = static::$pidMap = [];
1703            // Remove other listener.
1704            foreach (static::$workers as $key => $oneWorker) {
1705                if ($oneWorker->workerId !== $worker->workerId) {
1706                    $oneWorker->unlisten();
1707                    unset(static::$workers[$key]);
1708                }
1709            }
1710            Timer::delAll();
1711
1712            //Update process state.
1713            static::$status = static::STATUS_RUNNING;
1714
1715            // Register shutdown function for checking errors.
1716            register_shutdown_function(static::checkErrors(...));
1717
1718            // Create a global event loop.
1719            if (static::$globalEvent === null) {
1720                static::$eventLoopClass = $worker->eventLoop ?: static::$eventLoopClass;
1721                static::$globalEvent = new static::$eventLoopClass();
1722                static::$globalEvent->setErrorHandler(function ($exception) {
1723                    static::stopAll(250, $exception);
1724                });
1725            }
1726
1727            // Reinstall signal.
1728            static::reinstallSignal();
1729
1730            // Init Timer.
1731            Timer::init(static::$globalEvent);
1732
1733            restore_error_handler();
1734
1735            static::setProcessTitle('WorkerMan: worker process  ' . $worker->name . ' ' . $worker->getSocketName());
1736            $worker->setUserAndGroup();
1737            $worker->id = $id;
1738            $worker->run();
1739            // Main loop.
1740            static::$globalEvent->run();
1741            if (static::$status !== self::STATUS_SHUTDOWN) {
1742                $err = new Exception('event-loop exited');
1743                static::log($err);
1744                exit(250);
1745            }
1746            exit(0);
1747        } else {
1748            throw new RuntimeException("forkOneWorker fail");
1749        }
1750    }
1751
1752    /**
1753     * Get worker id.
1754     *
1755     * @param string $workerId
1756     * @param int $pid
1757     * @return false|int|string
1758     */
1759    protected static function getId(string $workerId, int $pid): false|int|string
1760    {
1761        return array_search($pid, static::$idMap[$workerId]);
1762    }
1763
1764    /**
1765     * Set unix user and group for current process.
1766     *
1767     * @return void
1768     */
1769    public function setUserAndGroup(): void
1770    {
1771        // Get uid.
1772        $userInfo = posix_getpwnam($this->user);
1773        if (!$userInfo) {
1774            static::log("Warning: User $this->user not exists");
1775            return;
1776        }
1777        $uid = $userInfo['uid'];
1778        // Get gid.
1779        if ($this->group) {
1780            $groupInfo = posix_getgrnam($this->group);
1781            if (!$groupInfo) {
1782                static::log("Warning: Group $this->group not exists");
1783                return;
1784            }
1785            $gid = $groupInfo['gid'];
1786        } else {
1787            $gid = $userInfo['gid'];
1788        }
1789
1790        // Set uid and gid.
1791        if ($uid !== posix_getuid() || $gid !== posix_getgid()) {
1792            if (!posix_setgid($gid) || !posix_initgroups($userInfo['name'], $gid) || !posix_setuid($uid)) {
1793                static::log("Warning: change gid or uid fail.");
1794            }
1795        }
1796    }
1797
1798    /**
1799     * Set process name.
1800     *
1801     * @param string $title
1802     * @return void
1803     */
1804    protected static function setProcessTitle(string $title): void
1805    {
1806        set_error_handler(static fn (): bool => true);
1807        cli_set_process_title($title);
1808        restore_error_handler();
1809    }
1810
1811    /**
1812     * Monitor all child processes.
1813     *
1814     * @return void
1815     * @throws Throwable
1816     */
1817    protected static function monitorWorkers(): void
1818    {
1819        if (DIRECTORY_SEPARATOR === '/') {
1820            static::monitorWorkersForLinux();
1821        } else {
1822            static::monitorWorkersForWindows();
1823        }
1824    }
1825
1826    /**
1827     * Monitor all child processes.
1828     *
1829     * @return void
1830     */
1831    protected static function monitorWorkersForLinux(): void
1832    {
1833        static::$status = static::STATUS_RUNNING;
1834        // @phpstan-ignore-next-line While loop condition is always true.
1835        while (1) {
1836            // Calls signal handlers for pending signals.
1837            pcntl_signal_dispatch();
1838            // Suspends execution of the current process until a child has exited, or until a signal is delivered
1839            $status = 0;
1840            $pid = pcntl_wait($status, WUNTRACED);
1841            // Calls signal handlers for pending signals again.
1842            pcntl_signal_dispatch();
1843            // If a child has already exited.
1844            if ($pid > 0) {
1845                // Find out which worker process exited.
1846                foreach (static::$pidMap as $workerId => $workerPidArray) {
1847                    if (isset($workerPidArray[$pid])) {
1848                        $worker = static::$workers[$workerId];
1849                        // Fix exit with status 2 for php8.2
1850                        if ($status === SIGINT && static::$status === static::STATUS_SHUTDOWN) {
1851                            $status = 0;
1852                        }
1853                        // Exit status.
1854                        if ($status !== 0) {
1855                            static::log("worker[$worker->name:$pid] exit with status $status");
1856                        }
1857
1858                        // onWorkerExit
1859                        if (static::$onWorkerExit) {
1860                            try {
1861                                (static::$onWorkerExit)($worker, $status, $pid);
1862                            } catch (Throwable $exception) {
1863                                static::log("worker[$worker->name] onWorkerExit $exception");
1864                            }
1865                        }
1866
1867                        // For Statistics.
1868                        static::$globalStatistics['worker_exit_info'][$workerId][$status] ??= 0;
1869                        static::$globalStatistics['worker_exit_info'][$workerId][$status]++;
1870
1871                        // Clear process data.
1872                        unset(static::$pidMap[$workerId][$pid]);
1873
1874                        // Mark id is available.
1875                        $id = static::getId($workerId, $pid);
1876                        static::$idMap[$workerId][$id] = 0;
1877
1878                        break;
1879                    }
1880                }
1881                // Is still running state then fork a new worker process.
1882                if (static::$status !== static::STATUS_SHUTDOWN) {
1883                    static::forkWorkers();
1884                    // If reloading continue.
1885                    if (isset(static::$pidsToRestart[$pid])) {
1886                        unset(static::$pidsToRestart[$pid]);
1887                        static::reload();
1888                    }
1889                }
1890            }
1891
1892            // If shutdown state and all child processes exited, then master process exit.
1893            if (static::$status === static::STATUS_SHUTDOWN && empty(static::getAllWorkerPids())) {
1894                static::exitAndClearAll();
1895            }
1896        }
1897    }
1898
1899    /**
1900     * Monitor all child processes.
1901     *
1902     * @return void
1903     */
1904    protected static function monitorWorkersForWindows(): void
1905    {
1906        Timer::add(1, static::checkWorkerStatusForWindows(...));
1907
1908        static::$globalEvent->run();
1909    }
1910
1911    /**
1912     * Exit current process.
1913     */
1914    protected static function exitAndClearAll(): void
1915    {
1916        clearstatcache();
1917        foreach (static::$workers as $worker) {
1918            $socketName = $worker->getSocketName();
1919            if ($worker->transport === 'unix' && $socketName) {
1920                [, $address] = explode(':', $socketName, 2);
1921                $address = substr($address, strpos($address, '/') + 2);
1922                if (file_exists($address)) {
1923                    @unlink($address);
1924                }
1925            }
1926        }
1927        if (file_exists(static::$pidFile)) {
1928            @unlink(static::$pidFile);
1929        }
1930        static::log("Workerman[" . basename(static::$startFile) . "] has been stopped");
1931        if (static::$onMasterStop) {
1932            (static::$onMasterStop)();
1933        }
1934        exit(0);
1935    }
1936
1937    /**
1938     * Execute reload.
1939     *
1940     * @return void
1941     */
1942    protected static function reload(): void
1943    {
1944        // For master process.
1945        if (static::$masterPid === posix_getpid()) {
1946            $sig = static::getGracefulStop() ? SIGUSR2 : SIGUSR1;
1947            // Set reloading state.
1948            if (static::$status === static::STATUS_RUNNING) {
1949                static::log("Workerman[" . basename(static::$startFile) . "] reloading");
1950                static::$status = static::STATUS_RELOADING;
1951
1952                static::resetStd();
1953                // Try to emit onMasterReload callback.
1954                if (static::$onMasterReload) {
1955                    try {
1956                        (static::$onMasterReload)();
1957                    } catch (Throwable $e) {
1958                        static::stopAll(250, $e);
1959                    }
1960                    static::initId();
1961                }
1962
1963                // Send reload signal to all child processes.
1964                $reloadablePidArray = [];
1965                foreach (static::$pidMap as $workerId => $workerPidArray) {
1966                    $worker = static::$workers[$workerId];
1967                    if ($worker->reloadable) {
1968                        $reloadablePidArray += $workerPidArray;
1969                        continue;
1970                    }
1971                    // Send reload signal to a worker process which reloadable is false.
1972                    array_walk($workerPidArray, static fn ($pid) => posix_kill($pid, $sig));
1973                }
1974                // Get all pids that are waiting reload.
1975                static::$pidsToRestart = array_intersect(static::$pidsToRestart, $reloadablePidArray);
1976            }
1977
1978            // Reload complete.
1979            if (empty(static::$pidsToRestart)) {
1980                if (static::$status !== static::STATUS_SHUTDOWN) {
1981                    static::$status = static::STATUS_RUNNING;
1982                }
1983                return;
1984            }
1985            // Continue reload.
1986            $oneWorkerPid = current(static::$pidsToRestart);
1987            // Send reload signal to a worker process.
1988            posix_kill($oneWorkerPid, $sig);
1989            // If the process does not exit after stopTimeout seconds try to kill it.
1990            if (!static::getGracefulStop()) {
1991                Timer::add(static::$stopTimeout, posix_kill(...), [$oneWorkerPid, SIGKILL], false);
1992            }
1993        } // For child processes.
1994        else {
1995            reset(static::$workers);
1996            $worker = current(static::$workers);
1997            // Try to emit onWorkerReload callback.
1998            if ($worker->onWorkerReload) {
1999                try {
2000                    ($worker->onWorkerReload)($worker);
2001                } catch (Throwable $e) {
2002                    static::stopAll(250, $e);
2003                }
2004            }
2005
2006            if ($worker->reloadable) {
2007                static::stopAll();
2008            } else {
2009                static::resetStd();
2010            }
2011        }
2012    }
2013
2014    /**
2015     * Stop all.
2016     *
2017     * @param int $code
2018     * @param mixed $log
2019     */
2020    public static function stopAll(int $code = 0, mixed $log = ''): void
2021    {
2022        static::$status = static::STATUS_SHUTDOWN;
2023        // For master process.
2024        if (DIRECTORY_SEPARATOR === '/' && static::$masterPid === posix_getpid()) {
2025            if ($log) {
2026                static::log("Workerman[" . basename(static::$startFile) . "$log");
2027            }
2028            static::log("Workerman[" . basename(static::$startFile) . "] stopping" . ($code ? ", code [$code]" : ''));
2029            $workerPidArray = static::getAllWorkerPids();
2030            // Send stop signal to all child processes.
2031            $sig = static::getGracefulStop() ? SIGQUIT : SIGINT;
2032            foreach ($workerPidArray as $workerPid) {
2033                // Fix exit with status 2 for php8.2
2034                if ($sig === SIGINT && !static::$daemonize) {
2035                    Timer::add(1, posix_kill(...), [$workerPid, SIGINT], false);
2036                } else {
2037                    posix_kill($workerPid, $sig);
2038                }
2039                if (!static::getGracefulStop()) {
2040                    Timer::add(ceil(static::$stopTimeout), posix_kill(...), [$workerPid, SIGKILL], false);
2041                }
2042            }
2043            Timer::add(1, static::checkIfChildRunning(...));
2044        } // For child processes.
2045        else {
2046            if ($code && $log) {
2047                static::log($log);
2048            }
2049            // Execute exit.
2050            $workers = array_reverse(static::$workers);
2051            array_walk($workers, static fn (Worker $worker) => $worker->stop(false));
2052
2053            $callback = function () use ($code, $workers) {
2054                $allWorkerConnectionClosed = true;
2055                if (!static::getGracefulStop()) {
2056                    foreach ($workers as $worker) {
2057                        foreach ($worker->connections as $connection) {
2058                            // Delay closing, waiting for data to be sent.
2059                            if (!$connection->getRecvBufferQueueSize() && !isset($connection->context->closeTimer)) {
2060                                $connection->context->closeTimer = Timer::delay(0.01, static fn () => $connection->close());
2061                            }
2062                            $allWorkerConnectionClosed = false;
2063                        }
2064                    }
2065                }
2066                if ((!static::getGracefulStop() && $allWorkerConnectionClosed) || ConnectionInterface::$statistics['connection_count'] <= 0) {
2067                    static::$globalEvent?->stop();
2068                    try {
2069                        // Ignore Swoole ExitException: Swoole exit.
2070                        exit($code);
2071                        /** @phpstan-ignore-next-line */
2072                    } catch (Throwable) {
2073                        // do nothing
2074                    }
2075                }
2076            };
2077            Timer::repeat(0.01, $callback);
2078        }
2079    }
2080
2081    /**
2082     * check if child processes is really running
2083     */
2084    protected static function checkIfChildRunning(): void
2085    {
2086        foreach (static::$pidMap as $workerId => $workerPidArray) {
2087            foreach ($workerPidArray as $pid => $workerPid) {
2088                if (!posix_kill($pid, 0)) {
2089                    unset(static::$pidMap[$workerId][$pid]);
2090                }
2091            }
2092        }
2093    }
2094
2095    /**
2096     * Get process status.
2097     *
2098     * @return int
2099     */
2100    public static function getStatus(): int
2101    {
2102        return static::$status;
2103    }
2104
2105    /**
2106     * If stop gracefully.
2107     *
2108     * @return bool
2109     */
2110    public static function getGracefulStop(): bool
2111    {
2112        return static::$gracefulStop;
2113    }
2114
2115    /**
2116     *
2117     * Write statistics data to disk.
2118     *
2119     * @return void
2120     */
2121    protected static function writeStatisticsToStatusFile(): void
2122    {
2123        // For master process.
2124        if (static::$masterPid === posix_getpid()) {
2125            $allWorkerInfo = [];
2126            foreach (static::$pidMap as $workerId => $pidArray) {
2127                $worker = static::$workers[$workerId];
2128                foreach ($pidArray as $pid) {
2129                    $allWorkerInfo[$pid] = ['name' => $worker->name, 'listen' => $worker->getSocketName()];
2130                }
2131            }
2132            file_put_contents(static::$statisticsFile, '');
2133            chmod(static::$statisticsFile, 0722);
2134            file_put_contents(static::$statisticsFile, serialize($allWorkerInfo) . "\n", FILE_APPEND);
2135            $loadavg = function_exists('sys_getloadavg') ? array_map(round(...), sys_getloadavg(), [2, 2, 2]) : ['-', '-', '-'];
2136            file_put_contents(static::$statisticsFile,
2137                (static::$daemonize ? "Start worker in DAEMON mode." : "Start worker in DEBUG mode.") . "\n", FILE_APPEND);
2138            file_put_contents(static::$statisticsFile,
2139                "---------------------------------------------------GLOBAL STATUS---------------------------------------------------------\n", FILE_APPEND);
2140            file_put_contents(static::$statisticsFile, static::getVersionLine(), FILE_APPEND);
2141            file_put_contents(static::$statisticsFile, 'start time:' . date('Y-m-d H:i:s',
2142                    static::$globalStatistics['start_timestamp'])
2143                . '   run ' . floor((time() - static::$globalStatistics['start_timestamp']) / (24 * 60 * 60))
2144                . ' days ' . floor(((time() - static::$globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60))
2145                . " hours   " . 'load average: ' . implode(", ", $loadavg) . "\n", FILE_APPEND);
2146            file_put_contents(static::$statisticsFile,
2147                count(static::$pidMap) . ' workers    ' . count(static::getAllWorkerPids()) . " processes\n",
2148                FILE_APPEND);
2149            file_put_contents(static::$statisticsFile,
2150                str_pad('name', static::getUiColumnLength('maxWorkerNameLength')) . "     event-loop     exit_status     exit_count\n", FILE_APPEND);
2151            foreach (static::$pidMap as $workerId => $workerPidArray) {
2152                $worker = static::$workers[$workerId];
2153                if (isset(static::$globalStatistics['worker_exit_info'][$workerId])) {
2154                    foreach (static::$globalStatistics['worker_exit_info'][$workerId] as $workerExitStatus => $workerExitCount) {
2155                        file_put_contents(static::$statisticsFile,
2156                            str_pad($worker->name, static::getUiColumnLength('maxWorkerNameLength')) . "     " .
2157                            str_pad($worker->context->eventLoopName, 14) . " " .
2158                            str_pad((string)$workerExitStatus, 16) . str_pad((string)$workerExitCount, 16) . "\n", FILE_APPEND);
2159                    }
2160                } else {
2161                    file_put_contents(static::$statisticsFile,
2162                        str_pad($worker->name, static::getUiColumnLength('maxWorkerNameLength')) . "     " .
2163                        str_pad($worker->context->eventLoopName, 14) . " " .
2164                        str_pad('0', 16) . str_pad('0', 16) . "\n", FILE_APPEND);
2165                }
2166            }
2167            file_put_contents(static::$statisticsFile,
2168                "---------------------------------------------------PROCESS STATUS--------------------------------------------------------\n",
2169                FILE_APPEND);
2170            file_put_contents(static::$statisticsFile,
2171                "pid\tmemory  " . str_pad('listening', static::getUiColumnLength('maxSocketNameLength')) . " " . str_pad('name',
2172                    static::getUiColumnLength('maxWorkerNameLength')) . " connections " . str_pad('send_fail', 9) . " "
2173                . str_pad('timers', 8) . str_pad('total_request', 13) . " qps    status\n", FILE_APPEND);
2174
2175            foreach (static::getAllWorkerPids() as $workerPid) {
2176                posix_kill($workerPid, SIGIOT);
2177            }
2178            return;
2179        }
2180
2181        reset(static::$workers);
2182        /** @var static $worker */
2183        $worker = current(static::$workers);
2184        $workerStatusStr = posix_getpid() . "\t" . str_pad(round(memory_get_usage() / (1024 * 1024), 2) . "M", 7)
2185            . " " . str_pad($worker->getSocketName(), static::getUiColumnLength('maxSocketNameLength')) . " "
2186            . str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::getUiColumnLength('maxWorkerNameLength'))
2187            . " ";
2188        $workerStatusStr .= str_pad((string)ConnectionInterface::$statistics['connection_count'], 11)
2189            . " " . str_pad((string)ConnectionInterface::$statistics['send_fail'], 9)
2190            . " " . str_pad((string)static::$globalEvent->getTimerCount(), 7)
2191            . " " . str_pad((string)ConnectionInterface::$statistics['total_request'], 13) . "\n";
2192        file_put_contents(static::$statisticsFile, $workerStatusStr, FILE_APPEND);
2193    }
2194
2195    /**
2196     * Get UI column length
2197     *
2198     * @param $name
2199     * @return int
2200     */
2201    protected static function getUiColumnLength($name): int
2202    {
2203        return static::$uiLengthData[$name] ?? 0;
2204    }
2205
2206    /**
2207     * Write statistics data to disk.
2208     *
2209     * @return void
2210     */
2211    protected static function writeConnectionsStatisticsToStatusFile(): void
2212    {
2213        // For master process.
2214        if (static::$masterPid === posix_getpid()) {
2215            file_put_contents(static::$connectionsFile, '');
2216            chmod(static::$connectionsFile, 0722);
2217            file_put_contents(static::$connectionsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", FILE_APPEND);
2218            file_put_contents(static::$connectionsFile, "PID      Worker          CID       Trans   Protocol        ipv4   ipv6   Recv-Q       Send-Q       Bytes-R      Bytes-W       Status         Local Address          Foreign Address\n", FILE_APPEND);
2219            foreach (static::getAllWorkerPids() as $workerPid) {
2220                posix_kill($workerPid, SIGIO);
2221            }
2222            return;
2223        }
2224
2225        // For child processes.
2226        $bytesFormat = function ($bytes) {
2227            if ($bytes > 1024 * 1024 * 1024 * 1024) {
2228                return round($bytes / (1024 * 1024 * 1024 * 1024), 1) . "TB";
2229            }
2230            if ($bytes > 1024 * 1024 * 1024) {
2231                return round($bytes / (1024 * 1024 * 1024), 1) . "GB";
2232            }
2233            if ($bytes > 1024 * 1024) {
2234                return round($bytes / (1024 * 1024), 1) . "MB";
2235            }
2236            if ($bytes > 1024) {
2237                return round($bytes / (1024), 1) . "KB";
2238            }
2239            return $bytes . "B";
2240        };
2241
2242        $pid = posix_getpid();
2243        $str = '';
2244        reset(static::$workers);
2245        $currentWorker = current(static::$workers);
2246        $defaultWorkerName = $currentWorker->name;
2247
2248        foreach (TcpConnection::$connections as $connection) {
2249            /** @var TcpConnection $connection */
2250            $transport = $connection->transport;
2251            $ipv4 = $connection->isIpV4() ? ' 1' : ' 0';
2252            $ipv6 = $connection->isIpV6() ? ' 1' : ' 0';
2253            $recvQ = $bytesFormat($connection->getRecvBufferQueueSize());
2254            $sendQ = $bytesFormat($connection->getSendBufferQueueSize());
2255            $localAddress = trim($connection->getLocalAddress());
2256            $remoteAddress = trim($connection->getRemoteAddress());
2257            $state = $connection->getStatus(false);
2258            $bytesRead = $bytesFormat($connection->bytesRead);
2259            $bytesWritten = $bytesFormat($connection->bytesWritten);
2260            $id = $connection->id;
2261            $protocol = $connection->protocol ?: $connection->transport;
2262            $pos = strrpos($protocol, '\\');
2263            if ($pos) {
2264                $protocol = substr($protocol, $pos + 1);
2265            }
2266            if (strlen($protocol) > 15) {
2267                $protocol = substr($protocol, 0, 13) . '..';
2268            }
2269            $workerName = isset($connection->worker) ? $connection->worker->name : $defaultWorkerName;
2270            if (strlen($workerName) > 14) {
2271                $workerName = substr($workerName, 0, 12) . '..';
2272            }
2273            $str .= str_pad((string)$pid, 9) . str_pad($workerName, 16) . str_pad((string)$id, 10) . str_pad($transport, 8)
2274                . str_pad($protocol, 16) . str_pad($ipv4, 7) . str_pad($ipv6, 7) . str_pad($recvQ, 13)
2275                . str_pad($sendQ, 13) . str_pad($bytesRead, 13) . str_pad($bytesWritten, 13) . ' '
2276                . str_pad($state, 14) . ' ' . str_pad($localAddress, 22) . ' ' . str_pad($remoteAddress, 22) . "\n";
2277        }
2278        if ($str) {
2279            file_put_contents(static::$connectionsFile, $str, FILE_APPEND);
2280        }
2281    }
2282
2283    /**
2284     * Check errors when current process exited.
2285     *
2286     * @return void
2287     */
2288    protected static function checkErrors(): void
2289    {
2290        if (static::STATUS_SHUTDOWN !== static::$status) {
2291            $errorMsg = DIRECTORY_SEPARATOR === '/' ? 'Worker[' . posix_getpid() . '] process terminated' : 'Worker process terminated';
2292            $errors = error_get_last();
2293            if ($errors && ($errors['type'] === E_ERROR ||
2294                    $errors['type'] === E_PARSE ||
2295                    $errors['type'] === E_CORE_ERROR ||
2296                    $errors['type'] === E_COMPILE_ERROR ||
2297                    $errors['type'] === E_RECOVERABLE_ERROR)
2298            ) {
2299                $errorMsg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\"";
2300            }
2301            static::log($errorMsg);
2302        }
2303    }
2304
2305    /**
2306     * Get error message by error code.
2307     *
2308     * @param int $type
2309     * @return string
2310     */
2311    protected static function getErrorType(int $type): string
2312    {
2313        return self::ERROR_TYPE[$type] ?? '';
2314    }
2315
2316    /**
2317     * Log.
2318     *
2319     * @param Stringable|string $msg
2320     * @param bool $decorated
2321     * @return void
2322     */
2323    public static function log(Stringable|string $msg, bool $decorated = false): void
2324    {
2325        $msg = trim((string)$msg);
2326
2327        if (!static::$daemonize) {
2328            static::safeEcho("$msg\n", $decorated);
2329        }
2330
2331        if (isset(static::$logFile)) {
2332            $pid = DIRECTORY_SEPARATOR === '/' ? posix_getpid() : 1;
2333            file_put_contents(static::$logFile, sprintf("%s pid:%d %s\n", date('Y-m-d H:i:s'), $pid, $msg), FILE_APPEND | LOCK_EX);
2334        }
2335    }
2336
2337    /**
2338     * Safe Echo.
2339     *
2340     * @param string $msg
2341     * @param bool $decorated
2342     * @return void
2343     */
2344    public static function safeEcho(string $msg, bool $decorated = false): void
2345    {
2346        if ((static::$outputDecorated ?? false) && $decorated) {
2347            $line = "\033[1A\n\033[K";
2348            $white = "\033[47;30m";
2349            $green = "\033[32;40m";
2350            $end = "\033[0m";
2351        } else {
2352            $line = '';
2353            $white = '';
2354            $green = '';
2355            $end = '';
2356        }
2357
2358        $msg = str_replace(['<n>', '<w>', '<g>'], [$line, $white, $green], $msg);
2359        $msg = str_replace(['</n>', '</w>', '</g>'], $end, $msg);
2360        set_error_handler(static fn (): bool => true);
2361        if (!feof(self::$outputStream)) {
2362            fwrite(self::$outputStream, $msg);
2363            fflush(self::$outputStream);
2364        }
2365        restore_error_handler();
2366    }
2367
2368    /**
2369     * Listen.
2370     */
2371    public function listen(): void
2372    {
2373        if (!$this->socketName) {
2374            return;
2375        }
2376
2377        if (!$this->mainSocket) {
2378
2379            $localSocket = $this->parseSocketAddress();
2380
2381            // Flag.
2382            $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
2383            $errNo = 0;
2384            $errMsg = '';
2385            // SO_REUSEPORT.
2386            if ($this->reusePort) {
2387                stream_context_set_option($this->socketContext, 'socket', 'so_reuseport', 1);
2388            }
2389
2390            // Create an Internet or Unix domain server socket.
2391            $this->mainSocket = stream_socket_server($localSocket, $errNo, $errMsg, $flags, $this->socketContext);
2392            if (!$this->mainSocket) {
2393                throw new RuntimeException($errMsg);
2394            }
2395
2396            if ($this->transport === 'ssl') {
2397                stream_socket_enable_crypto($this->mainSocket, false);
2398            } elseif ($this->transport === 'unix') {
2399                $socketFile = substr($localSocket, 7);
2400                if ($this->user) {
2401                    chown($socketFile, $this->user);
2402                }
2403                if ($this->group) {
2404                    chgrp($socketFile, $this->group);
2405                }
2406            }
2407
2408            // Try to open keepalive for tcp and disable Nagle algorithm.
2409            if (function_exists('socket_import_stream') && self::BUILD_IN_TRANSPORTS[$this->transport] === 'tcp') {
2410                set_error_handler(static fn (): bool => true);
2411                $socket = socket_import_stream($this->mainSocket);
2412                socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
2413                socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);
2414                if (defined('TCP_KEEPIDLE') && defined('TCP_KEEPINTVL') && defined('TCP_KEEPCNT')) {
2415                    socket_set_option($socket, SOL_TCP, TCP_KEEPIDLE, TcpConnection::TCP_KEEPALIVE_INTERVAL);
2416                    socket_set_option($socket, SOL_TCP, TCP_KEEPINTVL, TcpConnection::TCP_KEEPALIVE_INTERVAL);
2417                    socket_set_option($socket, SOL_TCP, TCP_KEEPCNT, 1);
2418                }
2419                restore_error_handler();
2420            }
2421
2422            // Non blocking.
2423            stream_set_blocking($this->mainSocket, false);
2424        }
2425
2426        $this->resumeAccept();
2427    }
2428
2429    /**
2430     * Unlisten.
2431     *
2432     * @return void
2433     */
2434    public function unlisten(): void
2435    {
2436        $this->pauseAccept();
2437        if ($this->mainSocket) {
2438            set_error_handler(static fn (): bool => true);
2439            fclose($this->mainSocket);
2440            restore_error_handler();
2441            $this->mainSocket = null;
2442        }
2443    }
2444
2445    /**
2446     * Check port available.
2447     *
2448     * @return void
2449     */
2450    protected static function checkPortAvailable(): void
2451    {
2452        foreach (static::$workers as $worker) {
2453            $socketName = $worker->getSocketName();
2454            if (DIRECTORY_SEPARATOR === '/'  // if linux
2455                && static::$status === static::STATUS_STARTING // only for starting status
2456                && $worker->transport === 'tcp' // if tcp socket
2457                && !str_starts_with($socketName, 'unix') // if not unix socket
2458                && !str_starts_with($socketName, 'udp')) { // if not udp socket
2459
2460                $address = parse_url($socketName);
2461                if (isset($address['host']) && isset($address['port'])) {
2462                    $address = "tcp://{$address['host']}:{$address['port']}";
2463                    $server = null;
2464                    set_error_handler(function ($code, $msg) {
2465                        throw new RuntimeException($msg);
2466                    });
2467                    $server = stream_socket_server($address, $code, $msg);
2468                    if ($server) {
2469                        fclose($server);
2470                    }
2471                    restore_error_handler();
2472                }
2473            }
2474        }
2475    }
2476
2477    /**
2478     * Parse local socket address.
2479     */
2480    protected function parseSocketAddress(): ?string
2481    {
2482        if (!$this->socketName) {
2483            return null;
2484        }
2485        // Get the application layer communication protocol and listening address.
2486        [$scheme, $address] = explode(':', $this->socketName, 2);
2487        // Check application layer protocol class.
2488        if (!isset(self::BUILD_IN_TRANSPORTS[$scheme])) {
2489            $scheme = ucfirst($scheme);
2490            $this->protocol = $scheme[0] === '\\' ? $scheme : 'Protocols\\' . $scheme;
2491            if (!class_exists($this->protocol)) {
2492                $this->protocol = "Workerman\\Protocols\\$scheme";
2493                if (!class_exists($this->protocol)) {
2494                    throw new RuntimeException("class \\Protocols\\$scheme not exist");
2495                }
2496            }
2497
2498            if (!isset(self::BUILD_IN_TRANSPORTS[$this->transport])) {
2499                throw new RuntimeException('Bad worker->transport ' . var_export($this->transport, true));
2500            }
2501        } else if ($this->transport === 'tcp') {
2502            $this->transport = $scheme;
2503        }
2504        //local socket
2505        return self::BUILD_IN_TRANSPORTS[$this->transport] . ":" . $address;
2506    }
2507
2508    /**
2509     * Pause accept new connections.
2510     *
2511     * @return void
2512     */
2513    public function pauseAccept(): void
2514    {
2515        if (static::$globalEvent !== null && $this->pauseAccept === false && $this->mainSocket !== null) {
2516            static::$globalEvent->offReadable($this->mainSocket);
2517            $this->pauseAccept = true;
2518        }
2519    }
2520
2521    /**
2522     * Resume accept new connections.
2523     *
2524     * @return void
2525     */
2526    public function resumeAccept(): void
2527    {
2528        // Register a listener to be notified when server socket is ready to read.
2529        if (static::$globalEvent !== null && $this->pauseAccept === true && $this->mainSocket !== null) {
2530            if ($this->transport !== 'udp') {
2531                static::$globalEvent->onReadable($this->mainSocket, $this->acceptTcpConnection(...));
2532            } else {
2533                static::$globalEvent->onReadable($this->mainSocket, $this->acceptUdpConnection(...));
2534            }
2535            $this->pauseAccept = false;
2536        }
2537    }
2538
2539    /**
2540     * Get socket name.
2541     *
2542     * @return string
2543     */
2544    public function getSocketName(): string
2545    {
2546        return $this->socketName ? lcfirst($this->socketName) : 'none';
2547    }
2548
2549    /**
2550     * Run worker instance.
2551     *
2552     * @return void
2553     * @throws Throwable
2554     */
2555    public function run(): void
2556    {
2557        $this->listen();
2558
2559        if (!$this->onWorkerStart) {
2560            return;
2561        }
2562
2563        // Try to emit onWorkerStart callback.
2564        $callback = function() {
2565            try {
2566                ($this->onWorkerStart)($this);
2567            } catch (Throwable $e) {
2568                // Avoid rapid infinite loop exit.
2569                sleep(1);
2570                static::stopAll(250, $e);
2571            }
2572        };
2573
2574        switch (Worker::$eventLoopClass) {
2575            case Swoole::class:
2576            case Swow::class:
2577            case Fiber::class:
2578                Coroutine::create($callback);
2579                break;
2580            default:
2581                (new \Fiber($callback))->start();
2582        }
2583    }
2584
2585    /**
2586     * Stop current worker instance.
2587     *
2588     * @param bool $force
2589     * @return void
2590     */
2591    public function stop(bool $force = true): void
2592    {
2593        if ($this->stopping === true) {
2594            return;
2595        }
2596        // Try to emit onWorkerStop callback.
2597        if ($this->onWorkerStop) {
2598            try {
2599                ($this->onWorkerStop)($this);
2600            } catch (Throwable $e) {
2601                static::log($e);
2602            }
2603        }
2604        // Remove listener for server socket.
2605        $this->unlisten();
2606        // Close all connections for the worker.
2607        if (!static::getGracefulStop()) {
2608            foreach ($this->connections as $connection) {
2609                if ($force || !$connection->getRecvBufferQueueSize()) {
2610                    $connection->close();
2611                }
2612            }
2613        }
2614        // Clear callback.
2615        $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null;
2616        $this->stopping  = true;
2617    }
2618
2619    /**
2620     * Accept a connection.
2621     *
2622     * @param resource $socket
2623     * @return void
2624     */
2625    protected function acceptTcpConnection(mixed $socket): void
2626    {
2627        // Accept a connection on server socket.
2628        set_error_handler(static fn (): bool => true);
2629        $newSocket = stream_socket_accept($socket, 0, $remoteAddress);
2630        restore_error_handler();
2631
2632        // Thundering herd.
2633        if (!$newSocket) {
2634            return;
2635        }
2636
2637        // TcpConnection.
2638        $connection = new TcpConnection(static::$globalEvent, $newSocket, $remoteAddress);
2639        $this->connections[$connection->id] = $connection;
2640        $connection->worker = $this;
2641        $connection->protocol = $this->protocol;
2642        $connection->transport = $this->transport;
2643        $connection->onMessage = $this->onMessage;
2644        $connection->onClose = $this->onClose;
2645        $connection->onError = $this->onError;
2646        $connection->onBufferDrain = $this->onBufferDrain;
2647        $connection->onBufferFull = $this->onBufferFull;
2648
2649        // Try to emit onConnect callback.
2650        if ($this->onConnect) {
2651            try {
2652                ($this->onConnect)($connection);
2653            } catch (Throwable $e) {
2654                static::stopAll(250, $e);
2655            }
2656        }
2657    }
2658
2659    /**
2660     * For udp package.
2661     *
2662     * @param resource $socket
2663     * @return void
2664     */
2665    protected function acceptUdpConnection(mixed $socket): void
2666    {
2667        set_error_handler(static fn (): bool => true);
2668        $recvBuffer = stream_socket_recvfrom($socket, UdpConnection::MAX_UDP_PACKAGE_SIZE, 0, $remoteAddress);
2669        restore_error_handler();
2670        if (false === $recvBuffer || empty($remoteAddress)) {
2671            return;
2672        }
2673        // UdpConnection.
2674        $connection = new UdpConnection($socket, $remoteAddress);
2675        $connection->protocol = $this->protocol;
2676        $messageCallback = $this->onMessage;
2677        if ($messageCallback) {
2678            try {
2679                if ($this->protocol !== null) {
2680                    $parser = $this->protocol;
2681                    if ($parser && method_exists($parser, 'input')) {
2682                        while ($recvBuffer !== '') {
2683                            $len = $parser::input($recvBuffer, $connection);
2684                            if ($len === 0) {
2685                                return;
2686                            }
2687                            $package = substr($recvBuffer, 0, $len);
2688                            $recvBuffer = substr($recvBuffer, $len);
2689                            $data = $parser::decode($package, $connection);
2690                            if ($data === false) {
2691                                continue;
2692                            }
2693                            $messageCallback($connection, $data);
2694                        }
2695                    } else {
2696                        $data = $parser::decode($recvBuffer, $connection);
2697                        // Discard bad packets.
2698                        if ($data === false) {
2699                            return;
2700                        }
2701                        $messageCallback($connection, $data);
2702                    }
2703                } else {
2704                    $messageCallback($connection, $recvBuffer);
2705                }
2706                ConnectionInterface::$statistics['total_request']++;
2707            } catch (Throwable $e) {
2708                static::stopAll(250, $e);
2709            }
2710        }
2711    }
2712
2713    /**
2714     * Check master process is alive
2715     *
2716     * @param int $masterPid
2717     * @return bool
2718     */
2719    protected static function checkMasterIsAlive(int $masterPid): bool
2720    {
2721        if (empty($masterPid)) {
2722            return false;
2723        }
2724
2725        $masterIsAlive = posix_kill($masterPid, 0) && posix_getpid() !== $masterPid;
2726        if (!$masterIsAlive) {
2727            static::log("Master pid:$masterPid is not alive");
2728            return false;
2729        }
2730
2731        $cmdline = "/proc/$masterPid/cmdline";
2732        if (!is_readable($cmdline)) {
2733            return true;
2734        }
2735
2736        $content = file_get_contents($cmdline);
2737        if (empty($content)) {
2738            return true;
2739        }
2740
2741        return str_contains($content, 'WorkerMan') || str_contains($content, 'php');
2742    }
2743
2744    /**
2745     * If worker is running.
2746     *
2747     * @return bool
2748     */
2749    public static function isRunning(): bool
2750    {
2751        return Worker::$status !== Worker::STATUS_INITIAL;
2752    }
2753}