Completed
Pull Request — master (#234)
by Дмитрий
07:45 queued 03:41
created

Generic::sigwait()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
dl 0
loc 22
rs 8.9197
eloc 12
c 1
b 1
f 0
nc 6
nop 2
1
<?php
2
namespace PHPDaemon\Thread;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\Exceptions\ClearStack;
7
8
/**
9
 * Thread
10
 *
11
 * @package Core
12
 *
13
 * @author  Vasily Zorin <[email protected]>
14
 */
15
abstract class Generic
16
{
17
    use \PHPDaemon\Traits\ClassWatchdog;
18
    use \PHPDaemon\Traits\StaticObjectWatchdog;
19
20
    /**
21
     * Process identificator
22
     * @var int
23
     */
24
    public $id;
25
26
    /**
27
     * PID
28
     * @var int
29
     */
30
    protected $pid;
31
32
    /**
33
     * Is this thread shutdown?
34
     * @var boolean
35
     */
36
    protected $shutdown = false;
37
38
    /**
39
     * Is this thread terminated?
40
     * @var boolean
41
     */
42
    protected $terminated = false;
43
44
    /**
45
     * Collections of childrens
46
     * @var array|Collection[]
47
     */
48
    protected $collections = [];
49
50
    /**
51
     * Storage of signal handler events
52
     * @var array
53
     */
54
    protected $sigEvents = [];
55
56
    /**
57
     * Hash of known signal [no => name, ...]
58
     * @var array
59
     */
60
    public static $signals = [
61
        SIGHUP    => 'SIGHUP',
62
        SIGSYS    => 'SIGSYS',
63
        SIGPIPE   => 'SIGPIPE',
64
        SIGALRM   => 'SIGALRM',
65
        SIGTERM   => 'SIGTERM',
66
        SIGSTOP   => 'SIGSTOP',
67
        SIGINT   => 'SIGINT',
68
        SIGCHLD   => 'SIGCHLD',
69
        SIGTTIN   => 'SIGTTIN',
70
        SIGTTOU   => 'SIGTTOU',
71
        SIGIO     => 'SIGIO',
72
        SIGXCPU   => 'SIGXCPU',
73
        SIGXFSZ   => 'SIGXFSZ',
74
        SIGVTALRM => 'SIGVTALRM',
75
        SIGPROF   => 'SIGPROF',
76
        SIGWINCH  => 'SIGWINCH',
77
        SIGUSR1   => 'SIGUSR1',
78
        SIGUSR2   => 'SIGUSR2',
79
        SIGTSTP      => 'SIGTSTP',
80
    ];
81
82
    /**
83
     * Get PID of this Thread
84
     * @return integer
85
     */
86
    public function getPid()
87
    {
88
        return $this->pid;
89
    }
90
91
    /**
92
     * Set ID of this Thread
93
     * @param integer Id
94
     * @return void
95
     */
96
    public function setId($id)
97
    {
98
        $this->id = $id;
99
    }
100
101
    /**
102
     * Get ID of this Thread
103
     * @return integer
104
     */
105
    public function getId()
106
    {
107
        return $this->id;
108
    }
109
110
    /**
111
     * Is this thread terminated?
112
     * @return boolean
113
     */
114
    public function isTerminated()
115
    {
116
        return $this->terminated;
117
    }
118
119
    protected function onTerminated()
120
    {
121
    }
122
123
    public function setTerminated()
124
    {
125
        $this->terminated = true;
126
        $this->onTerminated();
127
    }
128
129
    /**
130
     * Invoke magic method
131
     * @return void
132
     */
133
134
    public function __invoke()
135
    {
136
        $this->run();
137
        $this->shutdown();
138
    }
139
140
    /**
141
     * Register signals.
142
     * @return void
143
     */
144
    protected function registerEventSignals()
145
    {
146
        if (!$this->eventBase) {
0 ignored issues
show
Bug introduced by
The property eventBase does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
147
            return;
148
        }
149
        foreach (self::$signals as $no => $name) {
150
            if ($name === 'SIGKILL' || $name == 'SIGSTOP') {
151
                continue;
152
            }
153
154
            $ev = \Event::signal($this->eventBase, $no, [$this, 'eventSighandler'], [$no]);
155
            if (!$ev) {
156
                $this->log('Cannot event_set for ' . $name . ' signal');
0 ignored issues
show
Documentation Bug introduced by
The method log does not exist on object<PHPDaemon\Thread\Generic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
157
            }
158
            $ev->add();
159
            $this->sigEvents[$no] = $ev;
160
        }
161
    }
162
163
    /**
164
     * Unregister signals.
165
     * @return void
166
     */
167
    protected function unregisterSignals()
168
    {
169
        foreach ($this->sigEvents as $no => $ev) {
170
            $ev->free();
171
            unset($this->sigEvents[$no]);
172
        }
173
    }
174
175
    /**
176
     * Called when a signal caught through libevent.
177
     * @param integer Signal's number.
178
     * @param integer Events.
179
     * @param mixed   Argument.
180
     * @return void
181
     */
182
    public function eventSighandler($fd, $arg)
183
    {
184
        $this->sighandler($arg[0]);
185
    }
186
187
    /**
188
     * Run thread process
189
     * @return void
190
     */
191
    protected function run()
192
    {
193
    }
194
195
    /**
196
     * If true, we do not register signals automatically at start
197
     * @var boolean
198
     */
199
    protected $delayedSigReg = false;
200
201
    /**
202
     * Registers signals
203
     * @return void
204
     */
205
    protected function registerSignals()
206
    {
207
        foreach (self::$signals as $no => $name) {
208
            if (($name === 'SIGKILL') || ($name == 'SIGSTOP')) {
209
                continue;
210
            }
211
212
            if (!\pcntl_signal($no, [$this, 'sighandler'], true)) {
213
                $this->log('Cannot assign ' . $name . ' signal');
0 ignored issues
show
Documentation Bug introduced by
The method log does not exist on object<PHPDaemon\Thread\Generic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
214
            }
215
        }
216
    }
217
218
    /**
219
     * Starts the process
220
     * @return void
221
     */
222
    public function start($clearstack = true)
223
    {
224
        $pid = \pcntl_fork();
225
226
        if ($pid === -1) {
227
            throw new \Exception('Could not fork');
228
        } elseif ($pid === 0) { // we are the child
229
            $thread      = $this;
230
            $thread->pid = \posix_getpid();
231
            if (!$thread->delayedSigReg) {
232
                $thread->registerSignals();
233
            }
234
            if ($clearstack) {
235
                throw new ClearStack('', 0, $thread);
236
            } else {
237
                $thread->run();
238
                $thread->shutdown();
239
            }
240
        } else { // we are the master
241
            $this->pid = $pid;
242
        }
243
    }
244
245
    /**
246
     * Called when the signal is caught
247
     * @param integer Signal's number
248
     * @return void
249
     */
250
    public function sighandler($signo)
251
    {
252
        if (!isset(self::$signals[$signo])) {
253
            $this->log('caught unknown signal #' . $signo);
0 ignored issues
show
Documentation Bug introduced by
The method log does not exist on object<PHPDaemon\Thread\Generic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
254
            return;
255
        }
256
        if (method_exists($this, $m = strtolower(self::$signals[$signo]))) {
257
            $this->$m();
258
        } elseif (method_exists($this, 'sigunknown')) {
259
            $this->sigunknown($signo);
0 ignored issues
show
Documentation Bug introduced by
The method sigunknown does not exist on object<PHPDaemon\Thread\Generic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
260
        }
261
    }
262
263
    /**
264
     * Shutdowns the current process properly
265
     * @return void
266
     */
267
    protected function shutdown()
268
    {
269
        \posix_kill(\posix_getppid(), SIGCHLD);
270
        exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method shutdown() 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...
271
    }
272
273
    /**
274
     * Sends the signal to parent process
275
     * @param integer Signal's number
276
     * @return boolean Success
277
     */
278
    protected function backsig($sig)
279
    {
280
        return \posix_kill(\posix_getppid(), $sig);
281
    }
282
283
    /**
284
     * Delays the process execution for the given number of seconds
285
     * @param integer Sleep time in seconds
286
     * @return boolean Success
287
     */
288
    public function sleep($s)
289
    {
290
        static $interval = 0.2;
291
        $n = $s / $interval;
292
293
        for ($i = 0; $i < $n; ++$i) {
294
            if ($this->shutdown) {
295
                return false;
296
            }
297
298
            \usleep($interval * 1000000);
299
        }
300
301
        return true;
302
    }
303
304
    /**
305
     * Called when the signal SIGCHLD caught
306
     * @return void
307
     */
308
    protected function sigchld()
309
    {
310
        $this->waitPid();
311
    }
312
313
    /**
314
     * Called when the signal SIGTERM caught
315
     * @return void
316
     */
317
    protected function sigterm()
318
    {
319
        exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sigterm() 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...
320
    }
321
322
    /**
323
     * Called when the signal SIGINT caught
324
     * @return void
325
     */
326
    protected function sigint()
327
    {
328
        exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sigint() 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...
329
    }
330
331
    /**
332
     * Called when the signal SIGTERM caught
333
     * @return void
334
     */
335
    protected function sigquit()
336
    {
337
        $this->shutdown = true;
338
    }
339
340
    /**
341
     * Called when the signal SIGKILL caught
342
     * @return void
343
     */
344
    protected function sigkill()
345
    {
346
        exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sigkill() 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...
347
    }
348
349
    /**
350
     * Terminates the process
351
     * @param boolean Kill?
352
     * @return void
353
     */
354
    public function stop($kill = false)
355
    {
356
        $this->shutdown = true;
357
        \posix_kill($this->pid, $kill ? SIGKILL : SIGTERM);
358
    }
359
360
    /**
361
     * Checks for SIGCHLD
362
     * @return boolean Success
363
     */
364
    protected function waitPid()
365
    {
366
        start:
367
        $pid = \pcntl_waitpid(-1, $status, WNOHANG);
368
        if ($pid > 0) {
369
            foreach ($this->collections as $col) {
370
                foreach ($col->threads as $k => $t) {
371
                    if ($t->pid === $pid) {
372
                        $t->setTerminated();
373
                        unset($col->threads[$k]);
374
                        goto start;
375
                    }
376
                }
377
            }
378
        }
379
380
        return false;
381
    }
382
383
    /**
384
     * Sends arbitrary signal to the process
385
     * @param integer Signal's number
386
     * @return boolean Success
387
     */
388
    public function signal($sig)
389
    {
390
        return \posix_kill($this->pid, $sig);
391
    }
392
393
    /**
394
     * Checks if this process does exist
395
     * @return boolean Success
396
     */
397
    public function ifExists()
398
    {
399
        return \posix_kill($this->pid, 0);
400
    }
401
402
    /**
403
     * Checks if given process ID does exist
404
     * @static
405
     * @param integer PID
406
     * @param integer $pid
407
     * @return boolean Success
408
     */
409
    public static function ifExistsByPid($pid)
410
    {
411
        return \posix_kill($pid, 0);
412
    }
413
414
    /**
415
     * Waits until children is alive
416
     * @param boolean $check
417
     * @return void
418
     */
419
    protected function waitAll($check)
420
    {
421
        do {
422
            $n = 0;
423
424
            foreach ($this->collections as &$col) {
425
                $n += $col->removeTerminated($check);
426
            }
427
            if (!$this->waitPid()) {
428
                $this->sigwait(0, 20000);
429
            }
430
        } while ($n > 0);
431
    }
432
433
    /**
434
     * Sets a title of the current process
435
     *
436
     * @param string $title
437
     * @return boolean Success
438
     */
439
    protected function setTitle($title)
440
    {
441
        return cli_set_process_title($title);
442
    }
443
444
    /**
445
     * Returns a title of the current process
446
     *
447
     * @return string
448
     */
449
    protected function getTitle()
450
    {
451
        return cli_get_process_title();
452
    }
453
454
    /**
455
     * Waits for signals, with a timeout
456
     * @param int Seconds
457
     * @param int Nanoseconds
458
     * @return boolean Success
459
     */
460
    protected function sigwait($sec = 0, $nano = 0.3e9)
461
    {
462
        $siginfo = null;
463
464
        if (!function_exists('pcntl_sigtimedwait')) {
465
            $signo   = $this->sigtimedwait(array_keys(static::$signals), $siginfo, $sec, $nano);
466
        } else {
467
            $signo   = @\pcntl_sigtimedwait(array_keys(static::$signals), $siginfo, $sec, $nano);
468
        }
469
470
        if (is_bool($signo)) {
471
            return $signo;
472
        }
473
474
        if ($signo > 0) {
475
            $this->sighandler($signo);
476
477
            return true;
478
        }
479
480
        return false;
481
    }
482
483
    /**
484
     * Implementation of pcntl_sigtimedwait for Mac.
485
     *
486
     * @param array Signal
487
     * @param null|array SigInfo
488
     * @param int Seconds
489
     * @param int Nanoseconds
490
     * @param integer $sec
491
     * @param double $nano
492
     * @return boolean Success
493
     */
494
    protected function sigtimedwait($signals, $siginfo, $sec, $nano)
0 ignored issues
show
Unused Code introduced by
The parameter $signals is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $siginfo is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
495
    {
496
        \pcntl_signal_dispatch();
497
        if (\time_nanosleep($sec, $nano) === true) {
498
            return false;
499
        }
500
        \pcntl_signal_dispatch();
501
        return true;
502
    }
503
}
504