From 50c5bdb8d2f15a4807afd1235d1e0ed742cc7648 Mon Sep 17 00:00:00 2001 From: Markus Klein <markus.klein@typo3.org> Date: Thu, 23 Feb 2023 13:07:10 +0100 Subject: [PATCH] [BUGFIX] Output errors to CLI in scheduler:run command Whenever a scheduler task fails during execution via CLI `scheduler:run`, the error message is now always written to stderr. This gives the task executor (e.g. cron) a chance to identify any failures, if even stdout is redirected to /dev/null. The task-loop is adapted to avoid (ab)using exceptions for control-flow (for loop-abortion). It instead now uses nullables to signal that not finding a next-task is expected behavior, not an exception. Also the exit code of the command is adapted to switch to 1 in case at least one error occured. (This is important in case the cronjob is executed as systemd-timer) Resolves: #87806 Releases: main, 11.5 Change-Id: I0b55bab7fcd916209114d9e3e07f54c7243ce9a5 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/78220 Tested-by: core-ci <typo3@b13.com> Tested-by: Benjamin Franzke <benjaminfranzke@gmail.com> Reviewed-by: Benjamin Franzke <benjaminfranzke@gmail.com> --- .../Classes/Command/SchedulerCommand.php | 39 +++++++------------ typo3/sysext/scheduler/Classes/Scheduler.php | 6 +-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/typo3/sysext/scheduler/Classes/Command/SchedulerCommand.php b/typo3/sysext/scheduler/Classes/Command/SchedulerCommand.php index 64a502c86514..a207b3d65388 100644 --- a/typo3/sysext/scheduler/Classes/Command/SchedulerCommand.php +++ b/typo3/sysext/scheduler/Classes/Command/SchedulerCommand.php @@ -30,11 +30,6 @@ use TYPO3\CMS\Scheduler\Task\AbstractTask; */ class SchedulerCommand extends Command { - /** - * @var bool - */ - protected $hasTask = true; - /** * @var Scheduler */ @@ -122,8 +117,7 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f') $this->forceExecution = (bool)$input->getOption('force'); $this->stopTasks = $this->shouldStopTasks((bool)$input->getOption('stop')); - $this->loopTasks(); - return 0; + return $this->loopTasks() ? 0 : 1; } /** @@ -184,38 +178,37 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f') /** * Execute tasks in loop that are ready to execute */ - protected function loopTasks() + protected function loopTasks(): bool { + $hasError = false; do { + $task = null; // Try getting the next task and execute it // If there are no more tasks to execute, an exception is thrown by \TYPO3\CMS\Scheduler\Scheduler::fetchTask() try { $task = $this->fetchNextTask(); + if ($task === null) { + break; + } try { $this->executeOrStopTask($task); } catch (\Exception $e) { - if ($this->io->isVerbose()) { - $this->io->warning($e->getMessage()); - } + $this->io->getErrorStyle()->error($e->getMessage()); + $hasError = true; // We ignore any exception that may have been thrown during execution, // as this is a background process. // The exception message has been recorded to the database anyway continue; } - } catch (\OutOfBoundsException $e) { - if ($this->io->isVeryVerbose()) { - $this->io->writeln($e->getMessage()); - } - $this->hasTask = !empty($this->overwrittenTaskList); } catch (\UnexpectedValueException $e) { - if ($this->io->isVerbose()) { - $this->io->warning($e->getMessage()); - } + $this->io->getErrorStyle()->error($e->getMessage()); + $hasError = true; continue; } - } while ($this->hasTask); + } while ($task !== null); // Record the run in the system registry $this->scheduler->recordLastRun(); + return !$hasError; } /** @@ -224,18 +217,16 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f') * * Without the --task option we ask the scheduler for the next task with pending execution. * - * @return AbstractTask - * @throws \OutOfBoundsException When there are no more tasks to execute. * @throws \UnexpectedValueException When no task is found by the provided UID or the task is not marked for execution. */ - protected function fetchNextTask(): AbstractTask + protected function fetchNextTask(): ?AbstractTask { if ($this->overwrittenTaskList === null) { return $this->scheduler->fetchTask(); } if (count($this->overwrittenTaskList) === 0) { - throw new \OutOfBoundsException('No more tasks to execute', 1547675594); + return null; } $taskUid = (int)array_shift($this->overwrittenTaskList); diff --git a/typo3/sysext/scheduler/Classes/Scheduler.php b/typo3/sysext/scheduler/Classes/Scheduler.php index 1afa55349a01..24bfde3500f9 100644 --- a/typo3/sysext/scheduler/Classes/Scheduler.php +++ b/typo3/sysext/scheduler/Classes/Scheduler.php @@ -305,11 +305,11 @@ class Scheduler implements SingletonInterface * If there are no due tasks the method throws an exception. * * @param int $uid Primary key of a task - * @return Task\AbstractTask The fetched task object + * @return Task\AbstractTask|null The fetched task object * @throws \OutOfBoundsException * @throws \UnexpectedValueException */ - public function fetchTask($uid = 0) + public function fetchTask($uid = 0): ?AbstractTask { $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_scheduler_task'); @@ -352,7 +352,7 @@ class Scheduler implements SingletonInterface if (empty($row)) { if (empty($uid)) { // No uid was passed and no overdue task was found - throw new \OutOfBoundsException('No (more) tasks available for execution', 1247827244); + return null; } // Although a uid was passed, no task with given was found throw new \OutOfBoundsException('No task with id ' . $uid . ' found', 1422044826); -- GitLab