Completed
Push — master ( cfd1ff...e920d0 )
by Hu
02:46
created

Process::isStopped()   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

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
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);
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...
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);
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...
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 8
                $this->term_signal = pcntl_wtermsig($status);
252 8
            }
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 8
                $this->errno = pcntl_get_last_error();
261 8
                $this->errmsg = pcntl_strerror($this->errno);
262
            }
263 51
            if (pcntl_wifsignaled($status)) {
264 8
                $this->if_signal = true;
265 8
            } 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;
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...
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);
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...
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
}