Worker::onTerminated()   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 0
1
<?php
2
namespace PHPDaemon\Thread;
3
4
use PHPDaemon\Cache\CappedStorageHits;
5
use PHPDaemon\Core\AppInstance;
6
use PHPDaemon\Core\Daemon;
7
use PHPDaemon\Core\Debug;
8
use PHPDaemon\Core\EventLoop;
9
use PHPDaemon\Core\Timer;
10
use PHPDaemon\FS\FileSystem;
11
12
/**
13
 * Implementation of the worker thread
14
 *
15
 * @package Core
16
 *
17
 * @author  Vasily Zorin <[email protected]>
18
 */
19
class Worker extends Generic
20
{
21
22
    /**
23
     * Update?
24
     * @var boolean
25
     */
26
    public $update = false;
27
28
    /**
29
     * Reload?
30
     * @var boolean
31
     */
32
    public $reload = false;
33
34
    protected $graceful = false;
35
36
    /**
37
     * Reload delay
38
     * @var integer
39
     */
40
    protected $reloadDelay = 2;
41
42
    /**
43
     * Reloaded?
44
     * @var boolean
45
     */
46
    public $reloaded = false;
47
48
    /**
49
     * Time of last activity
50
     * @var integer
51
     */
52
    public $timeLastActivity = 0;
53
54
    /**
55
     * Last time of auto reload
56
     * @var integer
57
     */
58
    protected $autoReloadLast = 0;
59
60
    /**
61
     * State
62
     * @var integer
63
     */
64
    public $state = 0;
65
66
    /**
67
     * Request counter
68
     * @var integer
69
     */
70
    public $reqCounter = 0;
71
72
    /**
73
     * Break main loop?
74
     * @var boolean
75
     */
76
    public $breakMainLoop = false;
77
78
    /**
79
     * Reload ready?
80
     * @var boolean
81
     */
82
    public $reloadReady = false;
83
84
    /**
85
     * If true, we do not register signals automatically at start
86
     * @var boolean
87
     */
88
    protected $delayedSigReg = false;
89
90
    /**
91
     * Instances count
92
     * @var array
93
     */
94
    public $instancesCount = [];
95
96
    /**
97
     * Connection
98
     * @var Connection
99
     */
100
    public $connection;
101
102
    /**
103
     * Counter GC
104
     * @var integer
105
     */
106
    public $counterGC = 0;
107
108
109
    /** @var \PHPDaemon\IPCManager\IPCManager */
110
    public $IPCManager;
111
112
    public $lambdaCache;
113
114
    /**
115
     * Runtime of Worker process.
116
     * @return void
117
     */
118
    protected function run()
119
    {
120
        $this->lambdaCache = new CappedStorageHits;
121
        $this->lambdaCache->setMaxCacheSize(Daemon::$config->lambdacachemaxsize->value);
122
        $this->lambdaCache->setCapWindow(Daemon::$config->lambdacachecapwindow->value);
123
124
        if (Daemon::$process instanceof Master) {
125
            Daemon::$process->unregisterSignals();
0 ignored issues
show
Bug introduced by
The method unregisterSignals() cannot be called from this context as it is declared protected in class PHPDaemon\Thread\Generic.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
126
        }
127
128
        EventLoop::init();
129
130
        Daemon::$process = $this;
131
        if (Daemon::$logpointerAsync) {
132
            Daemon::$logpointerAsync->fd = null;
133
            Daemon::$logpointerAsync = null;
134
        }
135
        class_exists('Timer');
136
        $this->autoReloadLast = time();
137
        $this->reloadDelay = Daemon::$config->mpmdelay->value + 2;
138
        $this->setState(Daemon::WSTATE_PREINIT);
139
140
        if (Daemon::$config->autogc->value > 0) {
141
            gc_enable();
142
            gc_collect_cycles();
143
        } else {
144
            gc_disable();
145
        }
146
147
        if (Daemon::$runworkerMode) {
148 View Code Duplication
            if (!Daemon::$config->verbosetty->value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
149
                fclose(STDIN);
150
                fclose(STDOUT);
151
                fclose(STDERR);
152
            }
153
154
            Daemon::$appResolver->preload(true);
155
        }
156
157
        $this->prepareSystemEnv();
158
        $this->overrideNativeFuncs();
159
160
        $this->setState(Daemon::WSTATE_INIT);
161
        $this->registerEventSignals();
162
163
        FileSystem::init();
164
        FileSystem::initEvent();
165
        Daemon::openLogs();
166
167
        $this->IPCManager = Daemon::$appResolver->getInstanceByAppName('\PHPDaemon\IPCManager\IPCManager');
168
        if (!$this->IPCManager) {
169
            $this->log('cannot instantiate IPCManager');
170
        }
171
172
        Daemon::$appResolver->preload();
173
174
        foreach (Daemon::$appInstances as $app) {
175
            foreach ($app as $appInstance) {
176
                if (!$appInstance->ready) {
177
                    $appInstance->ready = true;
178
                    $appInstance->onReady();
179
                }
180
            }
181
        }
182
183
        $this->setState(Daemon::WSTATE_IDLE);
184
185
        Timer::add(function ($event) {
186
187
            if (!Daemon::$runworkerMode) {
188
                if ($this->IPCManager) {
189
                    $this->IPCManager->ensureConnection();
190
                }
191
            }
192
193
            $this->breakMainLoopCheck();
194
            if (Daemon::checkAutoGC()) {
195
                EventLoop::$instance->interrupt(function () {
196
                    gc_collect_cycles();
197
                });
198
            }
199
200
            $event->timeout();
201
        }, 1e6 * 1, 'breakMainLoopCheck');
202
        if (Daemon::$config->autoreload->value > 0) {
203
            Timer::add(function ($event) {
204
                static $n = 0;
205
                $list = get_included_files();
206
                $s = sizeof($list);
207
                if ($s > $n) {
208
                    $slice = array_map('realpath', array_slice($list, $n));
209
                    Daemon::$process->IPCManager->sendPacket(['op' => 'addIncludedFiles', 'files' => $slice]);
210
                    $n = $s;
211
                }
212
                $event->timeout();
213
            }, 1e6 * Daemon::$config->autoreload->value, 'watchIncludedFiles');
214
        }
215
        EventLoop::$instance->run();
216
        $this->shutdown();
217
    }
218
219
    /**
220
     * Override a standard PHP function
221
     * @param string $local e.g. isUploadedFile
0 ignored issues
show
Bug introduced by
There is no parameter named $local. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
222
     * @param string $real e.g. is_uploaded_file
223
     */
224
    protected function override($camelCase, $real)
225
    {
226
        runkit_function_rename($real, $real . '_native');
227
        runkit_function_rename('PHPDaemon\\Thread\\' . $camelCase, $real);
228
    }
229
230
    /**
231
     * Overrides native PHP functions.
232
     * @return void
233
     */
234
    protected function overrideNativeFuncs()
235
    {
236
        if (Daemon::supported(Daemon::SUPPORT_RUNKIT_INTERNAL_MODIFY)) {
237
            function define($k, $v)
238
            {
239
                if (defined($k)) {
240
                    runkit_constant_redefine($k, $v);
241
                } else {
242
                    runkit_constant_add($k, $v);
243
                }
244
            }
245
246
            $this->override('define', 'define');
247
248
            function header(...$args)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
249
            {
250
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
251
                    return false;
252
                }
253
                return Daemon::$context->header(...$args);
0 ignored issues
show
Documentation Bug introduced by
The method header does not exist on object<PHPDaemon\Request\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
            }
255
256
            $this->override('header', 'header');
257
258
            function isUploadedFile(...$args)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
259
            {
260
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
261
                    return false;
262
                }
263
                return Daemon::$context->isUploadedFile(...$args);
0 ignored issues
show
Documentation Bug introduced by
The method isUploadedFile does not exist on object<PHPDaemon\Request\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...
264
            }
265
266
            $this->override('isUploadedFile', 'is_uploaded_file');
267
268
            function moveUploadedFile(...$args)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
269
            {
270
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
271
                    return false;
272
                }
273
                return Daemon::$context->moveUploadedFile(...$args);
0 ignored issues
show
Documentation Bug introduced by
The method moveUploadedFile does not exist on object<PHPDaemon\Request\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...
274
            }
275
276
            $this->override('moveUploadedFile', 'move_uploaded_file');
277
278
            function headersSent(&$file = null, &$line = null)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
279
            {
280
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
281
                    return false;
282
                }
283
                return Daemon::$context->headers_sent($file, $line);
0 ignored issues
show
Documentation Bug introduced by
The method headers_sent does not exist on object<PHPDaemon\Request\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...
284
            }
285
286
            //$this->override('headersSent', 'headers_sent); // Commented out due to a runkit bug
287
288
            function headersList()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
289
            {
290
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
291
                    return false;
292
                }
293
                return Daemon::$context->headers_list();
0 ignored issues
show
Documentation Bug introduced by
The method headers_list does not exist on object<PHPDaemon\Request\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...
294
            }
295
296
            $this->override('headersList', 'headers_list');
297
298
            function setcookie(...$args)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
299
            {
300
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
301
                    return false;
302
                }
303
                return Daemon::$context->setcookie(...$args);
0 ignored issues
show
Documentation Bug introduced by
The method setcookie does not exist on object<PHPDaemon\Request\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...
304
            }
305
306
            $this->override('setcookie', 'setcookie');
307
308
            /**
309
             * @param callable $cb
310
             */
311
            function registerShutdownFunction($cb)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
312
            {
313
                if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
314
                    return false;
315
                }
316
                return Daemon::$context->registerShutdownFunction($cb);
317
            }
318
319
            $this->override('registerShutdownFunction', 'register_shutdown_function');
320
321
            runkit_function_copy('create_function', 'create_function_native');
322
            runkit_function_redefine('create_function', '$arg,$body',
323
                'return \PHPDaemon\Core\Daemon::$process->createFunction($arg,$body);');
324
        }
325
    }
326
327
    /**
328
     * Creates anonymous function (old-fashioned) like create_function()
329
     * @return void
330
     */
331
    public function createFunction($args, $body, $ttl = null)
332
    {
333
        $key = $args . "\x00" . $body;
334
        if (($f = $this->lambdaCache->getValue($key)) !== null) {
335
            return $f;
336
        }
337
        $f = eval('return function(' . $args . '){' . $body . '};');
338
        if ($ttl === null && Daemon::$config->lambdacachettl->value) {
339
            $ttl = Daemon::$config->lambdacachettl->value;
340
        }
341
        $this->lambdaCache->put($key, $f, $ttl);
342
        return $f;
343
    }
344
345
    /**
346
     * Setup settings on start.
347
     * @return void
348
     */
349
    protected function prepareSystemEnv()
350
    {
351
        proc_nice(Daemon::$config->workerpriority->value);
352
353
        register_shutdown_function(function () {
354
            $this->shutdown(true);
355
        });
356
357
        $this->setTitle(
358
            Daemon::$runName . ': worker process'
359
            . (Daemon::$config->pidfile->value !== Daemon::$config->defaultpidfile->value
360
                ? ' (' . Daemon::$config->pidfile->value . ')' : '')
361
        );
362
363
364
        if (isset(Daemon::$config->group->value)) {
365
            $sg = posix_getgrnam(Daemon::$config->group->value);
366
        }
367
368
        if (isset(Daemon::$config->user->value)) {
369
            $su = posix_getpwnam(Daemon::$config->user->value);
370
        }
371
372
        $flushCache = false;
373
        if (Daemon::$config->chroot->value !== '/') {
374 View Code Duplication
            if (posix_getuid() != 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375
                Daemon::log('You must have the root privileges to change root.');
376
                exit(0);
377
            } elseif (!chroot(Daemon::$config->chroot->value)) {
378
                Daemon::log('Couldn\'t change root to \'' . Daemon::$config->chroot->value . '\'.');
379
                exit(0);
380
            }
381
            $flushCache = true;
382
        }
383
384
        if (isset(Daemon::$config->group->value)) {
385
            if ($sg === false) {
386
                Daemon::log('Couldn\'t change group to \'' . Daemon::$config->group->value . '\'. You must replace config-variable \'group\' with existing group.');
387
                exit(0);
388
            } elseif (($sg['gid'] != posix_getgid()) && (!posix_setgid($sg['gid']))) {
0 ignored issues
show
Bug introduced by
The variable $sg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
389
                Daemon::log('Couldn\'t change group to \'' . Daemon::$config->group->value . "'. Error (" . ($errno = posix_get_last_error()) . '): ' . posix_strerror($errno));
390
                exit(0);
391
            }
392
            $flushCache = true;
393
        }
394
395
        if (isset(Daemon::$config->user->value)) {
396
            if ($su === false) {
397
                Daemon::log('Couldn\'t change user to \'' . Daemon::$config->user->value . '\', user not found. You must replace config-variable \'user\' with existing username.');
398
                exit(0);
399
            } else {
400
                posix_initgroups($su['name'], $su['gid']);
0 ignored issues
show
Bug introduced by
The variable $su does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
401
                if ($su['uid'] != posix_getuid() && !posix_setuid($su['uid'])) {
402
                    Daemon::log('Couldn\'t change user to \'' . Daemon::$config->user->value . "'. Error (" . ($errno = posix_get_last_error()) . '): ' . posix_strerror($errno));
403
                    exit(0);
404
                }
405
            }
406
            $flushCache = true;
407
        }
408
        if ($flushCache) {
409
            clearstatcache(true);
410
        }
411
        if (Daemon::$config->cwd->value !== '.') {
412 View Code Duplication
            if (!@chdir(Daemon::$config->cwd->value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
413
                Daemon::log('Couldn\'t change directory to \'' . Daemon::$config->cwd->value . '.');
414
            }
415
            clearstatcache(true);
416
        }
417
    }
418
419
    /**
420
     * Log something
421
     * @param string - Message.
422
     * @param string $message
423
     * @return void
424
     */
425
    public function log($message)
426
    {
427
        Daemon::log('W#' . $this->pid . ' ' . $message);
428
    }
429
430
    /**
431
     * Reloads additional config-files on-the-fly.
432
     * @return void
433
     */
434
    protected function update()
435
    {
436
        FileSystem::updateConfig();
437
        foreach (Daemon::$appInstances as $app) {
438
            foreach ($app as $appInstance) {
439
                $appInstance->handleStatus(AppInstance::EVENT_CONFIG_UPDATED);
440
            }
441
        }
442
    }
443
444
    /**
445
     * Check if we should break main loop
446
     * @return void
447
     */
448
    protected function breakMainLoopCheck()
449
    {
450
        $time = microtime(true);
451
452
        if ($this->terminated || $this->breakMainLoop) {
453
            EventLoop::$instance->stop();
454
            return;
455
        }
456
457
        if ($this->shutdown) {
458
            EventLoop::$instance->stop();
459
            return;
460
        }
461
462
        if ($this->reload) {
463
            return;
464
        }
465
466
        if (Daemon::$config->maxmemoryusage->value > 0
467
            && (memory_get_usage(true) > Daemon::$config->maxmemoryusage->value)
468
        ) {
469
            $this->log('\'maxmemory\' exceed. Graceful shutdown.');
470
471
            $this->gracefulRestart();
472
        }
473
474
        if (Daemon::$config->maxrequests->value > 0
475
            && ($this->reqCounter >= Daemon::$config->maxrequests->value)
476
        ) {
477
            $this->log('\'max-requests\' exceed. Graceful shutdown.');
478
479
            $this->gracefulRestart();
480
        }
481
482
        if (Daemon::$config->maxidle->value
483
            && $this->timeLastActivity && ($time - $this->timeLastActivity > Daemon::$config->maxidle->value)
484
        ) {
485
            $this->log('\'maxworkeridle\' exceed. Graceful shutdown.');
486
487
            $this->gracefulRestart();
488
        }
489
490
        if ($this->update === true) {
491
            $this->update = false;
492
            $this->update();
493
        }
494
    }
495
496
    /**
497
     * Graceful restart
498
     * @return void
499
     */
500 View Code Duplication
    public function gracefulRestart()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
501
    {
502
        $this->graceful = true;
503
        $this->reload = true;
504
        Timer::add(function () {
505
            EventLoop::$instance->stop();
506
        }, $this->reloadDelay * 1e6);
507
        $this->setState($this->state);
0 ignored issues
show
Documentation introduced by
$this->state is of type integer, but the function expects a object<PHPDaemon\Thread\Constant>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
508
    }
509
510
    /**
511
     * Graceful stop
512
     * @return void
513
     */
514 View Code Duplication
    public function gracefulStop()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
515
    {
516
        $this->graceful = true;
517
        Timer::add(function () {
518
            EventLoop::$instance->stop();
519
        }, $this->reloadDelay * 1e6);
520
        $this->setState($this->state);
0 ignored issues
show
Documentation introduced by
$this->state is of type integer, but the function expects a object<PHPDaemon\Thread\Constant>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
521
    }
522
523
    /**
524
     * Asks the running applications the whether we can go to shutdown current (old) worker.
525
     * @return boolean - Ready?
526
     */
527
    protected function appInstancesReloadReady()
528
    {
529
        $ready = true;
530
531
        foreach (Daemon::$appInstances as $k => $app) {
532
            foreach ($app as $name => $appInstance) {
533
                if (!$appInstance->handleStatus($this->graceful ? AppInstance::EVENT_GRACEFUL_SHUTDOWN : AppInstance::EVENT_SHUTDOWN)) {
534
                    $this->log(__METHOD__ . ': waiting for ' . $k . ':' . $name);
535
                    $ready = false;
536
                }
537
            }
538
        }
539
        return $ready;
540
    }
541
542
    /**
543
     * Shutdown this worker
544
     * @param boolean Hard? If hard, we shouldn't wait for graceful shutdown of the running applications.
545
     * @return boolean|null Ready?
546
     */
547
    protected function shutdown($hard = false)
548
    {
549
        $error = error_get_last();
550 View Code Duplication
        if ($error) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $error of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
551
            if ($error['type'] === E_ERROR) {
552
                Daemon::log('W#' . $this->pid . ' crashed by error \'' . $error['message'] . '\' at ' . $error['file'] . ':' . $error['line']);
553
            }
554
        }
555
556
        if (Daemon::$config->throwexceptiononshutdown->value) {
557
            throw new \Exception('event shutdown');
558
        }
559
560
        @ob_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
561
562 View Code Duplication
        if ($this->terminated === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
563
            if ($hard) {
564
                posix_kill(getmypid(), SIGKILL);
565
                exit(0);
566
            }
567
568
            return;
569
        }
570
571
        $this->terminated = true;
572
573
        if ($hard) {
574
            $this->setState(Daemon::WSTATE_SHUTDOWN);
575
            posix_kill(getmypid(), SIGKILL);
576
            exit(0);
577
        }
578
579
        $this->reloadReady = $this->appInstancesReloadReady();
580
        Timer::remove('breakMainLoopCheck');
581
582
        Timer::add(function ($event) {
583
            $self = Daemon::$process;
584
585
            $self->reloadReady = $self->appInstancesReloadReady();
0 ignored issues
show
Bug introduced by
The method appInstancesReloadReady does only exist in PHPDaemon\Thread\Worker, but not in PHPDaemon\Thread\IPC and PHPDaemon\Thread\Master.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
586
587
            if ($self->reload === true) {
588
                $self->reloadReady = $self->reloadReady && (microtime(true) > $self->reloadTime);
589
            }
590
            if (!$self->reloadReady) {
591
                $event->timeout();
592
            } else {
593
                EventLoop::$instance->stop();
594
            }
595
        }, 1e6, 'checkReloadReady');
596
        while (!$this->reloadReady) {
597
            EventLoop::$instance->run();
598
        }
599
        FileSystem::waitAllEvents(); // ensure that all I/O events completed before suicide
600
        posix_kill(getmypid(), SIGKILL);
601
        exit(0); // R.I.P.
602
    }
603
604
    /**
605
     * Set current status of worker
606
     * @param int Constant
607
     * @return boolean Success.
608
     */
609
    public function setState($int)
610
    {
611
        if (Daemon::$compatMode) {
612
            return true;
613
        }
614
        if (!$this->id) {
615
            return false;
616
        }
617
618
        if (Daemon::$config->logworkersetstate->value) {
619
            $this->log('state is ' . Daemon::$wstateRev[$int]);
620
        }
621
622
        $this->state = $int;
0 ignored issues
show
Documentation Bug introduced by
It seems like $int of type object<PHPDaemon\Thread\Constant> is incompatible with the declared type integer of property $state.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
623
624
        if ($this->reload) {
625
            $int += 100;
626
        }
627
        if (Daemon::$shm_wstate !== null) {
628
            Daemon::$shm_wstate->write(chr($int), $this->id - 1);
629
        }
630
        return true;
631
    }
632
633
    /**
634
     * Handler of the SIGINT (hard shutdown) signal in worker process.
635
     * @return void
636
     */
637
    protected function sigint()
638
    {
639
        if (Daemon::$config->logsignals->value) {
640
            $this->log('caught SIGINT.');
641
        }
642
643
        $this->shutdown(true);
644
    }
645
646
    /**
647
     * Handler of the SIGTERM (graceful shutdown) signal in worker process.
648
     * @return void
649
     */
650
    protected function sigterm()
651
    {
652
        if (Daemon::$config->logsignals->value) {
653
            $this->log('caught SIGTERM.');
654
        }
655
656
        $this->graceful = false;
657
        EventLoop::$instance->stop();
658
    }
659
660
    /**
661
     * Handler of the SIGQUIT (graceful shutdown) signal in worker process.
662
     * @return void
663
     */
664
    protected function sigquit()
665
    {
666
        if (Daemon::$config->logsignals->value) {
667
            $this->log('caught SIGQUIT.');
668
        }
669
670
        parent::sigquit();
671
    }
672
673
    /**
674
     * Handler of the SIGHUP (reload config) signal in worker process.
675
     * @return void
676
     */
677 View Code Duplication
    protected function sighup()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
678
    {
679
        if (Daemon::$config->logsignals->value) {
680
            $this->log('caught SIGHUP (reload config).');
681
        }
682
683
        if (isset(Daemon::$config->configfile->value)) {
684
            Daemon::loadConfig(Daemon::$config->configfile->value);
685
        }
686
687
        $this->update = true;
688
    }
689
690
    /**
691
     * Handler of the SIGUSR1 (re-open log-file) signal in worker process.
692
     * @return void
693
     */
694
    protected function sigusr1()
695
    {
696
        if (Daemon::$config->logsignals->value) {
697
            $this->log('caught SIGUSR1 (re-open log-file).');
698
        }
699
700
        Daemon::openLogs();
701
    }
702
703
    /**
704
     * Handler of the SIGUSR2 (graceful shutdown for update) signal in worker process.
705
     * @return void
706
     */
707
    protected function sigusr2()
708
    {
709
        if (Daemon::$config->logsignals->value) {
710
            $this->log('caught SIGUSR2 (graceful shutdown for update).');
711
        }
712
713
        $this->gracefulRestart();
714
    }
715
716
    /**
717
     * Handler of the SIGTSTP (graceful stop) signal in worker process.
718
     * @return void
719
     */
720
    protected function sigtstp()
721
    {
722
        if (Daemon::$config->logsignals->value) {
723
            $this->log('caught SIGTSTP (graceful stop).');
724
        }
725
726
        $this->gracefulStop();
727
    }
728
729
    /**
730
     * Handler of the SIGTTIN signal in worker process.
731
     * @return void
732
     */
733
    protected function sigttin()
734
    {
735
    }
736
737
    /**
738
     * Handler of the SIGPIPE signal in worker process.
739
     * @return void
740
     */
741
    protected function sigpipe()
742
    {
743
    }
744
745
    /**
746
     * Handler of non-known signals.
747
     * @return void
748
     */
749 View Code Duplication
    protected function sigunknown($signo)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
750
    {
751
        if (isset(Generic::$signals[$signo])) {
752
            $sig = Generic::$signals[$signo];
753
        } else {
754
            $sig = 'UNKNOWN';
755
        }
756
757
        $this->log('caught signal #' . $signo . ' (' . $sig . ').');
758
    }
759
760
    /**
761
     * Called (in master) when process is terminated
762
     * @return void
763
     */
764
    public function onTerminated()
765
    {
766
        $this->setState(Daemon::WSTATE_SHUTDOWN);
767
    }
768
769
    /**
770
     * Destructor of worker thread.
771
     * @return void
772
     */
773
    public function __destruct()
774
    {
775
        if (posix_getpid() === $this->pid) {
776
            $this->setState(Daemon::WSTATE_SHUTDOWN);
777
        }
778
    }
779
}
780