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