Generic::setTitle()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
nc 1
nop 1
1
<?php
2
namespace PHPDaemon\Thread;
3
4
use PHPDaemon\Core\EventLoop;
5
use PHPDaemon\Exceptions\ClearStack;
6
7
/**
8
 * Thread
9
 *
10
 * @package Core
11
 *
12
 * @author  Vasily Zorin <[email protected]>
13
 */
14
abstract class Generic
15
{
16
    use \PHPDaemon\Traits\ClassWatchdog;
17
    use \PHPDaemon\Traits\StaticObjectWatchdog;
18
19
    /**
20
     * Hash of known signal [no => name, ...]
21
     * @var array
22
     */
23
    public static $signals = [
24
        SIGHUP => 'SIGHUP',
25
        SIGSYS => 'SIGSYS',
26
        SIGPIPE => 'SIGPIPE',
27
        SIGALRM => 'SIGALRM',
28
        SIGTERM => 'SIGTERM',
29
        SIGSTOP => 'SIGSTOP',
30
        SIGINT => 'SIGINT',
31
        SIGCHLD => 'SIGCHLD',
32
        SIGTTIN => 'SIGTTIN',
33
        SIGTTOU => 'SIGTTOU',
34
        SIGIO => 'SIGIO',
35
        SIGXCPU => 'SIGXCPU',
36
        SIGXFSZ => 'SIGXFSZ',
37
        SIGVTALRM => 'SIGVTALRM',
38
        SIGPROF => 'SIGPROF',
39
        SIGWINCH => 'SIGWINCH',
40
        SIGUSR1 => 'SIGUSR1',
41
        SIGUSR2 => 'SIGUSR2',
42
        SIGTSTP => 'SIGTSTP',
43
    ];
44
    /**
45
     * Process identificator
46
     * @var int
47
     */
48
    public $id;
49
    /**
50
     * PID
51
     * @var int
52
     */
53
    protected $pid;
54
    /**
55
     * Is this thread shutdown?
56
     * @var boolean
57
     */
58
    protected $shutdown = false;
59
    /**
60
     * Is this thread terminated?
61
     * @var boolean
62
     */
63
    protected $terminated = false;
64
    /**
65
     * Collections of childrens
66
     * @var array|Collection[]
67
     */
68
    protected $collections = [];
69
    /**
70
     * Storage of signal handler events
71
     * @var array
72
     */
73
    protected $sigEvents = [];
74
    /**
75
     * If true, we do not register signals automatically at start
76
     * @var boolean
77
     */
78
    protected $delayedSigReg = false;
79
80
    /**
81
     * Checks if given process ID does exist
82
     * @static
83
     * @param integer PID
84
     * @param integer $pid
85
     * @return boolean Success
86
     */
87
    public static function ifExistsByPid($pid)
88
    {
89
        return \posix_kill($pid, 0);
90
    }
91
92
    /**
93
     * Get PID of this Thread
94
     * @return integer
95
     */
96
    public function getPid()
97
    {
98
        return $this->pid;
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
     * Set ID of this Thread
112
     * @param integer Id
113
     * @return void
114
     */
115
    public function setId($id)
116
    {
117
        $this->id = $id;
118
    }
119
120
    /**
121
     * Is this thread terminated?
122
     * @return boolean
123
     */
124
    public function isTerminated()
125
    {
126
        return $this->terminated;
127
    }
128
129
    public function setTerminated()
130
    {
131
        $this->terminated = true;
132
        $this->onTerminated();
133
    }
134
135
    protected function onTerminated()
136
    {
137
    }
138
139
    /**
140
     * Invoke magic method
141
     * @return void
142
     */
143
144
    public function __invoke()
145
    {
146
        $this->run();
147
        $this->shutdown();
148
    }
149
150
    /**
151
     * Run thread process
152
     * @return void
153
     */
154
    protected function run()
155
    {
156
    }
157
158
    /**
159
     * Shutdowns the current process properly
160
     * @return void
161
     */
162
    protected function shutdown()
163
    {
164
        \posix_kill(\posix_getppid(), SIGCHLD);
165
        exit(0);
166
    }
167
168
    /**
169
     * Called when a signal caught through libevent.
170
     * @param integer Signal's number.
171
     * @param integer Events.
172
     * @param mixed   Argument.
173
     * @return void
174
     */
175
    public function eventSighandler($fd, $arg)
176
    {
177
        $this->sighandler($arg[0]);
178
    }
179
180
    /**
181
     * Called when the signal is caught
182
     * @param integer Signal's number
183
     * @return void
184
     */
185
    public function sighandler($signo)
186
    {
187
        if (!isset(self::$signals[$signo])) {
188
            $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...
189
            return;
190
        }
191
        if (method_exists($this, $m = strtolower(self::$signals[$signo]))) {
192
            $this->$m();
193
        } elseif (method_exists($this, 'sigunknown')) {
194
            $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...
195
        }
196
    }
197
198
    /**
199
     * Starts the process
200
     * @return void
201
     */
202
    public function start($clearstack = true)
203
    {
204
        $pid = \pcntl_fork();
205
206
        if ($pid === -1) {
207
            throw new \Exception('Could not fork');
208
        } elseif ($pid === 0) { // we are the child
209
            $thread = $this;
210
            $thread->pid = \posix_getpid();
211
            if (!$thread->delayedSigReg) {
212
                $thread->registerSignals();
213
            }
214
            if ($clearstack) {
215
                throw new ClearStack('', 0, $thread);
216
            } else {
217
                $thread->run();
218
                $thread->shutdown();
219
            }
220
        } else { // we are the master
221
            $this->pid = $pid;
222
        }
223
    }
224
225
    /**
226
     * Registers signals
227
     * @return void
228
     */
229
    protected function registerSignals()
230
    {
231
        foreach (self::$signals as $no => $name) {
232
            if (($name === 'SIGKILL') || ($name == 'SIGSTOP')) {
233
                continue;
234
            }
235
236
            if (!\pcntl_signal($no, [$this, 'sighandler'], true)) {
237
                $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...
238
            }
239
        }
240
    }
241
242
    /**
243
     * Delays the process execution for the given number of seconds
244
     * @param integer Sleep time in seconds
245
     * @return boolean Success
246
     */
247
    public function sleep($s)
248
    {
249
        static $interval = 0.2;
250
        $n = $s / $interval;
251
252
        for ($i = 0; $i < $n; ++$i) {
253
            if ($this->shutdown) {
254
                return false;
255
            }
256
257
            \usleep($interval * 1000000);
258
        }
259
260
        return true;
261
    }
262
263
    /**
264
     * Terminates the process
265
     * @param boolean Kill?
266
     * @return void
267
     */
268
    public function stop($kill = false)
269
    {
270
        $this->shutdown = true;
271
        \posix_kill($this->pid, $kill ? SIGKILL : SIGTERM);
272
    }
273
274
    /**
275
     * Sends arbitrary signal to the process
276
     * @param integer Signal's number
277
     * @return boolean Success
278
     */
279
    public function signal($sig)
280
    {
281
        return \posix_kill($this->pid, $sig);
282
    }
283
284
    /**
285
     * Checks if this process does exist
286
     * @return boolean Success
287
     */
288
    public function ifExists()
289
    {
290
        return \posix_kill($this->pid, 0);
291
    }
292
293
    /**
294
     * Register signals.
295
     * @return void
296
     */
297
    protected function registerEventSignals()
298
    {
299
        if (!EventLoop::$instance) {
300
            return;
301
        }
302
        foreach (self::$signals as $no => $name) {
303
            if ($name === 'SIGKILL' || $name == 'SIGSTOP') {
304
                continue;
305
            }
306
307
            $ev = EventLoop::$instance->signal($no, [$this, 'eventSighandler'], [$no]);
308
            if (!$ev) {
309
                $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...
310
            }
311
            $ev->add();
312
            $this->sigEvents[$no] = $ev;
313
        }
314
    }
315
316
    /**
317
     * Unregister signals.
318
     * @return void
319
     */
320
    protected function unregisterSignals()
321
    {
322
        foreach ($this->sigEvents as $no => $ev) {
323
            $ev->free();
324
            unset($this->sigEvents[$no]);
325
        }
326
    }
327
328
    /**
329
     * Sends the signal to parent process
330
     * @param integer Signal's number
331
     * @return boolean Success
332
     */
333
    protected function backsig($sig)
334
    {
335
        return \posix_kill(\posix_getppid(), $sig);
336
    }
337
338
    /**
339
     * Called when the signal SIGCHLD caught
340
     * @return void
341
     */
342
    protected function sigchld()
343
    {
344
        $this->waitPid();
345
    }
346
347
    /**
348
     * Checks for SIGCHLD
349
     * @return boolean Success
350
     */
351
    protected function waitPid()
352
    {
353
        start:
354
        $pid = \pcntl_waitpid(-1, $status, WNOHANG);
355
        if ($pid > 0) {
356
            foreach ($this->collections as $col) {
357
                foreach ($col->threads as $k => $t) {
358
                    if ($t->pid === $pid) {
359
                        $t->setTerminated();
360
                        unset($col->threads[$k]);
361
                        goto start;
362
                    }
363
                }
364
            }
365
        }
366
367
        return false;
368
    }
369
370
    /**
371
     * Called when the signal SIGTERM caught
372
     * @return void
373
     */
374
    protected function sigterm()
375
    {
376
        exit(0);
377
    }
378
379
    /**
380
     * Called when the signal SIGINT caught
381
     * @return void
382
     */
383
    protected function sigint()
384
    {
385
        exit(0);
386
    }
387
388
    /**
389
     * Called when the signal SIGTERM caught
390
     * @return void
391
     */
392
    protected function sigquit()
393
    {
394
        $this->shutdown = true;
395
    }
396
397
    /**
398
     * Called when the signal SIGKILL caught
399
     * @return void
400
     */
401
    protected function sigkill()
402
    {
403
        exit(0);
404
    }
405
406
    /**
407
     * Wait until all processes are terminated
408
     * @return void
409
     */
410
    protected function waitAll()
411
    {
412
        do {
413
            $n = 0;
414
415
            foreach ($this->collections as &$col) {
416
                $n += $col->removeTerminated();
417
            }
418
            if (!$this->waitPid()) {
419
                $this->sigwait(0, 20000);
420
            }
421
        } while ($n > 0);
422
    }
423
424
    /**
425
     * Waits for signals, with a timeout
426
     * @param int Seconds
427
     * @param int Nanoseconds
428
     * @return boolean Success
429
     */
430
    protected function sigwait($sec = 0, $nano = 0.3e9)
431
    {
432
        $siginfo = null;
433
434
        if (!function_exists('pcntl_sigtimedwait')) {
435
            $signo = $this->sigtimedwait(array_keys(static::$signals), $siginfo, $sec, $nano);
436
        } else {
437
            $signo = @\pcntl_sigtimedwait(array_keys(static::$signals), $siginfo, $sec, $nano);
438
        }
439
440
        if (is_bool($signo)) {
441
            return $signo;
442
        }
443
444
        if ($signo > 0) {
445
            $this->sighandler($signo);
446
447
            return true;
448
        }
449
450
        return false;
451
    }
452
453
    /**
454
     * Implementation of pcntl_sigtimedwait for Mac.
455
     *
456
     * @param array Signal
457
     * @param null|array SigInfo
458
     * @param int Seconds
459
     * @param int Nanoseconds
460
     * @param integer $sec
461
     * @param double $nano
462
     * @return boolean Success
463
     */
464
    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...
465
    {
466
        \pcntl_signal_dispatch();
467
        if (\time_nanosleep($sec, $nano) === true) {
468
            return false;
469
        }
470
        \pcntl_signal_dispatch();
471
        return true;
472
    }
473
474
    /**
475
     * Sets a title of the current process
476
     *
477
     * @param string $title
478
     * @return boolean Success
479
     */
480
    protected function setTitle($title)
481
    {
482
        return cli_set_process_title($title);
483
    }
484
485
    /**
486
     * Returns a title of the current process
487
     *
488
     * @return string
489
     */
490
    protected function getTitle()
491
    {
492
        return cli_get_process_title();
493
    }
494
}
495