Completed
Push — master ( e42de6...8350e2 )
by Hu
02:44
created

Process::shutdown()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.024

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 15
ccs 6
cts 10
cp 0.6
rs 9.2
cc 4
eloc 9
nc 4
nop 2
crap 5.024
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
     * get pid
99
     *
100
     * @return int
101
     */
102 9
    public function getPid()
103
    {
104 9
        return $this->pid;
105
    }
106
107
    /**
108
     * get or set name
109
     *
110
     * @param string|null $name
111
     * @return mixed
112
     */
113 3
    public function name($name = null)
114
    {
115 3
        if (!is_null($name)) {
116
            $this->name = $name;
117
        } else {
118 3
            return $this->name;
119
        }
120
    }
121
122
    /**
123
     * if the process is running
124
     *
125
     * @return bool
126
     */
127 54
    public function isRunning()
128
    {
129 54
        $this->updateStatus();
130 54
        return $this->running;
131
    }
132
133
    /**
134
     * if the process is stopped
135
     *
136
     * @return bool
137
     */
138 3
    public function isStopped()
139
    {
140 3
        if (is_null($this->errno)) {
141 3
            return false;
142
        }
143
144 3
        return true;
145
    }
146
147
    /**
148
     * if the process is started
149
     *
150
     * @return bool
151
     */
152 15
    public function isStarted()
153
    {
154 15
        return $this->started;
155
    }
156
157
    /**
158
     * get pcntl errno
159
     *
160
     * @return int
161
     */
162 9
    public function errno()
163
    {
164 9
        return $this->errno;
165
    }
166
167
    /**
168
     * get pcntl errmsg
169
     *
170
     * @return string
171
     */
172 6
    public function errmsg()
173
    {
174 6
        return $this->errmsg;
175
    }
176
177 3
    public function ifSignal()
178
    {
179 3
        return $this->if_signal;
180
    }
181
182
    /**
183
     * start the sub process
184
     * and run the callback
185
     *
186
     * @return string pid
187
     */
188 54
    public function start()
189
    {
190 54
        if (!empty($this->pid) && $this->isRunning()) {
191
            throw new \LogicException("the process is already running");
192
        }
193
194 54
        $callback = $this->getCallable();
195
196 54
        $pid = pcntl_fork();
197 54
        if ($pid < 0) {
198
            throw new \RuntimeException("fork error");
199 54
        } elseif ($pid > 0) {
200 54
            $this->pid = $pid;
201 54
            $this->running = true;
202 54
            $this->started = true;
203 54
        } else {
204
            $this->pid = getmypid();
205
            $this->signal();
206
            foreach ($this->signal_handlers as $signal => $handler) {
207
                pcntl_signal($signal, $handler);
208
            }
209
            call_user_func($callback);
210
            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...
211
        }
212 54
    }
213
214
    /**
215
     * kill self
216
     *
217
     * @param bool|true $block
218
     * @param int $signal
219
     */
220 12
    public function shutdown($block = true, $signal = SIGTERM)
221
    {
222 12
        if (empty($this->pid)) {
223
            $message = "the process pid is null, so maybe the process is not started";
224
            throw new \LogicException($message);
225
        }
226 12
        if (!$this->isRunning()) {
227
            throw new \LogicException("the process is not running");
228
        }
229 12
        if (!posix_kill($this->pid, $signal)) {
230
            throw new \RuntimeException("kill son process failed");
231
        }
232
233 12
        $this->updateStatus($block);
234 12
    }
235
236
    /**
237
     * waiting for the sub process exit
238
     *
239
     * @param bool|true $block if block the process
240
     * @param int $sleep default 0.1s check sub process status
241
     * every $sleep milliseconds.
242
     */
243 33
    public function wait($block = true, $sleep = 100000)
244
    {
245 33
        while (true) {
246 33
            if ($this->isRunning() === false) {
247 33
                return;
248
            }
249 30
            if (!$block) {
250
                break;
251
            }
252 30
            usleep($sleep);
253 30
        }
254
    }
255
256
    /**
257
     * register sub process signal handler,
258
     * when the sub process start, the handlers will be registered
259
     *
260
     * @param $signal
261
     * @param callable $handler
262
     */
263
    public function registerSignalHandler($signal, callable $handler)
264
    {
265
        $this->signal_handlers[$signal] = $handler;
266
    }
267
268
    /**
269
     * you should overwrite this function
270
     * if you do not use the Runnable or callback.
271
     */
272
    public function run()
273
    {
274
    }
275
276
    /**
277
     * update the process status
278
     *
279
     * @param bool $block
280
     */
281 54
    protected function updateStatus($block = false)
282
    {
283 54
        if ($this->running !== true) {
284 24
            return;
285
        }
286
287 54
        if ($block) {
288 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...
289 12
        } else {
290 54
            $res = pcntl_waitpid($this->pid, $status, WNOHANG | WUNTRACED);
291
        }
292
293 54
        if ($res === -1) {
294
            $message = "pcntl_waitpid failed. the process maybe available";
295
            throw new \RuntimeException($message);
296 54
        } elseif ($res === 0) {
297 54
            $this->running = true;
298 54
        } else {
299 51
            if (pcntl_wifsignaled($status)) {
300 6
                $this->term_signal = pcntl_wtermsig($status);
301 6
            }
302 51
            if (pcntl_wifstopped($status)) {
303
                $this->stop_signal = pcntl_wstopsig($status);
304
            }
305 51
            if (pcntl_wifexited($status)) {
306 45
                $this->errno = pcntl_wexitstatus($status);
307 45
                $this->errmsg = pcntl_strerror($this->errno);
308 45
            } else {
309 6
                $this->errno = pcntl_get_last_error();
310 6
                $this->errmsg = pcntl_strerror($this->errno);
311
            }
312 51
            if (pcntl_wifsignaled($status)) {
313 6
                $this->if_signal = true;
314 6
            } else {
315 45
                $this->if_signal = false;
316
            }
317
318 51
            $this->running = false;
319
        }
320 54
    }
321
322
    /**
323
     * register signal SIGTERM handler,
324
     * when the parent process call shutdown and use the default signal,
325
     * this handler will be triggered
326
     */
327
    protected function signal()
328
    {
329
        pcntl_signal(SIGTERM, function () {
330
            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...
331
        });
332
    }
333
334
    /**
335
     * get sub process callback
336
     *
337
     * @return array|callable|null
338
     */
339 54
    protected function getCallable()
340
    {
341 54
        $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...
342 54
        if (is_object($this->runnable) && $this->runnable instanceof Runnable) {
343 9
            $callback = array($this->runnable, 'run');
344 54
        } elseif (is_callable($this->runnable)) {
345 45
            $callback = $this->runnable;
346 45
        } else {
347 6
            $callback = array($this, 'run');
348
        }
349
350 54
        return $callback;
351
    }
352
353
    /**
354
     * init process status
355
     */
356 57
    protected function initStatus()
357
    {
358 57
        $this->pid = null;
359 57
        $this->running = null;
360 57
        $this->term_signal = null;
361 57
        $this->stop_signal = null;
362 57
        $this->errno = null;
363 57
        $this->errmsg = null;
364
    }
365
}