Completed
Push — master ( bd10e4...c7dba8 )
by Hu
14:29 queued 12:10
created

Process::on()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 8
ccs 4
cts 5
cp 0.8
rs 9.4286
cc 2
eloc 4
nc 2
nop 2
crap 2.032
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
     * event name of before process start
75
     */
76
    const BEFORE_START = 'beforeStart';
77
78
    /**
79
     * event name of before process exit
80
     */
81
    const BEFORE_EXIT = 'beforeExit';
82
83
    /**
84
     * event name of after process exit
85
     */
86
    const AFTER_FINISHED = 'afterFinished';
87
88
89
    /**
90
     * @param string $execution it can be a Runnable object, callback function or null
91
     * @param null $name process name,you can manager the process by it's name.
92
     */
93 52
    public function __construct($execution = null, $name = null)
94
    {
95 52
        if (!is_null($execution) && $execution instanceof Runnable) {
1 ignored issue
show
Coding Style introduced by
As per coding-style, please use === null instead of is_null.
Loading history...
96 9
            $this->runnable = $execution;
97 52
        } elseif (!is_null($execution) && is_callable($execution)) {
1 ignored issue
show
Coding Style introduced by
As per coding-style, please use === null instead of is_null.
Loading history...
98 40
            $this->runnable = $execution;
99 46
        } elseif (!is_null($execution)) {
1 ignored issue
show
Coding Style introduced by
As per coding-style, please use === null instead of is_null.
Loading history...
100
            $message = "param execution is not a object of Runnable or callable";
101
            throw new \InvalidArgumentException($message);
102
        } else {
103 9
            Utils::checkOverwriteRunMethod(get_class($this));
104
        }
105 52
        if (!is_null($name)) {
1 ignored issue
show
Coding Style introduced by
As per coding-style, please use === null instead of is_null.
Loading history...
106 3
            $this->name = $name;
107 3
        }
108
109 52
        $this->initStatus();
110 52
    }
111
112
    /**
113
     * get pid
114
     *
115
     * @return int
116
     */
117 9
    public function getPid()
118
    {
119 9
        return $this->pid;
120
    }
121
122
    /**
123
     * get or set name
124
     *
125
     * @param string|null $name
126
     * @return mixed
127
     */
128 3
    public function name($name = null)
129
    {
130 3
        if (!is_null($name)) {
1 ignored issue
show
Coding Style introduced by
As per coding-style, please use === null instead of is_null.
Loading history...
131
            $this->name = $name;
132
        } else {
133 3
            return $this->name;
134
        }
135
    }
136
137
    /**
138
     * if the process is running
139
     *
140
     * @return bool
141
     */
142 46
    public function isRunning()
143
    {
144 46
        $this->updateStatus();
145 46
        return $this->running;
146
    }
147
148
    /**
149
     * if the process is stopped
150
     *
151
     * @return bool
152
     */
153 3
    public function isStopped()
154
    {
155 3
        if (is_null($this->errno)) {
1 ignored issue
show
Coding Style introduced by
As per coding-style, please use === null instead of is_null.
Loading history...
156 3
            return false;
157
        }
158
159 3
        return true;
160
    }
161
162
    /**
163
     * if the process is started
164
     *
165
     * @return bool
166
     */
167 3
    public function hasStarted()
168
    {
169 3
        return $this->started;
170
    }
171
172
    /**
173
     * get pcntl errno
174
     *
175
     * @return int
176
     */
177 9
    public function errno()
178
    {
179 9
        return $this->errno;
180
    }
181
182
    /**
183
     * get pcntl errmsg
184
     *
185
     * @return string
186
     */
187 6
    public function errmsg()
188
    {
189 6
        return $this->errmsg;
190
    }
191
192 3
    public function ifSignal()
193
    {
194 3
        return $this->if_signal;
195
    }
196
197
    /**
198
     * start the sub process
199
     * and run the callback
200
     *
201
     * @return string pid
202
     */
203 49
    public function start()
204
    {
205 49
        if (!empty($this->pid) && $this->isRunning()) {
206
            throw new \LogicException("the process is already running");
207
        }
208
209 49
        $callback = $this->getCallable();
210
211 49
        $pid = pcntl_fork();
212 49
        if ($pid < 0) {
213
            throw new \RuntimeException("fork error");
214 49
        } elseif ($pid > 0) {
215 49
            $this->pid = $pid;
216 49
            $this->running = true;
217 49
            $this->started = true;
218 49
        } else {
219
            $this->pid = getmypid();
220
            $this->signal();
221
            foreach ($this->signal_handlers as $signal => $handler) {
222
                pcntl_signal($signal, $handler);
223
            }
224
225
            if (array_key_exists(self::BEFORE_START, $this->callbacks)) {
226
                $result = call_user_func($this->callbacks[self::BEFORE_START]);
227
                if ($result !== true) {
228
                    exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method start() contains an exit expression.

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.

Loading history...
229
                }
230
            }
231
232
            $result = call_user_func($callback);
233
234
            if (array_key_exists(self::AFTER_FINISHED, $this->callbacks)) {
235
                call_user_func($this->callbacks[self::AFTER_FINISHED], $result);
236
            }
237
238
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method start() contains an exit expression.

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.

Loading history...
239
        }
240 49
    }
241
242
    /**
243
     * kill self
244
     *
245
     * @param bool|true $block
246
     * @param int $signal
247
     */
248 12
    public function shutdown($block = true, $signal = SIGTERM)
249
    {
250 12
        if (empty($this->pid)) {
251
            $message = "the process pid is null, so maybe the process is not started";
252
            throw new \LogicException($message);
253
        }
254 12
        if (!$this->isRunning()) {
255
            throw new \LogicException("the process is not running");
256
        }
257 12
        if (!posix_kill($this->pid, $signal)) {
258
            throw new \RuntimeException("kill son process failed");
259
        }
260
261 12
        $this->updateStatus($block);
262 12
    }
263
264
    /**
265
     * waiting for the sub process exit
266
     *
267
     * @param bool|true $block if block the process
268
     * @param int $sleep default 0.1s check sub process status
269
     * every $sleep milliseconds.
270
     */
271 25
    public function wait($block = true, $sleep = 100000)
272
    {
273 25
        while (true) {
274 25
            if ($this->isRunning() === false) {
275 25
                return;
276
            }
277 22
            if (!$block) {
278
                break;
279
            }
280 22
            usleep($sleep);
281 22
        }
282
    }
283
284
    /**
285
     * register callback functions
286
     *
287
     * @param $event
288
     * @param $function
289
     */
290 3
    public function on($event, $function)
291
    {
292 3
        if (!is_callable($function)) {
293
            throw new \LogicException("the callback function is not callable");
294
        }
295
296 3
        $this->callbacks[$event] = $function;
297 3
    }
298
299
    /**
300
     * register sub process signal handler,
301
     * when the sub process start, the handlers will be registered
302
     *
303
     * @param $signal
304
     * @param callable $handler
305
     */
306
    public function registerSignalHandler($signal, callable $handler)
307
    {
308
        $this->signal_handlers[$signal] = $handler;
309
    }
310
311
    /**
312
     * you should overwrite this function
313
     * if you do not use the Runnable or callback.
314
     */
315
    public function run()
316
    {
317
    }
318
319
    /**
320
     * update the process status
321
     *
322
     * @param bool $block
323
     */
324 46
    protected function updateStatus($block = false)
325
    {
326 46
        if ($this->running !== true) {
327 24
            return;
328
        }
329
330 46
        if ($block) {
331 12
            $res = pcntl_waitpid($this->pid, $status);
0 ignored issues
show
Bug introduced by
The variable $status does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
332 12
        } else {
333 46
            $res = pcntl_waitpid($this->pid, $status, WNOHANG | WUNTRACED);
334
        }
335
336 46
        if ($res === -1) {
337
            $message = "pcntl_waitpid failed. the process maybe available";
338
            throw new \RuntimeException($message);
339 46
        } elseif ($res === 0) {
340 46
            $this->running = true;
341 46
        } else {
342 43
            if (pcntl_wifsignaled($status)) {
343 6
                $this->term_signal = pcntl_wtermsig($status);
344 6
            }
345 43
            if (pcntl_wifstopped($status)) {
346
                $this->stop_signal = pcntl_wstopsig($status);
347
            }
348 43
            if (pcntl_wifexited($status)) {
349 37
                $this->errno = pcntl_wexitstatus($status);
350 37
                $this->errmsg = pcntl_strerror($this->errno);
351 37
            } else {
352 6
                $this->errno = pcntl_get_last_error();
353 6
                $this->errmsg = pcntl_strerror($this->errno);
354
            }
355 43
            if (pcntl_wifsignaled($status)) {
356 6
                $this->if_signal = true;
357 6
            } else {
358 37
                $this->if_signal = false;
359
            }
360
361 43
            $this->running = false;
362
        }
363 46
    }
364
365
    /**
366
     * register signal SIGTERM handler,
367
     * when the parent process call shutdown and use the default signal,
368
     * this handler will be triggered
369
     */
370
    protected function signal()
371
    {
372
        pcntl_signal(SIGTERM, function () {
373
            if (!array_key_exists(self::BEFORE_EXIT, $this->callbacks)) {
374
                exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method signal() contains an exit expression.

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.

Loading history...
375
            }
376
377
            $result = call_user_func($this->callbacks[self::BEFORE_EXIT]);
378
            if ($result === true) {
379
                exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method signal() contains an exit expression.

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.

Loading history...
380
            }
381
        });
382
    }
383
384
    /**
385
     * get sub process callback
386
     *
387
     * @return array|callable|null
388
     */
389 49
    protected function getCallable()
390
    {
391 49
        $callback = null;
0 ignored issues
show
Unused Code introduced by
$callback is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
392 49
        if (is_object($this->runnable) && $this->runnable instanceof Runnable) {
393 9
            $callback = array($this->runnable, 'run');
394 49
        } elseif (is_callable($this->runnable)) {
395 40
            $callback = $this->runnable;
396 40
        } else {
397 6
            $callback = array($this, 'run');
398
        }
399
400 49
        return $callback;
401
    }
402
403
    /**
404
     * init process status
405
     */
406 52
    protected function initStatus()
407
    {
408 52
        $this->pid = null;
409 52
        $this->running = null;
410 52
        $this->term_signal = null;
411 52
        $this->stop_signal = null;
412 52
        $this->errno = null;
413 52
        $this->errmsg = null;
414
    }
415
}