Completed
Push — master ( 0ed363...3da2c5 )
by Hu
03:43
created

Process::name()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

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