ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
JobManagerImpl.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
25
27{
28 public function __construct(
29 private JobRepository $job_repository,
30 private \ilDBInterface $db,
31 private \ilSetting $settings,
32 private \ilLogger $logger,
33 private \ILIAS\Data\Clock\ClockFactory $clock_factory
34 ) {
35 }
36
37 private function getMicrotime(): float
38 {
39 return ((int) $this->clock_factory->system()->now()->format('Uu')) / 1000000;
40 }
41
42 public function runActiveJobs(\ilObjUser $actor): void
43 {
44 $this->logger->info('CRON - batch start');
45
46 $ts = $this->clock_factory->system()->now()->getTimestamp();
47 $this->settings->set('last_cronjob_start_ts', (string) $ts);
48
49 $useRelativeDates = \ilDatePresentation::useRelativeDates();
51 $this->logger->info(
52 \sprintf(
53 'Set last datetime to: %s',
55 )
56 );
57 $this->logger->info(
58 \sprintf(
59 'Verification of last run datetime (read from database): %s',
61 new \ilDateTime(\ilSetting::_lookupValue('common', 'last_cronjob_start_ts'), IL_CAL_UNIX)
62 )
63 )
64 );
66
67 // ilLink::_getStaticLink() should work in crons
68 if (!\defined('ILIAS_HTTP_PATH')) {
69 \define('ILIAS_HTTP_PATH', \ilUtil::_getHttpPath());
70 }
71
72 // system
73 foreach ($this->job_repository->getCronJobData(null, false) as $row) {
74 $job = $this->job_repository->getJobInstanceById($row['job_id']);
75 if ($job instanceof \ILIAS\Cron\CronJob) {
76 // #18411 - we are NOT using the initial job data as it might be outdated at this point
77 $this->runJob($job, $actor);
78 }
79 }
80
81 // plugins
82 foreach ($this->job_repository->getPluginJobs(true) as $item) {
83 // #18411 - we are NOT using the initial job data as it might be outdated at this point
84 $this->runJob($item[0], $actor);
85 }
86
87 $this->logger->info('CRON - batch end');
88 }
89
90 public function runJobManual(string $jobId, \ilObjUser $actor): bool
91 {
92 $result = false;
93
94 $this->logger->info('CRON - manual start (' . $jobId . ')');
95
96 $job = $this->job_repository->getJobInstanceById($jobId);
97 if ($job instanceof \ILIAS\Cron\CronJob) {
98 if ($job->isManuallyExecutable()) {
99 $result = $this->runJob($job, $actor, null, true);
100 } else {
101 $this->logger->info('CRON - job ' . $jobId . ' is not intended to be executed manually');
102 }
103 } else {
104 $this->logger->info('CRON - job ' . $jobId . ' seems invalid or is inactive');
105 }
106
107 $this->logger->info('CRON - manual end (' . $jobId . ')');
108
109 return $result;
110 }
111
117 private function runJob(
118 \ILIAS\Cron\CronJob $job,
119 \ilObjUser $actor,
120 ?array $jobData = null,
121 bool $isManualExecution = false
122 ): bool {
123 $did_run = false;
124
125 if ($jobData === null) {
126 // aquire "fresh" job (status) data
127 $jobsData = $this->job_repository->getCronJobData($job->getId());
128 $jobData = array_pop($jobsData);
129 }
130
131 $job->setDateTimeProvider(fn(): \DateTimeImmutable => $this->clock_factory->system()->now());
132
133 // already running?
134 if ($jobData['alive_ts']) {
135 $this->logger->info('CRON - job ' . $jobData['job_id'] . ' still running');
136
137 $cut = 60 * 60 * 3;
138
139 // is running (and has not pinged) for 3 hours straight, we assume it crashed
140 if ($this->clock_factory->system()->now()->getTimestamp() - ((int) $jobData['alive_ts']) > $cut) {
141 $this->job_repository->updateRunInformation($jobData['job_id'], 0, 0);
142 $this->deactivateJob($job, $actor); // #13082
143
144 $result = new \ILIAS\Cron\Job\JobResult();
145 $result->setStatus(\ILIAS\Cron\Job\JobResult::STATUS_CRASHED);
146 $result->setCode(\ILIAS\Cron\Job\JobResult::CODE_SUPPOSED_CRASH);
147 $result->setMessage('Cron job deactivated because it has been inactive for 3 hours');
148
149 $this->job_repository->updateJobResult(
150 $job,
151 $this->clock_factory->system()->now(),
152 $actor,
153 $result,
154 $isManualExecution
155 );
156
157 $this->logger->info('CRON - job ' . $jobData['job_id'] . ' deactivated (assumed crash)');
158 }
159 } // initiate run?
160 elseif ($job->isDue(
161 $jobData['job_result_ts'] ? (new \DateTimeImmutable(
162 '@' . $jobData['job_result_ts']
163 ))->setTimezone($this->clock_factory->system()->now()->getTimezone()) : null,
164 is_numeric($jobData['schedule_type']) ? JobScheduleType::tryFrom(
165 (int) $jobData['schedule_type']
166 ) : null,
167 $jobData['schedule_value'] ? (int) $jobData['schedule_value'] : null,
168 $isManualExecution
169 )) {
170 $this->logger->info('CRON - job ' . $jobData['job_id'] . ' started');
171
172 $this->job_repository->updateRunInformation(
173 $jobData['job_id'],
174 $this->clock_factory->system()->now()->getTimestamp(),
175 $this->clock_factory->system()->now()->getTimestamp()
176 );
177
178 $ts_in = $this->getMicrotime();
179 try {
180 $result = $job->run();
181 } catch (\Throwable $e) {
182 $result = new \ILIAS\Cron\Job\JobResult();
183 $result->setStatus(\ILIAS\Cron\Job\JobResult::STATUS_CRASHED);
184 $result->setMessage(
185 \ilStr::subStr(\sprintf('Exception: %s / %s', $e->getMessage(), $e->getTraceAsString()), 0, 400)
186 );
187
188 $this->logger->error($e->getMessage());
189 $this->logger->error($e->getTraceAsString());
190 } finally {
191 $ts_dur = $this->getMicrotime() - $ts_in;
192 }
193
194 if ($result->getStatus() === \ILIAS\Cron\Job\JobResult::STATUS_INVALID_CONFIGURATION) {
195 $this->deactivateJob($job, $actor);
196 $this->logger->info('CRON - job ' . $jobData['job_id'] . ' invalid configuration');
197 } else {
198 // success!
199 $did_run = true;
200 }
201
202 $result->setDuration($ts_dur);
203
204 $this->job_repository->updateJobResult(
205 $job,
206 $this->clock_factory->system()->now(),
207 $actor,
208 $result,
209 $isManualExecution
210 );
211 $this->job_repository->updateRunInformation($jobData['job_id'], 0, 0);
212
213 $this->logger->info('CRON - job ' . $jobData['job_id'] . ' finished');
214 } else {
215 $this->logger->info('CRON - job ' . $jobData['job_id'] . ' returned status inactive');
216 }
217
218 return $did_run;
219 }
220
221 public function resetJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor): void
222 {
223 $result = new \ILIAS\Cron\Job\JobResult();
224 $result->setStatus(\ILIAS\Cron\Job\JobResult::STATUS_RESET);
225 $result->setCode(\ILIAS\Cron\Job\JobResult::CODE_MANUAL_RESET);
226 $result->setMessage('Cron job re-activated by admin');
227
228 $this->job_repository->updateJobResult(
229 $job,
230 $this->clock_factory->system()->now(),
231 $actor,
232 $result,
233 true
234 );
235 $this->job_repository->resetJob($job);
236
237 $this->activateJob($job, $actor, true);
238 }
239
240 public function activateJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, bool $wasManuallyExecuted = false): void
241 {
242 $this->job_repository->activateJob($job, $this->clock_factory->system()->now(), $actor, $wasManuallyExecuted);
243 $job->activationWasToggled($this->db, $this->settings, true);
244 }
245
246 public function deactivateJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, bool $wasManuallyExecuted = false): void
247 {
248 $this->job_repository->deactivateJob($job, $this->clock_factory->system()->now(), $actor, $wasManuallyExecuted);
249 $job->activationWasToggled($this->db, $this->settings, false);
250 }
251
252 public function isJobActive(string $jobId): bool
253 {
254 $jobs_data = $this->job_repository->getCronJobData($jobId);
255
256 return $jobs_data !== [] && $jobs_data[0]['job_status'];
257 }
258
259 public function isJobInactive(string $jobId): bool
260 {
261 $jobs_data = $this->job_repository->getCronJobData($jobId);
262
263 return $jobs_data !== [] && !((bool) $jobs_data[0]['job_status']);
264 }
265
266 public function ping(string $jobId): void
267 {
268 $this->db->manipulateF(
269 'UPDATE cron_job SET alive_ts = %s WHERE job_id = %s',
270 ['integer', 'text'],
271 [$this->clock_factory->system()->now()->getTimestamp(), $jobId]
272 );
273 }
274}
final const int STATUS_RESET
Definition: JobResult.php:29
final const int STATUS_CRASHED
Definition: JobResult.php:28
final const string CODE_SUPPOSED_CRASH
Definition: JobResult.php:34
final const string CODE_MANUAL_RESET
Definition: JobResult.php:33
final const int STATUS_INVALID_CONFIGURATION
Definition: JobResult.php:25
resetJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor)
deactivateJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, bool $wasManuallyExecuted=false)
activateJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, bool $wasManuallyExecuted=false)
__construct(private JobRepository $job_repository, private \ilDBInterface $db, private \ilSetting $settings, private \ilLogger $logger, private \ILIAS\Data\Clock\ClockFactory $clock_factory)
runJobManual(string $jobId, \ilObjUser $actor)
runJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, ?array $jobData=null, bool $isManualExecution=false)
Run single cron job (internal)
const IL_CAL_UNIX
static setUseRelativeDates(bool $a_status)
set use relative dates
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
@classDescription Date and time handling
Component logger with individual log levels by component id.
User class.
ILIAS Setting Class.
static _lookupValue(string $a_module, string $a_keyword)
static subStr(string $a_str, int $a_start, ?int $a_length=null)
Definition: class.ilStr.php:21
static _getHttpPath()
Interface ilDBInterface.
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.