CronMaster::run()   C
last analyzed

Complexity

Conditions 10
Paths 61

Size

Total Lines 48
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 48
rs 5.3454
c 0
b 0
f 0
cc 10
eloc 29
nc 61
nop 0

How to fix   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
3
namespace Ridibooks\Platform\Common\Cron;
4
5
use Exception;
6
use Ridibooks\Exception\MsgException;
7
use Ridibooks\Library\SentryHelper;
8
use Ridibooks\Platform\Common\Cron\Dto\CronMasterConfigDto;
9
use Ridibooks\Platform\Common\Cron\Interfaced\CronInterface;
10
11
class CronMaster
12
{
13
	/** @var CronMasterConfigDto */
14
	private $config_dto;
15
16
	public function __construct(CronMasterConfigDto $config_dto)
17
	{
18
		$this->config_dto = $config_dto;
19
	}
20
21
	public function run()
22
	{
23
		$pid_to_cron_class = [];
24
		foreach ($this->config_dto->working_classes as $cron_class) {
25
			try {
26
				/** @var $cron CronInterface */
27
				$cron = new $cron_class;
28
				$lock_fd = $this->tryLock($this->getShortClassName($cron));
29
				if (!$lock_fd) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lock_fd of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
30
					continue;
31
				}
32
33
				$pid = pcntl_fork();
34
				if ($pid === -1) {
35
					throw new MsgException('could not fork : ' . $cron_class);
36
				} elseif ($pid > 0) { //parent
37
					$pid_to_cron_class[$pid] = $cron_class;
38
					continue;
39
				}
40
41
				$this->runChildProcess($cron);
42
43
				fclose($lock_fd);
44
45
				return;
46
			} catch (\Exception $e) {
47
				SentryHelper::triggerSentryException($e);
48
			}
49
		}
50
51
		//waiting for children
52
		while (true) {
53
			$pid = pcntl_waitpid(0, $status);
54
			if ($pid === -1) {
55
				break;
56
			}
57
58
			if (!pcntl_wifexited($status)) {
59
				$message = 'process not normal exit : ' . $pid_to_cron_class[$pid] . ' / ' . $status;
60
				SentryHelper::triggerSentryMessage($message);
61
			}
62
63
			$first_pid = array_keys($pid_to_cron_class)[0];
64
			if ($pid === $first_pid) {
65
				$this->notifyCronIsRunning();
66
			}
67
		}
68
	}
69
70
	private function runChildProcess(CronInterface $cron)
71
	{
72
		try {
73
			$cron_unique_name = $this->getShortClassName($cron);
74
75
			$last_executed_datetime = $this->getLastExecutedDatetime($cron_unique_name);
76
			if (!$cron->isTimeToRun($last_executed_datetime)) {
77
				return;
78
			}
79
			if ($cron->run()) {
80
				$this->config_dto->cron_history_model->insertLogExecuted($cron_unique_name);
81
			}
82
		} catch (MsgException $e) {
0 ignored issues
show
Bug introduced by
The class Ridibooks\Exception\MsgException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
83
			SentryHelper::triggerSentryException($e);
84
		}
85
	}
86
87
	private function notifyCronIsRunning()
88
	{
89
		if ($this->config_dto->is_under_dev) {
90
			return;
91
		}
92
93
		$this->config_dto->health_check_ping_service->ping();
94
	}
95
96
	private function getLastExecutedDatetime(string $cron_class_name): \DateTime
97
	{
98
		$last_time = $this->config_dto->cron_history_model->getLastTime($cron_class_name);
99
		if (!empty($last_time)) {
100
			return new \DateTime($last_time);
101
		}
102
103
		return new \DateTime('1970-01-01 00:00:00 GMT');
104
	}
105
106
	/**
107
	 * @param string $lock_unique_name
108
	 *
109
	 * @return resource|null|bool
110
	 * @throws Exception
111
	 */
112
	private function tryLock(string $lock_unique_name)
113
	{
114
		$lock = fopen(
115
			sys_get_temp_dir() . '/' . $this->config_dto->file_lock_prefix . '.'
116
			. $this->config_dto->project_name . '.' . $lock_unique_name . '.lock',
117
			'c+'
118
		);
119
		if (!$lock) {
120
			throw new Exception('failed on tryLock : ' . $lock_unique_name);
121
		}
122
		if (flock($lock, LOCK_EX | LOCK_NB)) {
123
			return $lock;
124
		}
125
126
		return null;
127
	}
128
129
	private function getShortClassName($obj): string
130
	{
131
		$path = explode('\\', get_class($obj));
132
133
		return array_pop($path);
134
	}
135
}
136