ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
JobManagerImpl.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
25 
26 readonly class JobManagerImpl implements \ILIAS\Cron\Job\JobManager
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 }
static array static setUseRelativeDates(bool $a_status)
set use relative dates
resetJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor)
runJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, ?array $jobData=null, bool $isManualExecution=false)
Run single cron job (internal)
Interface Observer Contains several chained tasks and infos about them.
static subStr(string $a_str, int $a_start, ?int $a_length=null)
Definition: class.ilStr.php:21
const IL_CAL_UNIX
static _lookupValue(string $a_module, string $a_keyword)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
__construct(private JobRepository $job_repository, private \ilDBInterface $db, private \ilSetting $settings, private \ilLogger $logger, private \ILIAS\Data\Clock\ClockFactory $clock_factory)
static _getHttpPath()
deactivateJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, bool $wasManuallyExecuted=false)
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
activateJob(\ILIAS\Cron\CronJob $job, \ilObjUser $actor, bool $wasManuallyExecuted=false)