Completed
Push — master ( f669a1...dbf89f )
by Vasily
05:55
created

Worker.php ➔ define()   A

Complexity

Conditions 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 7
rs 9.4285
1
<?php
2
namespace PHPDaemon\Thread;
3
4
use PHPDaemon\Core\AppInstance;
5
use PHPDaemon\Core\Daemon;
6
use PHPDaemon\Core\Debug;
7
use PHPDaemon\Core\Timer;
8
use PHPDaemon\FS\FileSystem;
9
use PHPDaemon\Structures\StackCallbacks;
10
use PHPDaemon\Cache\CappedStorageHits;
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
	 * Update?
23
	 * @var boolean
24
	 */
25
	public $update = false;
26
27
	/**
28
	 * Reload?
29
	 * @var boolean
30
	 */
31
	public $reload = false;
32
33
	protected $graceful = false;
34
35
	/**
36
	 * Reload time
37
	 * @var integer
38
	 */
39
	protected $reloadTime = 0;
40
41
	/**
42
	 * Reload delay
43
	 * @var integer
44
	 */
45
	protected $reloadDelay = 2;
46
47
	/**
48
	 * Reloaded?
49
	 * @var boolean
50
	 */
51
	public $reloaded = false;
52
53
	/**
54
	 * Time of last activity
55
	 * @var integer
56
	 */
57
	public $timeLastActivity = 0;
58
59
	/**
60
	 * Last time of auto reload
61
	 * @var integer
62
	 */
63
	protected $autoReloadLast = 0;
64
65
	/**
66
	 * Event base
67
	 * @var EventBase
68
	 */
69
	public $eventBase;
70
71
	/**
72
	 * DNS base
73
	 * @var EventDnsBase
74
	 */
75
	public $dnsBase;
76
77
	/**
78
	 * State
79
	 * @var integer
80
	 */
81
	public $state = 0;
82
83
	/**
84
	 * Request counter
85
	 * @var integer
86
	 */
87
	public $reqCounter = 0;
88
89
	/**
90
	 * Break main loop?
91
	 * @var boolean
92
	 */
93
	public $breakMainLoop = false;
94
95
	/**
96
	 * Reload ready?
97
	 * @var boolean
98
	 */
99
	public $reloadReady = false;
100
101
	/**
102
	 * If true, we do not register signals automatically at start
103
	 * @var boolean
104
	 */
105
	protected $delayedSigReg = false;
106
107
	/**
108
	 * Instances count
109
	 * @var array
110
	 */
111
	public $instancesCount = [];
112
113
	/**
114
	 * Connection
115
	 * @var Connection
116
	 */
117
	public $connection;
118
119
	/**
120
	 * Counter GC
121
	 * @var integer
122
	 */
123
	public $counterGC = 0;
124
125
	/**
126
	 * Stack of callbacks to execute
127
	 * @var \PHPDaemon\Structures\StackCallbacks
128
	 */
129
	public $callbacks;
130
131
	/** @var \PHPDaemon\IPCManager\IPCManager */
132
	public $IPCManager;
133
134
	public $lambdaCache;
135
136
	/**
137
	 * Runtime of Worker process.
138
	 * @return void
139
	 */
140
	protected function run() {
141
		$this->lambdaCache = new CappedStorageHits;
142
		$this->lambdaCache->setMaxCacheSize(Daemon::$config->lambdacachemaxsize->value);
143
		$this->lambdaCache->setCapWindow(Daemon::$config->lambdacachecapwindow->value);
144
145
		$this->callbacks = new StackCallbacks();
146
		if (Daemon::$process instanceof Master) {
147
			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...
148
		}
149 View Code Duplication
		if (Daemon::$process && Daemon::$process->eventBase) {
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...
150
			Daemon::$process->eventBase->reinit();
151
			$this->eventBase = Daemon::$process->eventBase;
152
		}
153
		else {
154
			$this->eventBase = new \EventBase();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \EventBase() of type object<EventBase> is incompatible with the declared type object<PHPDaemon\Thread\EventBase> of property $eventBase.

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...
155
		}
156
		Daemon::$process = $this;
157
		if (Daemon::$logpointerAsync) {
158
			$oldfd                       = Daemon::$logpointerAsync->fd;
0 ignored issues
show
Unused Code introduced by
$oldfd is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
159
			Daemon::$logpointerAsync->fd = null;
160
			Daemon::$logpointerAsync     = null;
161
		}
162
		class_exists('Timer');
163
		$this->autoReloadLast = time();
164
		$this->reloadDelay    = Daemon::$config->mpmdelay->value + 2;
165
		$this->setState(Daemon::WSTATE_PREINIT);
166
167
		if (Daemon::$config->autogc->value > 0) {
168
			gc_enable();
169
			gc_collect_cycles();
170
		}
171
		else {
172
			gc_disable();
173
		}
174
175
		if (Daemon::$runworkerMode) {
176 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...
177
				fclose(STDIN);
178
				fclose(STDOUT);
179
				fclose(STDERR);
180
			}
181
182
			Daemon::$appResolver->preload(true);
183
		}
184
185
		$this->prepareSystemEnv();
186
		$this->overrideNativeFuncs();
187
188
		$this->setState(Daemon::WSTATE_INIT);;
189
		$this->dnsBase = new \EventDnsBase($this->eventBase, false); // @TODO: test with true
0 ignored issues
show
Documentation Bug introduced by
It seems like new \EventDnsBase($this->eventBase, false) of type object<EventDnsBase> is incompatible with the declared type object<PHPDaemon\Thread\EventDnsBase> of property $dnsBase.

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...
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
190
		$this->registerEventSignals();
191
192
		FileSystem::init();
193
		FileSystem::initEvent();
194
		Daemon::openLogs();
195
196
		$this->IPCManager = Daemon::$appResolver->getInstanceByAppName('\PHPDaemon\IPCManager\IPCManager');
197
		if (!$this->IPCManager) {
198
			$this->log('cannot instantiate IPCManager');
199
		}
200
201
		Daemon::$appResolver->preload();
202
203
		foreach (Daemon::$appInstances as $app) {
204
			foreach ($app as $appInstance) {
205
				if (!$appInstance->ready) {
206
					$appInstance->ready = true;
207
					$appInstance->onReady();
208
				}
209
			}
210
		}
211
212
		$this->setState(Daemon::WSTATE_IDLE);
213
214
		Timer::add(function ($event) {
215
216
			if (!Daemon::$runworkerMode) {
217
				if ($this->IPCManager) {
218
					$this->IPCManager->ensureConnection();
219
				}
220
			}
221
222
			$this->breakMainLoopCheck();
223
			if ($this->breakMainLoop) {
224
				$this->eventBase->exit();
225
				return;
226
			}
227
228
			if (Daemon::checkAutoGC()) {
229
				$this->callbacks->push(function ($thread) {
0 ignored issues
show
Unused Code introduced by
The parameter $thread is not used and could be removed.

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

Loading history...
230
					gc_collect_cycles();
231
				});
232
				$this->eventBase->exit();
233
			}
234
235
			$event->timeout();
236
		}, 1e6 * 1, 'breakMainLoopCheck');
237
		if (Daemon::$config->autoreload->value > 0) {
238
			Timer::add(function ($event) {
239
				static $n = 0;
240
				$list = get_included_files();
241
				$s    = sizeof($list);
242
				if ($s > $n) {
243
					$slice = array_map('realpath', array_slice($list, $n));
244
					Daemon::$process->IPCManager->sendPacket(['op' => 'addIncludedFiles', 'files' => $slice]);
245
					$n = $s;
246
				}
247
				$event->timeout();
248
			}, 1e6 * Daemon::$config->autoreload->value, 'watchIncludedFiles');
249
		}
250
251
		while (!$this->breakMainLoop) {
252
			$this->callbacks->executeAll($this);
253
			if (!$this->eventBase->dispatch()) {
254
				break;
255
			}
256
		}
257
		$this->shutdown();
258
	}
259
260
	/**
261
	 * @param string $f
262
	 */
263
	protected function override($f) {
264
		runkit_function_rename($f, $f.'_native');
265
		runkit_function_rename('PHPDaemon\\Thread\\'.$f, $f);
266
	}
267
268
	/**
269
	 * Overrides native PHP functions.
270
	 * @return void
271
	 */
272
	protected function overrideNativeFuncs() {
273
		if (Daemon::supported(Daemon::SUPPORT_RUNKIT_INTERNAL_MODIFY)) {
274
275
			function define($k, $v) {
276
				if (defined($k)) {
277
					runkit_constant_redefine($k, $v);
278
				} else {
279
					runkit_constant_add($k, $v);
280
				}
281
			}
282
			$this->override('define');
283
284
			function header() {
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...
285
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
286
					return false;
287
				}
288
				return Daemon::$context->header(...func_get_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...
289
			}
290
			$this->override('header');
291
292
			function is_uploaded_file() {
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...
293
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
294
					return false;
295
				}
296
				return Daemon::$context->isUploadedFile(...func_get_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...
297
			}
298
			$this->override('is_uploaded_file');
299
300
			function move_uploaded_file() {
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...
301
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
302
					return false;
303
				}
304
				return Daemon::$context->moveUploadedFile(...func_get_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...
305
			}
306
			$this->override('move_uploaded_file');
307
308
			function headers_sent(&$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...
309
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
310
					return false;
311
				}
312
				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...
313
			}
314
315
			//$this->override('headers_sent');
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
316
317
			function headers_list() {
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...
318
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
319
					return false;
320
				}
321
				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...
322
			}
323
			$this->override('headers_list');
324
325
			function setcookie() {
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...
326
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
327
					return false;
328
				}
329
				return Daemon::$context->setcookie(...func_get_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...
330
			}
331
			$this->override('setcookie');
332
333
			/**
334
			 * @param callable $cb
335
			 */
336
			function register_shutdown_function($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...
337
				if (!Daemon::$context instanceof \PHPDaemon\Request\Generic) {
338
					return false;
339
				}
340
				return Daemon::$context->registerShutdownFunction($cb);
341
			}
342
			$this->override('register_shutdown_function');
343
344
			runkit_function_copy('create_function', 'create_function_native');
345
			runkit_function_redefine('create_function', '$arg,$body', 'return \PHPDaemon\Core\Daemon::$process->createFunction($arg,$body);');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 133 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...
346
		}
347
	}
348
349
	/**
350
	 * Creates anonymous function (old-fashioned) like create_function()
351
	 * @return void
352
	 */
353
	public function createFunction($args, $body, $ttl = null) {
354
		$key = $args . "\x00" . $body;
355
		if (($f = $this->lambdaCache->getValue($key)) !== null) {
356
			return $f;
357
		}
358
		$f = eval('return function(' . $args . '){' . $body . '};');
359
		if ($ttl === null && Daemon::$config->lambdacachettl->value) {
360
			$ttl = Daemon::$config->lambdacachettl->value;
361
		}
362
		$this->lambdaCache->put($key, $f, $ttl);
363
		return $f;
364
	}
365
366
	/**
367
	 * Setup settings on start.
368
	 * @return void
369
	 */
370
	protected function prepareSystemEnv() {
371
		proc_nice(Daemon::$config->workerpriority->value);
372
373
		register_shutdown_function(function () {
374
			$this->shutdown(true);
375
		});
376
377
		$this->setTitle(
378
			Daemon::$runName . ': worker process'
379
			. (Daemon::$config->pidfile->value !== Daemon::$config->defaultpidfile->value
380
					? ' (' . Daemon::$config->pidfile->value . ')' : '')
381
		);
382
383
		if (isset(Daemon::$config->group->value)) {
384
			$sg = posix_getgrnam(Daemon::$config->group->value);
385
		}
386
387
		if (isset(Daemon::$config->user->value)) {
388
			$su = posix_getpwnam(Daemon::$config->user->value);
389
		}
390
391
		$flushCache = false;
392
		if (Daemon::$config->chroot->value !== '/') {
393 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...
394
				Daemon::log('You must have the root privileges to change root.');
395
				exit(0);
396
			}
397
			elseif (!chroot(Daemon::$config->chroot->value)) {
398
				Daemon::log('Couldn\'t change root to \'' . Daemon::$config->chroot->value . '\'.');
399
				exit(0);
400
			}
401
			$flushCache = true;
402
		}
403
404 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...
405
			if ($sg === FALSE) {
406
				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 152 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...
407
				exit(0);
408
			}
409
			elseif (
410
					($sg['gid'] != posix_getgid())
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...
411
					&& (!posix_setgid($sg['gid']))
412
			) {
413
				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 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...
414
				exit(0);
415
			}
416
			$flushCache = true;
417
		}
418
419 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...
420
			if ($su === FALSE) {
421
				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 168 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...
422
				exit(0);
423
			}
424
			elseif (
425
					($su['uid'] != posix_getuid())
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...
426
					&& (!posix_setuid($su['uid']))
427
			) {
428
				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 162 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...
429
				exit(0);
430
			}
431
			$flushCache = true;
432
		}
433
		if ($flushCache) {
434
			clearstatcache(true);
435
		}
436
		if (Daemon::$config->cwd->value !== '.') {
437 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...
438
				Daemon::log('Couldn\'t change directory to \'' . Daemon::$config->cwd->value . '.');
439
			}
440
			clearstatcache(true);
441
		}
442
	}
443
444
	/**
445
	 * Log something
446
	 * @param string - Message.
447
	 * @param string $message
448
	 * @return void
449
	 */
450
	public function log($message) {
451
		Daemon::log('W#' . $this->pid . ' ' . $message);
452
	}
453
454
	/**
455
	 * Reloads additional config-files on-the-fly.
456
	 * @return void
457
	 */
458
	protected function update() {
459
		FileSystem::updateConfig();
460
		foreach (Daemon::$appInstances as $app) {
461
			foreach ($app as $appInstance) {
462
				$appInstance->handleStatus(AppInstance::EVENT_CONFIG_UPDATED);
463
			}
464
		}
465
	}
466
467
	/**
468
	 * Check if we should break main loop
469
	 * @return void
470
	 */
471
	protected function breakMainLoopCheck() {
472
		$time = microtime(true);
473
474
		if ($this->terminated || $this->breakMainLoop) {
475
			return;
476
		}
477
478
		if ($this->shutdown) {
479
			$this->breakMainLoop = true;
480
			return;
481
		}
482
483
		if ($this->reload) {
484
			if ($time > $this->reloadTime) {
485
				$this->breakMainLoop = true;
486
			}
487
			return;
488
		}
489
490
		if (
491
				(Daemon::$config->maxmemoryusage->value > 0)
492
				&& (memory_get_usage(true) > Daemon::$config->maxmemoryusage->value)
493
		) {
494
			$this->log('\'maxmemory\' exceed. Graceful shutdown.');
495
496
			$this->gracefulRestart();
497
		}
498
499
		if (
500
				(Daemon::$config->maxrequests->value > 0)
501
				&& ($this->reqCounter >= Daemon::$config->maxrequests->value)
502
		) {
503
			$this->log('\'max-requests\' exceed. Graceful shutdown.');
504
505
			$this->gracefulRestart();
506
		}
507
508
		if (
509
				Daemon::$config->maxidle->value
510
				&& $this->timeLastActivity
511
				&& ($time - $this->timeLastActivity > Daemon::$config->maxidle->value)
512
		) {
513
			$this->log('\'maxworkeridle\' exceed. Graceful shutdown.');
514
515
			$this->gracefulRestart();
516
		}
517
518
		if ($this->update === true) {
519
			$this->update = false;
520
			$this->update();
521
		}
522
	}
523
524
	/**
525
	 * Graceful restart
526
	 * @return void
527
	 */
528 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...
529
		$this->reload     = true;
530
		$this->graceful = true;
531
		$this->reloadTime = microtime(true) + $this->reloadDelay;
0 ignored issues
show
Documentation Bug introduced by
The property $reloadTime was declared of type integer, but microtime(true) + $this->reloadDelay is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
532
		$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...
533
	}
534
535
	/**
536
	 * Graceful stop
537
	 * @return void
538
	 */
539 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...
540
		$this->breakMainLoop = true;
541
		$this->graceful = true;
542
		$this->reloadTime = microtime(true) + $this->reloadDelay;
0 ignored issues
show
Documentation Bug introduced by
The property $reloadTime was declared of type integer, but microtime(true) + $this->reloadDelay is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
543
		$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...
544
	}
545
546
	/**
547
	 * Asks the running applications the whether we can go to shutdown current (old) worker.
548
	 * @return boolean - Ready?
549
	 */
550
	protected function appInstancesReloadReady() {
551
		$ready = true;
552
553
		foreach (Daemon::$appInstances as $k => $app) {
554
			foreach ($app as $name => $appInstance) {
555
				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 124 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...
556
					$this->log(__METHOD__ . ': waiting for ' . $k . ':' . $name);
557
					$ready = false;
558
				}
559
			}
560
		}
561
		return $ready;
562
	}
563
564
	/**
565
	 * Shutdown this worker
566
	 * @param boolean Hard? If hard, we shouldn't wait for graceful shutdown of the running applications.
567
	 * @return boolean|null Ready?
568
	 */
569
	protected function shutdown($hard = false) {
570
		$error = error_get_last();
571 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...
572
			if ($error['type'] === E_ERROR) {
573
				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 131 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...
574
			}
575
576
		}
577
		if (Daemon::$config->logevents->value) {
578
			$this->log('event shutdown(' . ($hard ? 'HARD' : '') . ') invoked.');
579
		}
580
581
		if (Daemon::$config->throwexceptiononshutdown->value) {
582
			throw new \Exception('event shutdown');
583
		}
584
585
		@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...
586
587
		if ($this->terminated === true) {
588
			if ($hard) {
589
				exit(0);
590
			}
591
592
			return;
593
		}
594
595
		$this->terminated = true;
596
597
		if ($hard) {
598
			$this->setState(Daemon::WSTATE_SHUTDOWN);
599
			exit(0);
600
		}
601
602
		$this->reloadReady = $this->appInstancesReloadReady();
603
604
		if ($this->reload && $this->graceful) {
605
			$this->reloadReady = $this->reloadReady && (microtime(TRUE) > $this->reloadTime);
606
		}
607
608
		if (Daemon::$config->logevents->value) {
609
			$this->log('reloadReady = ' . Debug::dump($this->reloadReady));
610
		}
611
612
		Timer::remove('breakMainLoopCheck');
613
614
		Timer::add(function ($event) {
615
			$self = Daemon::$process;
616
617
			$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...
618
619
			if ($self->reload === TRUE) {
620
				$self->reloadReady = $self->reloadReady && (microtime(TRUE) > $self->reloadTime);
621
			}
622
			if (!$self->reloadReady) {
623
				$event->timeout();
624
			}
625
			else {
626
				$self->eventBase->exit();
627
			}
628
		}, 1e6, 'checkReloadReady');
629
		while (!$this->reloadReady) {
630
			$this->eventBase->loop();
631
		}
632
		FileSystem::waitAllEvents(); // ensure that all I/O events completed before suicide
633
		exit(0); // R.I.P.
634
	}
635
636
	/**
637
	 * Set current status of worker
638
	 * @param int Constant
639
	 * @return boolean Success.
640
	 */
641
	public function setState($int) {
642
		if (Daemon::$compatMode) {
643
			return true;
644
		}
645
		if (!$this->id) {
646
			return false;
647
		}
648
649
		if (Daemon::$config->logworkersetstate->value) {
650
			$this->log('state is ' . Daemon::$wstateRev[$int]);
651
		}
652
653
		$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...
654
655
		if ($this->reload) {
656
			$int += 100;
657
		}
658
		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...
659
		return true;
660
	}
661
662
	/**
663
	 * Handler of the SIGINT (hard shutdown) signal in worker process.
664
	 * @return void
665
	 */
666
	protected function sigint() {
667
		if (Daemon::$config->logsignals->value) {
668
			$this->log('caught SIGINT.');
669
		}
670
671
		$this->shutdown(TRUE);
672
	}
673
674
	/**
675
	 * Handler of the SIGTERM (graceful shutdown) signal in worker process.
676
	 * @return void
677
	 */
678
	protected function sigterm() {
679
		if (Daemon::$config->logsignals->value) {
680
			$this->log('caught SIGTERM.');
681
		}
682
683
		$this->graceful = false;
684
		$this->breakMainLoop = true;
685
		$this->eventBase->exit();
686
	}
687
688
	/**
689
	 * Handler of the SIGQUIT (graceful shutdown) signal in worker process.
690
	 * @return void
691
	 */
692
	protected function sigquit() {
693
		if (Daemon::$config->logsignals->value) {
694
			$this->log('caught SIGQUIT.');
695
		}
696
697
		parent::sigquit();
698
	}
699
700
	/**
701
	 * Handler of the SIGHUP (reload config) signal in worker process.
702
	 * @return void
703
	 */
704 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...
705
		if (Daemon::$config->logsignals->value) {
706
			$this->log('caught SIGHUP (reload config).');
707
		}
708
709
		if (isset(Daemon::$config->configfile->value)) {
710
			Daemon::loadConfig(Daemon::$config->configfile->value);
711
		}
712
713
		$this->update = true;
714
	}
715
716
	/**
717
	 * Handler of the SIGUSR1 (re-open log-file) signal in worker process.
718
	 * @return void
719
	 */
720
	protected function sigusr1() {
721
		if (Daemon::$config->logsignals->value) {
722
			$this->log('caught SIGUSR1 (re-open log-file).');
723
		}
724
725
		Daemon::openLogs();
726
	}
727
728
	/**
729
	 * Handler of the SIGUSR2 (graceful shutdown for update) signal in worker process.
730
	 * @return void
731
	 */
732
	protected function sigusr2() {
733
		if (Daemon::$config->logsignals->value) {
734
			$this->log('caught SIGUSR2 (graceful shutdown for update).');
735
		}
736
737
		$this->gracefulRestart();
738
	}
739
740
	/**
741
	 * Handler of the SIGTSTP (graceful stop) signal in worker process.
742
	 * @return void
743
	 */
744
	protected function sigtstp() {
745
		if (Daemon::$config->logsignals->value) {
746
			$this->log('caught SIGTSTP (graceful stop).');
747
		}
748
749
		$this->gracefulStop();
750
	}
751
752
	/**
753
	 * Handler of the SIGTTIN signal in worker process.
754
	 * @return void
755
	 */
756
	protected function sigttin() {
757
	}
758
759
	/**
760
	 * Handler of the SIGPIPE signal in worker process.
761
	 * @return void
762
	 */
763
	protected function sigpipe() {
764
	}
765
766
	/**
767
	 * Handler of non-known signals.
768
	 * @return void
769
	 */
770 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...
771
		if (isset(Generic::$signals[$signo])) {
772
			$sig = Generic::$signals[$signo];
773
		}
774
		else {
775
			$sig = 'UNKNOWN';
776
		}
777
778
		$this->log('caught signal #' . $signo . ' (' . $sig . ').');
779
	}
780
781
	/**
782
	 * Called (in master) when process is terminated
783
	 * @return void
784
	 */
785
	public function onTerminated() {
786
		$this->setState(Daemon::WSTATE_SHUTDOWN);
787
	}
788
789
	/**
790
	 * Destructor of worker thread.
791
	 * @return void
792
	 */
793
	public function __destruct() {
794
		if (posix_getpid() === $this->pid) {
795
			$this->setState(Daemon::WSTATE_SHUTDOWN);
796
		}
797
	}
798
}
799