Completed
Push — master ( e920d0...b06e30 )
by Hu
02:38
created

Process::isStopped()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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