Completed
Push — EventLoopContainer ( c9a84d...6e77fa )
by Vasily
04:04
created

Worker::sigtstp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
eloc 4
nc 2
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
        if (isset(Daemon::$config->group->value)) {
364
            $sg = posix_getgrnam(Daemon::$config->group->value);
365
        }
366
367
        if (isset(Daemon::$config->user->value)) {
368
            $su = posix_getpwnam(Daemon::$config->user->value);
369
        }
370
371
        $flushCache = false;
372
        if (Daemon::$config->chroot->value !== '/') {
373 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...
374
                Daemon::log('You must have the root privileges to change root.');
375
                exit(0);
376
            } elseif (!chroot(Daemon::$config->chroot->value)) {
377
                Daemon::log('Couldn\'t change root to \'' . Daemon::$config->chroot->value . '\'.');
378
                exit(0);
379
            }
380
            $flushCache = true;
381
        }
382
383 View Code Duplication
        if (isset(Daemon::$config->group->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...
384
            if ($sg === false) {
385
                Daemon::log('Couldn\'t change group to \'' . Daemon::$config->group->value . '\'. You must replace config-variable \'group\' with existing group.');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 164 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
386
                exit(0);
387
            } 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...
388
                Daemon::log('Couldn\'t change group to \'' . Daemon::$config->group->value . "'. Error (" . ($errno = posix_get_last_error()) . '): ' . posix_strerror($errno));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 176 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
389
                exit(0);
390
            }
391
            $flushCache = true;
392
        }
393
394 View Code Duplication
        if (isset(Daemon::$config->user->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...
395
            if ($su === false) {
396
                Daemon::log('Couldn\'t change user to \'' . Daemon::$config->user->value . '\', user not found. You must replace config-variable \'user\' with existing username.');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 180 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
397
                exit(0);
398
            } elseif ($su['uid'] != posix_getuid() & !posix_setuid($su['uid'])) {
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...
399
                Daemon::log('Couldn\'t change user to \'' . Daemon::$config->user->value . "'. Error (" . ($errno = posix_get_last_error()) . '): ' . posix_strerror($errno));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 174 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
400
                exit(0);
401
            }
402
            $flushCache = true;
403
        }
404
        if ($flushCache) {
405
            clearstatcache(true);
406
        }
407
        if (Daemon::$config->cwd->value !== '.') {
408 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...
409
                Daemon::log('Couldn\'t change directory to \'' . Daemon::$config->cwd->value . '.');
410
            }
411
            clearstatcache(true);
412
        }
413
    }
414
415
    /**
416
     * Log something
417
     * @param string - Message.
418
     * @param string $message
419
     * @return void
420
     */
421
    public function log($message)
422
    {
423
        Daemon::log('W#' . $this->pid . ' ' . $message);
424
    }
425
426
    /**
427
     * Reloads additional config-files on-the-fly.
428
     * @return void
429
     */
430
    protected function update()
431
    {
432
        FileSystem::updateConfig();
433
        foreach (Daemon::$appInstances as $app) {
434
            foreach ($app as $appInstance) {
435
                $appInstance->handleStatus(AppInstance::EVENT_CONFIG_UPDATED);
436
            }
437
        }
438
    }
439
440
    /**
441
     * Check if we should break main loop
442
     * @return void
443
     */
444
    protected function breakMainLoopCheck()
445
    {
446
        $time = microtime(true);
447
448
        if ($this->terminated || $this->breakMainLoop) {
449
            EventLoop::$instance->stop();
450
            return;
451
        }
452
453
        if ($this->shutdown) {
454
            EventLoop::$instance->stop();
455
            return;
456
        }
457
458
        if ($this->reload) {
459
            return;
460
        }
461
462
        if (Daemon::$config->maxmemoryusage->value > 0
463
            && (memory_get_usage(true) > Daemon::$config->maxmemoryusage->value)
464
        ) {
465
            $this->log('\'maxmemory\' exceed. Graceful shutdown.');
466
467
            $this->gracefulRestart();
468
        }
469
470
        if (Daemon::$config->maxrequests->value > 0
471
            && ($this->reqCounter >= Daemon::$config->maxrequests->value)
472
        ) {
473
            $this->log('\'max-requests\' exceed. Graceful shutdown.');
474
475
            $this->gracefulRestart();
476
        }
477
478
        if (Daemon::$config->maxidle->value
479
            && $this->timeLastActivity && ($time - $this->timeLastActivity > Daemon::$config->maxidle->value)
480
        ) {
481
            $this->log('\'maxworkeridle\' exceed. Graceful shutdown.');
482
483
            $this->gracefulRestart();
484
        }
485
486
        if ($this->update === true) {
487
            $this->update = false;
488
            $this->update();
489
        }
490
    }
491
492
    /**
493
     * Graceful restart
494
     * @return void
495
     */
496 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...
497
    {
498
        $this->graceful = true;
499
        $this->reload = true;
500
        Timer::add(function () {
501
            EventLoop::$instance->stop();
502
        }, $this->reloadDelay * 1e6);
503
        $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...
504
    }
505
506
    /**
507
     * Graceful stop
508
     * @return void
509
     */
510 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...
511
    {
512
        $this->graceful = true;
513
        Timer::add(function () {
514
            EventLoop::$instance->stop();
515
        }, $this->reloadDelay * 1e6);
516
        $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...
517
    }
518
519
    /**
520
     * Asks the running applications the whether we can go to shutdown current (old) worker.
521
     * @return boolean - Ready?
522
     */
523
    protected function appInstancesReloadReady()
524
    {
525
        $ready = true;
526
527
        foreach (Daemon::$appInstances as $k => $app) {
528
            foreach ($app as $name => $appInstance) {
529
                if (!$appInstance->handleStatus($this->graceful ? AppInstance::EVENT_GRACEFUL_SHUTDOWN : AppInstance::EVENT_SHUTDOWN)) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
530
                    $this->log(__METHOD__ . ': waiting for ' . $k . ':' . $name);
531
                    $ready = false;
532
                }
533
            }
534
        }
535
        return $ready;
536
    }
537
538
    /**
539
     * Shutdown this worker
540
     * @param boolean Hard? If hard, we shouldn't wait for graceful shutdown of the running applications.
541
     * @return boolean|null Ready?
542
     */
543
    protected function shutdown($hard = false)
544
    {
545
        $error = error_get_last();
546 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...
547
            if ($error['type'] === E_ERROR) {
548
                Daemon::log('W#' . $this->pid . ' crashed by error \'' . $error['message'] . '\' at ' . $error['file'] . ':' . $error['line']);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 143 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
549
            }
550
        }
551
552
        if (Daemon::$config->throwexceptiononshutdown->value) {
553
            throw new \Exception('event shutdown');
554
        }
555
556
        @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...
557
558
        if ($this->terminated === true) {
559
            if ($hard) {
560
                exit(0);
561
            }
562
563
            return;
564
        }
565
566
        $this->terminated = true;
567
568
        if ($hard) {
569
            $this->setState(Daemon::WSTATE_SHUTDOWN);
570
            exit(0);
571
        }
572
573
        $this->reloadReady = $this->appInstancesReloadReady();
574
        Timer::remove('breakMainLoopCheck');
575
576
        Timer::add(function ($event) {
577
            $self = Daemon::$process;
578
579
            $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...
580
581
            if ($self->reload === true) {
582
                $self->reloadReady = $self->reloadReady && (microtime(true) > $self->reloadTime);
583
            }
584
            if (!$self->reloadReady) {
585
                $event->timeout();
586
            } else {
587
                $self->loop->stop();
588
            }
589
        }, 1e6, 'checkReloadReady');
590
        while (!$this->reloadReady) {
591
            EventLoop::$instance->run();
592
        }
593
        FileSystem::waitAllEvents(); // ensure that all I/O events completed before suicide
594
        exit(0); // R.I.P.
595
    }
596
597
    /**
598
     * Set current status of worker
599
     * @param int Constant
600
     * @return boolean Success.
601
     */
602
    public function setState($int)
603
    {
604
        if (Daemon::$compatMode) {
605
            return true;
606
        }
607
        if (!$this->id) {
608
            return false;
609
        }
610
611
        if (Daemon::$config->logworkersetstate->value) {
612
            $this->log('state is ' . Daemon::$wstateRev[$int]);
613
        }
614
615
        $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...
616
617
        if ($this->reload) {
618
            $int += 100;
619
        }
620
        Daemon::$shm_wstate->write(chr($int), $this->id - 1);
0 ignored issues
show
Documentation Bug introduced by
The method write does not exist on object<PHPDaemon\Thread\Collection>? 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...
621
        return true;
622
    }
623
624
    /**
625
     * Handler of the SIGINT (hard shutdown) signal in worker process.
626
     * @return void
627
     */
628
    protected function sigint()
629
    {
630
        if (Daemon::$config->logsignals->value) {
631
            $this->log('caught SIGINT.');
632
        }
633
634
        $this->shutdown(true);
635
    }
636
637
    /**
638
     * Handler of the SIGTERM (graceful shutdown) signal in worker process.
639
     * @return void
640
     */
641
    protected function sigterm()
642
    {
643
        if (Daemon::$config->logsignals->value) {
644
            $this->log('caught SIGTERM.');
645
        }
646
647
        $this->graceful = false;
648
        EventLoop::$instance->stop();
649
    }
650
651
    /**
652
     * Handler of the SIGQUIT (graceful shutdown) signal in worker process.
653
     * @return void
654
     */
655
    protected function sigquit()
656
    {
657
        if (Daemon::$config->logsignals->value) {
658
            $this->log('caught SIGQUIT.');
659
        }
660
661
        parent::sigquit();
662
    }
663
664
    /**
665
     * Handler of the SIGHUP (reload config) signal in worker process.
666
     * @return void
667
     */
668 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...
669
    {
670
        if (Daemon::$config->logsignals->value) {
671
            $this->log('caught SIGHUP (reload config).');
672
        }
673
674
        if (isset(Daemon::$config->configfile->value)) {
675
            Daemon::loadConfig(Daemon::$config->configfile->value);
676
        }
677
678
        $this->update = true;
679
    }
680
681
    /**
682
     * Handler of the SIGUSR1 (re-open log-file) signal in worker process.
683
     * @return void
684
     */
685
    protected function sigusr1()
686
    {
687
        if (Daemon::$config->logsignals->value) {
688
            $this->log('caught SIGUSR1 (re-open log-file).');
689
        }
690
691
        Daemon::openLogs();
692
    }
693
694
    /**
695
     * Handler of the SIGUSR2 (graceful shutdown for update) signal in worker process.
696
     * @return void
697
     */
698
    protected function sigusr2()
699
    {
700
        if (Daemon::$config->logsignals->value) {
701
            $this->log('caught SIGUSR2 (graceful shutdown for update).');
702
        }
703
704
        $this->gracefulRestart();
705
    }
706
707
    /**
708
     * Handler of the SIGTSTP (graceful stop) signal in worker process.
709
     * @return void
710
     */
711
    protected function sigtstp()
712
    {
713
        if (Daemon::$config->logsignals->value) {
714
            $this->log('caught SIGTSTP (graceful stop).');
715
        }
716
717
        $this->gracefulStop();
718
    }
719
720
    /**
721
     * Handler of the SIGTTIN signal in worker process.
722
     * @return void
723
     */
724
    protected function sigttin()
725
    {
726
    }
727
728
    /**
729
     * Handler of the SIGPIPE signal in worker process.
730
     * @return void
731
     */
732
    protected function sigpipe()
733
    {
734
    }
735
736
    /**
737
     * Handler of non-known signals.
738
     * @return void
739
     */
740 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...
741
    {
742
        if (isset(Generic::$signals[$signo])) {
743
            $sig = Generic::$signals[$signo];
744
        } else {
745
            $sig = 'UNKNOWN';
746
        }
747
748
        $this->log('caught signal #' . $signo . ' (' . $sig . ').');
749
    }
750
751
    /**
752
     * Called (in master) when process is terminated
753
     * @return void
754
     */
755
    public function onTerminated()
756
    {
757
        $this->setState(Daemon::WSTATE_SHUTDOWN);
758
    }
759
760
    /**
761
     * Destructor of worker thread.
762
     * @return void
763
     */
764
    public function __destruct()
765
    {
766
        if (posix_getpid() === $this->pid) {
767
            $this->setState(Daemon::WSTATE_SHUTDOWN);
768
        }
769
    }
770
}
771