Passed
Pull Request — master (#44)
by Mischa ter
01:40
created

QueueShell::runworker()   D

Complexity

Conditions 16
Paths 176

Size

Total Lines 81
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 16
eloc 56
c 5
b 1
f 0
nc 176
nop 0
dl 0
loc 81
rs 4.9333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
App::uses('Folder', 'Utility');
3
App::uses('QueuedTask', 'Model');
4
App::uses('AppShell', 'Console/Command');
5
App::uses('CakeText', 'Utility');
6
7
declare(ticks = 1);
8
9
/**
10
 * Queue Shell.
11
 *
12
 * @property Queue.QueuedTask $QueuedTask
13
 */
0 ignored issues
show
Documentation Bug introduced by
The doc comment Queue.QueuedTask at position 0 could not be parsed: Unknown type name 'Queue.QueuedTask' at position 0 in Queue.QueuedTask.
Loading history...
14
class QueueShell extends AppShell {
0 ignored issues
show
Bug introduced by
The type AppShell was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
16
/**
17
 * An array of names of models to load.
18
 *
19
 * @var array
20
 */
21
	public $uses = ['Queue.QueuedTask'];
22
23
/**
24
 * A list of available queue tasks and their individual configurations.
25
 *
26
 * @var array
27
 */
28
	protected $_taskConf;
29
30
/**
31
 * Indicates whether or not the worker should exit on next the iteration.
32
 *
33
 * @var bool
34
 */
35
	private $__exit;
36
37
/**
38
 * Overwrite shell initialize to dynamically load all queue related tasks.
39
 *
40
 * @return void
41
 */
42
	public function initialize() {
43
		// Check for tasks inside plugins and application
44
		$paths = App::path('Console/Command/Task');
45
46
		foreach ($paths as $path) {
47
			$Folder = new Folder($path);
0 ignored issues
show
Bug introduced by
The type Folder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
48
			$res = array_merge($this->tasks, $Folder->find('Queue.*\.php'));
49
			foreach ($res as &$r) {
50
				$r = basename($r, 'Task.php');
51
			}
52
53
			$this->tasks = $res;
0 ignored issues
show
Bug Best Practice introduced by
The property tasks does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
54
		}
55
56
		$plugins = App::objects('plugin');
57
		foreach ($plugins as $plugin) {
58
			$pluginPaths = App::path('Console/Command/Task', $plugin);
59
			foreach ($pluginPaths as $pluginPath) {
60
				$Folder = new Folder($pluginPath);
61
				$res = $Folder->find('Queue.*Task\.php');
62
				foreach ($res as &$r) {
63
					$r = $plugin . '.' . basename($r, 'Task.php');
64
				}
65
66
				$this->tasks = array_merge($this->tasks, $res);
67
			}
68
		}
69
70
		$conf = Configure::read('Queue');
0 ignored issues
show
Bug introduced by
The type Configure was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
71
		if (!is_array($conf)) {
72
			$conf = [];
73
		}
74
75
		// Merge with default configuration vars.
76
		Configure::write('Queue', array_merge(
77
				[
78
					'workers' => 3,
79
					'sleepTime' => 10,
80
					'gcprop' => 10,
81
					'defaultWorkerTimeout' => 2 * MINUTE,
0 ignored issues
show
Bug introduced by
The constant MINUTE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
82
					'defaultWorkerRetries' => 4,
83
					'workerMaxRuntime' => 0,
84
					'cleanupTimeout' => DAY,
0 ignored issues
show
Bug introduced by
The constant DAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
85
					'exitWhenNothingToDo' => false,
86
					'gcOnExit' => true,
87
				],
88
				$conf
89
			)
90
		);
91
92
		parent::initialize();
93
	}
94
95
/**
96
 * Gets and configures the option parser.
97
 *
98
 * @return ConsoleOptionParser
0 ignored issues
show
Bug introduced by
The type ConsoleOptionParser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
99
 */
100
	public function getOptionParser() {
101
		$parser = parent::getOptionParser();
102
		$parser->addSubcommand('add', [
103
			'help' => __d('queue', 'Tries to call the cli `add()` function on a task.'),
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

103
			'help' => /** @scrutinizer ignore-call */ __d('queue', 'Tries to call the cli `add()` function on a task.'),
Loading history...
104
			'parser' => [
105
				'description' => [
106
					__d('queue', 'Tries to call the cli `add()` function on a task.'),
107
					__d('queue', 'Tasks may or may not provide this functionality.')
108
				],
109
				'arguments' => [
110
					'taskname' => [
111
						'help' => __d('queue', 'Name of the task.'),
112
						'required' => true,
113
						'choices' => $this->taskNames
114
					],
115
					'taskdata' => [
116
						'help' => __d('queue', 'Data needed by task.'),
117
						'required' => false,
118
					]
119
				]
120
			]
121
		])->addSubcommand('runworker', [
122
			'help' => __d('queue', 'Run a queue worker.'),
123
			'parser' => [
124
				'description' => [__d('queue', 'Run a queue worker, which will look for a pending task it can execute.')],
125
				'options' => [
126
					'type' => [
127
						'short' => 't',
128
						'help' => 'Type (comma separated list possible)',
129
						'default' => null
130
					]
131
				]
132
			]
133
		])->addSubcommand('stats', [
134
			'help' => __d('queue', 'Display general statistics.'),
135
			'parser' => [
136
				'description' => __d('queue', 'Display general statistics.'),
137
			]
138
		])->addSubcommand('clean', [
139
			'help' => __d('queue', 'Manually call cleanup function to delete task data of completed tasks.'),
140
			'parser' => [
141
				'description' => __d('queue', 'Manually call cleanup function to delete task data of completed tasks.')
142
			]
143
		])->addSubcommand('clean_failed', [
144
			'help' => __d('queue', 'Manually call cleanup function to delete task data of failed tasks.'),
145
			'parser' => [
146
				'description' => __d('queue', 'Manually call cleanup function to delete task data of failed tasks.')
147
			]
148
		])->description(__d('queue', 'CakePHP Queue Plugin.'));
149
150
		return $parser;
151
	}
152
153
/**
154
 * Looks for a queue task of the passed name and try to call add() on it.
155
 *
156
 *	A queue task may provide an add function to enable the user to create new tasks via commandline.
157
 *
158
 * @return void
159
 */
160
	public function add() {
161
		$name = Inflector::camelize($this->args[0]);
0 ignored issues
show
Bug introduced by
The type Inflector was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
162
163
		if (in_array($name, $this->taskNames)) {
164
			$this->{$name}->add();
165
		} elseif (in_array('Queue' . $name, $this->taskNames)) {
166
			$this->{'Queue' . $name}->add();
167
		} else {
168
			$this->out(__d('queue', 'Error: Task not Found: %s', $name));
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
			$this->out(/** @scrutinizer ignore-call */ __d('queue', 'Error: Task not Found: %s', $name));
Loading history...
169
			$this->out('Available Tasks:');
170
			foreach ($this->taskNames as $loadedTask) {
171
				$this->out(' * ' . $this->_taskName($loadedTask));
172
			}
173
		}
174
	}
175
176
/**
177
 * Output the task without Queue or Task
178
 * example: QueueImageTask becomes Image on display
179
 *
180
 * @param string $task A task name
181
 * @return string Cleaned task name
182
 */
183
	protected function _taskName($task) {
184
		if (strpos($task, 'Queue') === 0) {
185
			return substr($task, 5);
186
		}
187
188
		return $task;
189
	}
190
191
/**
192
 * Run a queue worker loop.
193
 *
194
 *	Runs a queue worker process which will try to find unassigned tasks in the queue
195
 *	which it may run and try to fetch and execute them.
196
 *
197
 * @return void
198
 */
199
	public function runworker() {
200
		// Enable garbage collector (PHP >= 5.3)
201
		if (function_exists('gc_enable')) {
202
			gc_enable();
203
		}
204
205
		// Register signal handler(s) if possible
206
		if (function_exists('pcntl_signal')) {
207
			pcntl_signal(SIGTERM, [$this, 'signalHandler']);
208
			pcntl_signal(SIGINT, [$this, 'signalHandler']);
209
		} else {
210
			$this->err(__d('queue', 'Signal handler(s) could not be registered.'));
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

210
			$this->err(/** @scrutinizer ignore-call */ __d('queue', 'Signal handler(s) could not be registered.'));
Loading history...
211
		}
212
213
		$this->__exit = false;
214
215
		$workerStartTime = time();
216
217
		$typesParam = $this->param('type');
218
		$types = is_string($typesParam) ? $this->_stringToArray($typesParam) : [];
219
220
		while (!$this->__exit) {
221
			$this->out(__d('queue', 'Looking for a job.'), 1, Shell::VERBOSE);
0 ignored issues
show
Bug introduced by
The type Shell was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
222
223
			$data = $this->QueuedTask->requestJob($this->_getTaskConf(), $types);
224
			if ($this->QueuedTask->exit === true) {
225
				$this->__exit = true;
226
			} else {
227
				if ($data !== false) {
228
					$jobId = $data['id'];
229
					$taskname = 'Queue' . $data['task'];
230
					$this->out(__d('queue', 'Running job of task \'%s\' \'%d\'.', $data['task'], $jobId));
231
232
					$taskStartTime = time();
233
					$return = $this->{$taskname}->run(unserialize($data['data']));
234
					$took = time() - $taskStartTime;
235
					if ($return) {
236
						$this->QueuedTask->markJobDone($jobId);
237
						$this->out(
238
							__d(
239
								'queue',
240
								'Job \'%d\' finished (took %s).',
241
								$jobId,
242
								__dn('queue', '%d second', '%d seconds', $took, $took)
0 ignored issues
show
Bug introduced by
The function __dn was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

242
								/** @scrutinizer ignore-call */ 
243
        __dn('queue', '%d second', '%d seconds', $took, $took)
Loading history...
243
							)
244
						);
245
					} else {
246
						$failureMessage = null;
247
						if (isset($this->{$taskname}->failureMessage) && !empty($this->{$taskname}->failureMessage)) {
248
							$failureMessage = $this->{$taskname}->failureMessage;
249
						}
250
						$this->QueuedTask->markJobFailed($jobId, $failureMessage);
251
						$this->out(__d('queue', 'Job \'%d\' did not finish, requeued.', $jobId));
252
					}
253
				} elseif (Configure::read('Queue.exitWhenNothingToDo')) {
254
					$this->out(__d('queue', 'Nothing to do, exiting.'));
255
					$this->__exit = true;
256
				} else {
257
					$this->out(
258
						__d('queue', 'Nothing to do, sleeping for %d second(s).', Configure::read('Queue.sleepTime')),
259
						1, Shell::VERBOSE
260
					);
261
262
					sleep(Configure::read('Queue.sleepTime'));
263
				}
264
265
				// Check if we are over the maximum runtime and end processing if so.
266
				if (Configure::read('Queue.workerMaxRuntime') != 0
267
						&& (time() - $workerStartTime) >= Configure::read('Queue.workerMaxRuntime')
268
				) {
269
					$this->__exit = true;
270
					$this->out(__d('queue',
271
						'Reached runtime of %s seconds (max. %s), terminating.',
272
						(time() - $workerStartTime),
273
						Configure::read('Queue.workerMaxRuntime')
274
					));
275
				}
276
277
				if (($this->_exit && Configure::read('Queue.gcOnExit')) || rand(0, 100) > (100 - Configure::read('Queue.gcprop'))) {
278
					$this->out(__d('queue', 'Performing old job cleanup.'));
279
					$this->QueuedTask->cleanOldJobs($this->_getTaskConf());
280
				}
281
			}
282
		}
283
	}
284
285
/**
286
 * Triggers manual job cleanup of completed jobs.
287
 *
288
 * @return void
289
 */
290
	public function clean() {
291
		$this->out(__d('queue', 'Deleting old completed jobs, that have had cleanup timeout.'));
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

291
		$this->out(/** @scrutinizer ignore-call */ __d('queue', 'Deleting old completed jobs, that have had cleanup timeout.'));
Loading history...
292
		$this->QueuedTask->cleanOldJobs($this->_getTaskConf());
293
	}
294
295
/**
296
 * Triggers manual job cleanup of failed jobs.
297
 *
298
 * @return void
299
 */
300
	public function clean_failed() {
301
		$this->out(__d('queue', 'Deleting failed Jobs, that have had maximum worker retries.'));
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

301
		$this->out(/** @scrutinizer ignore-call */ __d('queue', 'Deleting failed Jobs, that have had maximum worker retries.'));
Loading history...
302
		$this->QueuedTask->cleanFailedJobs($this->_getTaskConf());
303
	}
304
305
/**
306
 * Displays some statistics about finished Jobs.
307
 *
308
 * @return void
309
 */
310
	public function stats() {
311
		$this->hr();
312
		$this->out(__d('queue', 'Jobs currenty in the queue:'));
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

312
		$this->out(/** @scrutinizer ignore-call */ __d('queue', 'Jobs currenty in the queue:'));
Loading history...
313
		$this->hr();
314
315
		$types = $this->QueuedTask->getTypes();
316
		foreach ($types as $type) {
317
			$this->out(sprintf('- %s: %s', $type, $this->QueuedTask->getLength($type)));
318
		}
319
		$this->out();
320
321
		$this->hr();
322
		$this->out(__d('queue', 'Total unfinished jobs: %s', $this->QueuedTask->getLength()));
323
		$this->hr();
324
		$this->out();
325
326
		$this->hr();
327
		$this->out(__d('queue', 'Finished job statistics:'));
328
		$this->hr();
329
330
		$data = $this->QueuedTask->getStats();
331
		foreach ($data as $item) {
332
			$this->out(sprintf('- %s: ', $item['QueuedTask']['task']));
333
			$this->out(sprintf('  - %s', __d('queue', 'Finished jobs in database: %s', $item[0]['num'])));
334
			$this->out(sprintf('  - %s', __d('queue', 'Average job existence: %ss', $item[0]['alltime'])));
335
			$this->out(sprintf('  - %s', __d('queue', 'Average execution delay: %ss', $item[0]['fetchdelay'])));
336
			$this->out(sprintf('  - %s', __d('queue', 'Average execution time: %ss', $item[0]['runtime'])));
337
			$this->out();
338
		}
339
	}
340
341
/**
342
 * Returns a list of available queue tasks and their individual configurations.
343
 *
344
 * @return array A list of available queue tasks and their individual configurations
345
 */
346
	protected function _getTaskConf() {
347
		if (!is_array($this->_taskConf)) {
0 ignored issues
show
introduced by
The condition is_array($this->_taskConf) is always true.
Loading history...
348
			$this->_taskConf = [];
349
			foreach ($this->tasks as $task) {
350
				list($pluginName, $taskName) = pluginSplit($task);
0 ignored issues
show
Bug introduced by
The function pluginSplit was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

350
				list($pluginName, $taskName) = /** @scrutinizer ignore-call */ pluginSplit($task);
Loading history...
351
352
				$this->_taskConf[$taskName]['name'] = substr($taskName, 5);
353
				$this->_taskConf[$taskName]['plugin'] = $pluginName;
354
355
				if (property_exists($this->{$taskName}, 'timeout')) {
356
					$this->_taskConf[$taskName]['timeout'] = $this->{$taskName}->timeout;
357
				} else {
358
					$this->_taskConf[$taskName]['timeout'] = Configure::read('Queue.defaultWorkerTimeout');
359
				}
360
				if (property_exists($this->{$taskName}, 'retries')) {
361
					$this->_taskConf[$taskName]['retries'] = $this->{$taskName}->retries;
362
				} else {
363
					$this->_taskConf[$taskName]['retries'] = Configure::read('Queue.defaultWorkerRetries');
364
				}
365
				if (property_exists($this->{$taskName}, 'cleanupTimeout')) {
366
					$this->_taskConf[$taskName]['cleanupTimeout'] = $this->{$taskName}->cleanupTimeout;
367
				} else {
368
					$this->_taskConf[$taskName]['cleanupTimeout'] = Configure::read('Queue.cleanupTimeout');
369
				}
370
			}
371
		}
372
373
		return $this->_taskConf;
374
	}
375
376
/**
377
 * Signal handler (for SIGTERM and SIGINT signal)
378
 *
379
 * @param int $signalNumber A signal number
380
 * @return void
381
 */
382
	public function signalHandler($signalNumber) {
383
		switch($signalNumber) {
384
			case SIGTERM:
385
				$this->out(__d('queue', 'Caught %s signal, exiting.', sprintf('SIGTERM (%d)', SIGTERM)));
0 ignored issues
show
Bug introduced by
The function __d was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

385
				$this->out(/** @scrutinizer ignore-call */ __d('queue', 'Caught %s signal, exiting.', sprintf('SIGTERM (%d)', SIGTERM)));
Loading history...
386
387
				$this->__exit = true;
388
				break;
389
			case SIGINT:
390
				$this->out(__d('queue', 'Caught %s signal, exiting.', sprintf('SIGINT (%d)', SIGINT)));
391
392
				$this->__exit = true;
393
				break;
394
		}
395
	}
396
397
/**
398
 * Converts string to array
399
 *
400
 * @param string|null $param String to convert
401
 * @return array
402
 */
403
	protected function _stringToArray(string $param = null) : array {
404
		if (!$param) {
405
			return [];
406
		}
407
408
		$array = CakeText::tokenize($param);
0 ignored issues
show
Bug introduced by
The type CakeText was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
409
		if (is_string($array)) {
410
			return [
411
				$array
412
			];
413
		}
414
415
		return array_filter($array);
416
	}
417
418
}
419