Skip to content

Commit acc9af5

Browse files
committed
update: update some proc util methods
1 parent 5044aad commit acc9af5

File tree

3 files changed

+440
-348
lines changed

3 files changed

+440
-348
lines changed

src/Proc/PcntlFunc.php

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Toolkit\Sys\Proc;
4+
5+
use Closure;
6+
use RuntimeException;
7+
use Toolkit\Stdlib\Helper\Assert;
8+
use Toolkit\Stdlib\OS;
9+
use function function_exists;
10+
use function getmypid;
11+
use function pcntl_alarm;
12+
use function pcntl_async_signals;
13+
use function pcntl_fork;
14+
use function pcntl_signal;
15+
use function pcntl_signal_dispatch;
16+
use function pcntl_signal_get_handler;
17+
use function pcntl_sigwaitinfo;
18+
use function pcntl_waitpid;
19+
use function pcntl_wexitstatus;
20+
use function posix_getpid;
21+
use function posix_setsid;
22+
use function time;
23+
use function usleep;
24+
use const WNOHANG;
25+
26+
/**
27+
* class PcntlFunc
28+
*
29+
* @author inhere
30+
*/
31+
class PcntlFunc
32+
{
33+
/**
34+
* @return bool
35+
*/
36+
public static function hasPcntl(): bool
37+
{
38+
return !OS::isWindows() && function_exists('pcntl_fork');
39+
}
40+
41+
public static function assertPcntl(): void
42+
{
43+
if (!self::hasPcntl()) {
44+
throw new RuntimeException('the extension "pcntl" is required');
45+
}
46+
}
47+
48+
/**
49+
* get current process id
50+
*
51+
* @return int
52+
*/
53+
public static function getPid(): int
54+
{
55+
return function_exists('posix_getpid') ? posix_getpid() : getmypid();
56+
}
57+
58+
/**
59+
* install signal
60+
*
61+
* @param int $signal e.g: SIGTERM SIGINT(Ctrl+C) SIGUSR1 SIGUSR2 SIGHUP
62+
* @param callable $handler
63+
*
64+
* @return bool
65+
*/
66+
public static function installSignal(int $signal, callable $handler): bool
67+
{
68+
return pcntl_signal($signal, $handler, false);
69+
}
70+
71+
/**
72+
* dispatch signal
73+
*
74+
* @return bool
75+
*/
76+
public static function dispatchSignal(): bool
77+
{
78+
// receive and dispatch signal
79+
return pcntl_signal_dispatch();
80+
}
81+
82+
/**
83+
* Get the current handler for specified signal.
84+
*
85+
* @param int $signal
86+
*
87+
* @return resource|false
88+
*/
89+
public static function getSignalHandler(int $signal)
90+
{
91+
return pcntl_signal_get_handler($signal);
92+
}
93+
94+
/**
95+
* Enable/disable asynchronous signal handling or return the old setting
96+
*
97+
* @param bool|null $on bool - Enable or disable; null - Return old setting.
98+
*
99+
* @return bool
100+
*/
101+
public static function asyncSignal(bool $on = null): bool
102+
{
103+
return pcntl_async_signals($on);
104+
}
105+
106+
/**
107+
* @return int
108+
*/
109+
public static function clearAlarm(): int
110+
{
111+
return pcntl_alarm(-1);
112+
}
113+
114+
/**********************************************************************
115+
* create child process `pcntl`
116+
*********************************************************************/
117+
118+
/**
119+
* Fork/create a child process.
120+
*
121+
* **Example:**
122+
*
123+
* ```php
124+
* $info = ProcessUtil::fork(function(int $pid, int $id) {
125+
* // do something...
126+
*
127+
* // exit worker
128+
* exit(0);
129+
* });
130+
* ```
131+
*
132+
* @param callable(int, int):void $onStart Will running on the child process start.
133+
* @param null|callable(int):void $onForkError Call on fork process error
134+
* @param int $id The process index number. when use `forks()`
135+
*
136+
* @return array{id: int, pid: int, startAt: int}
137+
*/
138+
public static function fork(callable $onStart, callable $onForkError = null, int $id = 0): array
139+
{
140+
$info = [];
141+
$pid = pcntl_fork();
142+
143+
// at parent, get forked child info
144+
if ($pid > 0) {
145+
$info = [
146+
'id' => $id,
147+
'pid' => $pid, // child pid
148+
'startAt' => time(),
149+
];
150+
} elseif ($pid === 0) {
151+
// at child
152+
$workerPid = self::getPid();
153+
$onStart($workerPid, $id);
154+
} elseif ($onForkError) {
155+
$onForkError($pid);
156+
} else {
157+
throw new RuntimeException('Fork child process failed!');
158+
}
159+
160+
return $info;
161+
}
162+
163+
/**
164+
* Alias of fork()
165+
*
166+
* @param callable $onStart
167+
* @param callable|null $onError
168+
* @param int $id
169+
*
170+
* @return array|false
171+
* @see ProcessUtil::fork()
172+
*/
173+
public static function create(callable $onStart, callable $onError = null, int $id = 0): bool|array
174+
{
175+
return self::fork($onStart, $onError, $id);
176+
}
177+
178+
/**
179+
* Daemon, detach and run in the background
180+
*
181+
* @param Closure|null $beforeQuit
182+
*
183+
* @return int Return new process PID
184+
* @throws RuntimeException
185+
*/
186+
public static function daemonRun(Closure $beforeQuit = null): int
187+
{
188+
if (!self::hasPcntl()) {
189+
return 0;
190+
}
191+
192+
// umask(0);
193+
$pid = pcntl_fork();
194+
switch ($pid) {
195+
case 0: // at new process
196+
$pid = self::getPid();
197+
if (posix_setsid() < 0) {
198+
throw new RuntimeException('posix_setsid() execute failed! exiting');
199+
}
200+
201+
// chdir('/');
202+
// umask(0);
203+
break;
204+
case -1: // fork failed.
205+
throw new RuntimeException('Fork new process is failed! exiting');
206+
default: // at parent
207+
if ($beforeQuit) {
208+
$beforeQuit($pid);
209+
}
210+
exit(0);
211+
}
212+
213+
return $pid;
214+
}
215+
216+
/**
217+
* Alias of forks()
218+
*
219+
* @param int $number
220+
* @param callable $onStart
221+
* @param callable|null $onForkError
222+
*
223+
* @return array
224+
* @see ProcessUtil::forks()
225+
*/
226+
public static function multi(int $number, callable $onStart, callable $onForkError = null): array
227+
{
228+
return self::forks($number, $onStart, $onForkError);
229+
}
230+
231+
/**
232+
* fork/create multi child processes.
233+
*
234+
* @param int $number
235+
* @param callable $onStart Will running on the child processes.
236+
* @param callable|null $onForkError on fork process error
237+
*
238+
* @return array<int, array{id: int, pid: int, startTime: int}>
239+
* @throws RuntimeException
240+
*/
241+
public static function forks(int $number, callable $onStart, callable $onForkError = null): array
242+
{
243+
Assert::intShouldGt0($number, 'process number', true);
244+
245+
$pidAry = [];
246+
for ($id = 0; $id < $number; $id++) {
247+
$info = self::fork(function () use ($onStart) {
248+
$onStart();
249+
exit(0);
250+
}, $onForkError, $id);
251+
252+
// log
253+
$pidAry[$info['pid']] = $info;
254+
}
255+
256+
return $pidAry;
257+
}
258+
259+
/**
260+
* Wait all child processes exit.
261+
*
262+
* @param callable(int, int, int):void $onChildExit On child process exited callback.
263+
*/
264+
public static function wait(callable $onChildExit): void
265+
{
266+
$status = 0;
267+
268+
// pid < 0:子进程都没了
269+
// pid > 0:捕获到一个子进程退出的情况
270+
// pid = 0:没有捕获到退出的子进程
271+
while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) >= 0) {
272+
if ($pid) {
273+
// TIP: get signal
274+
// $singal = pcntl_wifsignaled($status);
275+
276+
// handler(int $pid, int $exitCode, int $status)
277+
$onChildExit($pid, pcntl_wexitstatus($status), $status);
278+
} else {
279+
// sleep 50ms
280+
usleep(50000);
281+
}
282+
}
283+
}
284+
}

src/Proc/ProcFunc.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
namespace Toolkit\Sys\Proc;
1111

1212
use RuntimeException;
13+
use function array_keys;
14+
use function fclose;
1315
use function proc_close;
1416
use function proc_get_status;
1517
use function proc_open;
1618
use function proc_terminate;
19+
use function stream_get_contents;
1720

1821
/**
1922
* Class ProcFunc
@@ -55,14 +58,47 @@ public static function open(
5558
*
5659
* @param resource $process
5760
*
58-
* @return array
61+
* @return array{command:string,pid:int,running:bool,signaled:bool,stopped:bool,exitcode:int,termsig:int,stopsig:int}
5962
* @see https://www.php.net/manual/en/function.proc-get-status.php
6063
*/
6164
public static function getStatus($process): array
6265
{
6366
return proc_get_status($process);
6467
}
6568

69+
/**
70+
* @param resource $pipe
71+
*
72+
* @return string
73+
*/
74+
public static function readClosePipe($pipe): string
75+
{
76+
return self::readPipe($pipe, true);
77+
}
78+
79+
/**
80+
* @param resource $pipe
81+
* @param bool $close
82+
*
83+
* @return string
84+
*/
85+
public static function readPipe($pipe, bool $close = false): string
86+
{
87+
$output = stream_get_contents($pipe);
88+
$close && fclose($pipe);
89+
return $output;
90+
}
91+
92+
/**
93+
* @param array $pipes
94+
*/
95+
public static function closePipes(array $pipes): void
96+
{
97+
foreach ($pipes as $pipe) {
98+
fclose($pipe);
99+
}
100+
}
101+
66102
/**
67103
* Close a process opened by `proc_open` and return the exit code of that process
68104
*

0 commit comments

Comments
 (0)