1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by PhpStorm. |
4
|
|
|
* User: Jenner |
5
|
|
|
* Date: 2015/8/12 |
6
|
|
|
* Time: 15:25 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Jenner\SimpleFork; |
10
|
|
|
|
11
|
|
|
class Process |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* @var Runnable|callable |
15
|
|
|
*/ |
16
|
|
|
protected $runnable; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var int |
20
|
|
|
*/ |
21
|
|
|
protected $pid = 0; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var string custom process name |
25
|
|
|
*/ |
26
|
|
|
protected $name = null; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var bool if the process is started |
30
|
|
|
*/ |
31
|
|
|
protected $started = false; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var bool |
35
|
|
|
*/ |
36
|
|
|
protected $running = false; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var int the signal which made the process terminate |
40
|
|
|
*/ |
41
|
|
|
protected $term_signal = null; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var int the signal which made the process stop |
45
|
|
|
*/ |
46
|
|
|
protected $stop_signal = null; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var int error code |
50
|
|
|
*/ |
51
|
|
|
protected $errno = null; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var string error message |
55
|
|
|
*/ |
56
|
|
|
protected $errmsg = null; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var bool |
60
|
|
|
*/ |
61
|
|
|
protected $if_signal = false; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @var array |
65
|
|
|
*/ |
66
|
|
|
protected $callbacks = array(); |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @var array signal handlers |
70
|
|
|
*/ |
71
|
|
|
protected $signal_handlers = array(); |
72
|
|
|
|
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @param string $execution it can be a Runnable object, callback function or null |
76
|
|
|
* @param null $name process name,you can manager the process by it's name. |
77
|
|
|
*/ |
78
|
60 |
|
public function __construct($execution = null, $name = null) |
79
|
|
|
{ |
80
|
60 |
|
if (!is_null($execution) && $execution instanceof Runnable) { |
81
|
9 |
|
$this->runnable = $execution; |
82
|
60 |
|
} elseif (!is_null($execution) && is_callable($execution)) { |
83
|
45 |
|
$this->runnable = $execution; |
84
|
54 |
|
} elseif (!is_null($execution)) { |
85
|
|
|
$message = "param execution is not a object of Runnable or callable"; |
86
|
|
|
throw new \InvalidArgumentException($message); |
87
|
|
|
} else { |
88
|
12 |
|
Utils::checkOverwriteRunMethod(get_class($this)); |
89
|
|
|
} |
90
|
57 |
|
if (!is_null($name)) { |
91
|
3 |
|
$this->name = $name; |
92
|
3 |
|
} |
93
|
|
|
|
94
|
57 |
|
$this->initStatus(); |
95
|
57 |
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* init process status |
99
|
|
|
*/ |
100
|
57 |
|
protected function initStatus() |
101
|
|
|
{ |
102
|
57 |
|
$this->pid = null; |
103
|
57 |
|
$this->running = null; |
104
|
57 |
|
$this->term_signal = null; |
105
|
57 |
|
$this->stop_signal = null; |
106
|
57 |
|
$this->errno = null; |
107
|
57 |
|
$this->errmsg = null; |
108
|
57 |
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* get pid |
112
|
|
|
* |
113
|
|
|
* @return int |
114
|
|
|
*/ |
115
|
9 |
|
public function getPid() |
116
|
|
|
{ |
117
|
9 |
|
return $this->pid; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* get or set name |
122
|
|
|
* |
123
|
|
|
* @param string|null $name |
124
|
|
|
* @return mixed |
125
|
|
|
*/ |
126
|
3 |
|
public function name($name = null) |
127
|
|
|
{ |
128
|
3 |
|
if (!is_null($name)) { |
129
|
|
|
$this->name = $name; |
130
|
|
|
} else { |
131
|
3 |
|
return $this->name; |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* if the process is stopped |
137
|
|
|
* |
138
|
|
|
* @return bool |
139
|
|
|
*/ |
140
|
3 |
|
public function isStopped() |
141
|
|
|
{ |
142
|
3 |
|
if (is_null($this->errno)) { |
143
|
3 |
|
return false; |
144
|
|
|
} |
145
|
|
|
|
146
|
3 |
|
return true; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* if the process is started |
151
|
|
|
* |
152
|
|
|
* @return bool |
153
|
|
|
*/ |
154
|
15 |
|
public function isStarted() |
155
|
|
|
{ |
156
|
15 |
|
return $this->started; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* get pcntl errno |
161
|
|
|
* |
162
|
|
|
* @return int |
163
|
|
|
*/ |
164
|
9 |
|
public function errno() |
165
|
|
|
{ |
166
|
9 |
|
return $this->errno; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* get pcntl errmsg |
171
|
|
|
* |
172
|
|
|
* @return string |
173
|
|
|
*/ |
174
|
6 |
|
public function errmsg() |
175
|
|
|
{ |
176
|
6 |
|
return $this->errmsg; |
177
|
|
|
} |
178
|
|
|
|
179
|
3 |
|
public function ifSignal() |
180
|
|
|
{ |
181
|
3 |
|
return $this->if_signal; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* start the sub process |
186
|
|
|
* and run the callback |
187
|
|
|
* |
188
|
|
|
* @return string pid |
189
|
|
|
*/ |
190
|
54 |
|
public function start() |
191
|
|
|
{ |
192
|
54 |
|
if (!empty($this->pid) && $this->isRunning()) { |
193
|
|
|
throw new \LogicException("the process is already running"); |
194
|
|
|
} |
195
|
|
|
|
196
|
54 |
|
$callback = $this->getCallable(); |
197
|
|
|
|
198
|
54 |
|
$pid = pcntl_fork(); |
199
|
54 |
|
if ($pid < 0) { |
200
|
|
|
throw new \RuntimeException("fork error"); |
201
|
54 |
|
} elseif ($pid > 0) { |
202
|
54 |
|
$this->pid = $pid; |
203
|
54 |
|
$this->running = true; |
204
|
54 |
|
$this->started = true; |
205
|
54 |
|
} else { |
206
|
|
|
$this->pid = getmypid(); |
207
|
|
|
$this->signal(); |
208
|
|
|
foreach ($this->signal_handlers as $signal => $handler) { |
209
|
|
|
pcntl_signal($signal, $handler); |
210
|
|
|
} |
211
|
|
|
call_user_func($callback); |
212
|
|
|
exit(0); |
|
|
|
|
213
|
|
|
} |
214
|
54 |
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* if the process is running |
218
|
|
|
* |
219
|
|
|
* @return bool |
220
|
|
|
*/ |
221
|
54 |
|
public function isRunning() |
222
|
|
|
{ |
223
|
54 |
|
$this->updateStatus(); |
224
|
54 |
|
return $this->running; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* update the process status |
229
|
|
|
* |
230
|
|
|
* @param bool $block |
231
|
|
|
*/ |
232
|
54 |
|
protected function updateStatus($block = false) |
233
|
|
|
{ |
234
|
54 |
|
if ($this->running !== true) { |
235
|
24 |
|
return; |
236
|
|
|
} |
237
|
|
|
|
238
|
54 |
|
if ($block) { |
239
|
12 |
|
$res = pcntl_waitpid($this->pid, $status); |
|
|
|
|
240
|
12 |
|
} else { |
241
|
54 |
|
$res = pcntl_waitpid($this->pid, $status, WNOHANG | WUNTRACED); |
242
|
|
|
} |
243
|
|
|
|
244
|
54 |
|
if ($res === -1) { |
245
|
|
|
$message = "pcntl_waitpid failed. the process maybe available"; |
246
|
|
|
throw new \RuntimeException($message); |
247
|
54 |
|
} elseif ($res === 0) { |
248
|
54 |
|
$this->running = true; |
249
|
54 |
|
} else { |
250
|
51 |
|
if (pcntl_wifsignaled($status)) { |
251
|
7 |
|
$this->term_signal = pcntl_wtermsig($status); |
252
|
7 |
|
} |
253
|
51 |
|
if (pcntl_wifstopped($status)) { |
254
|
|
|
$this->stop_signal = pcntl_wstopsig($status); |
255
|
|
|
} |
256
|
51 |
|
if (pcntl_wifexited($status)) { |
257
|
45 |
|
$this->errno = pcntl_wexitstatus($status); |
258
|
45 |
|
$this->errmsg = pcntl_strerror($this->errno); |
259
|
45 |
|
} else { |
260
|
7 |
|
$this->errno = pcntl_get_last_error(); |
261
|
7 |
|
$this->errmsg = pcntl_strerror($this->errno); |
262
|
|
|
} |
263
|
51 |
|
if (pcntl_wifsignaled($status)) { |
264
|
7 |
|
$this->if_signal = true; |
265
|
7 |
|
} else { |
266
|
45 |
|
$this->if_signal = false; |
267
|
|
|
} |
268
|
|
|
|
269
|
51 |
|
$this->running = false; |
270
|
|
|
} |
271
|
54 |
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* get sub process callback |
275
|
|
|
* |
276
|
|
|
* @return array|callable|null |
277
|
|
|
*/ |
278
|
54 |
|
protected function getCallable() |
279
|
|
|
{ |
280
|
54 |
|
$callback = null; |
|
|
|
|
281
|
54 |
|
if (is_object($this->runnable) && $this->runnable instanceof Runnable) { |
282
|
9 |
|
$callback = array($this->runnable, 'run'); |
283
|
54 |
|
} elseif (is_callable($this->runnable)) { |
284
|
45 |
|
$callback = $this->runnable; |
285
|
45 |
|
} else { |
286
|
6 |
|
$callback = array($this, 'run'); |
287
|
|
|
} |
288
|
|
|
|
289
|
54 |
|
return $callback; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* register signal SIGTERM handler, |
294
|
|
|
* when the parent process call shutdown and use the default signal, |
295
|
|
|
* this handler will be triggered |
296
|
|
|
*/ |
297
|
|
|
protected function signal() |
298
|
|
|
{ |
299
|
|
|
pcntl_signal(SIGTERM, function () { |
300
|
|
|
exit(0); |
|
|
|
|
301
|
|
|
}); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* kill self |
306
|
|
|
* |
307
|
|
|
* @param bool|true $block |
308
|
|
|
* @param int $signal |
309
|
|
|
*/ |
310
|
12 |
|
public function shutdown($block = true, $signal = SIGTERM) |
311
|
|
|
{ |
312
|
12 |
|
if (empty($this->pid)) { |
313
|
|
|
$message = "the process pid is null, so maybe the process is not started"; |
314
|
|
|
throw new \LogicException($message); |
315
|
|
|
} |
316
|
12 |
|
if (!$this->isRunning()) { |
317
|
|
|
throw new \LogicException("the process is not running"); |
318
|
|
|
} |
319
|
12 |
|
if (!posix_kill($this->pid, $signal)) { |
320
|
|
|
throw new \RuntimeException("kill son process failed"); |
321
|
|
|
} |
322
|
|
|
|
323
|
12 |
|
$this->updateStatus($block); |
324
|
12 |
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* waiting for the sub process exit |
328
|
|
|
* |
329
|
|
|
* @param bool|true $block if block the process |
330
|
|
|
* @param int $sleep default 0.1s check sub process status |
331
|
|
|
* every $sleep milliseconds. |
332
|
|
|
*/ |
333
|
33 |
|
public function wait($block = true, $sleep = 100000) |
334
|
|
|
{ |
335
|
33 |
|
while (true) { |
336
|
33 |
|
if ($this->isRunning() === false) { |
337
|
33 |
|
return; |
338
|
|
|
} |
339
|
30 |
|
if (!$block) { |
340
|
|
|
break; |
341
|
|
|
} |
342
|
30 |
|
usleep($sleep); |
343
|
30 |
|
} |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* register sub process signal handler, |
348
|
|
|
* when the sub process start, the handlers will be registered |
349
|
|
|
* |
350
|
|
|
* @param $signal |
351
|
|
|
* @param callable $handler |
352
|
|
|
*/ |
353
|
|
|
public function registerSignalHandler($signal, callable $handler) |
354
|
|
|
{ |
355
|
|
|
$this->signal_handlers[$signal] = $handler; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* you should overwrite this function |
360
|
|
|
* if you do not use the Runnable or callback. |
361
|
|
|
*/ |
362
|
|
|
public function run() |
363
|
|
|
{ |
364
|
|
|
} |
365
|
|
|
} |
An exit expression should only be used in rare cases. For example, if you write a short command line script.
In most cases however, using an
exit
expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.