CronMaster::notifyCronIsRunning()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
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