Skip to content
Snippets Groups Projects
Commit 69ea6509 authored by Alexander Stehlik's avatar Alexander Stehlik Committed by Benni Mack
Browse files

[FEATURE] Allow multiple task options in scheduler command

The --task option (short -i) can now be provided multiple
times to the scheduler command.

The given task UIDs are executed or stopped sequentially.

Example call:

./bin/typo3 scheduler:run --task 3 --task 2

Additionally some debug output is added when the -v or -vv flags
are provided.

Resolves: #87451
Releases: master
Change-Id: I6e1555c456d8c0f35395ca40c0a303628ddf5acf
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/59452


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: default avatarJörg Bösche <typo3@joergboesche.de>
Tested-by: default avatarBenni Mack <benni@typo3.org>
Reviewed-by: default avatarJörg Bösche <typo3@joergboesche.de>
Reviewed-by: default avatarBenni Mack <benni@typo3.org>
parent 5f6f2abe
Branches
Tags
No related merge requests found
.. include:: ../../Includes.txt
=====================================================================
Feature: #87451 - scheduler:run command accepts multiple task options
=====================================================================
See :issue:`87451`
Description
===========
The `scheduler:run` command now accepts multiple `--task` options.
The tasks will be executed in the order in which they are given:
.. code-block:: bash
./typo3/sysext/core/bin/typo3 scheduler:run --task 1 --task 2
It is now also possible to pass verbose flags to the command to get more information about what is
going on.
A single `-v` flag will output errors only. Two `-vv` flags will also output additional information.
Impact
======
The new feature allows the execution of tasks in a given order.
This can be used to debug side effects between tasks that are executed within the same scheduler run.
.. index:: CLI, ext:scheduler
......@@ -18,6 +18,7 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Scheduler\Scheduler;
......@@ -38,6 +39,30 @@ class SchedulerCommand extends Command
*/
protected $scheduler;
/**
* @var SymfonyStyle
*/
protected $io;
/**
* Array of tasks UIDs that should be executed. Null if task option is not provided.
*
* @var int[]|array|null
*/
protected $overwrittenTaskList;
/**
* This is true when the tasks should be marked as stopped instead of beeing executed.
*
* @var bool
*/
protected $stopTasks = false;
/**
* @var bool
*/
protected $forceExecution;
/**
* Configure the command by defining the name, options and arguments
*/
......@@ -50,20 +75,20 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f')
->addOption(
'task',
'i',
InputOption::VALUE_REQUIRED,
'UID of a specific task'
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'UID of a specific task. Can be provided multiple times to execute multiple tasks sequentially.'
)
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force execution of the task which is passed with -i option'
'Force execution of the task which is passed with --task option'
)
->addOption(
'stop',
's',
InputOption::VALUE_NONE,
'Stop the task which is passed with -i option'
'Stop the task which is passed with --task option'
);
}
......@@ -76,41 +101,59 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f')
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$this->io = new SymfonyStyle($input, $output);
// Make sure the _cli_ user is loaded
Bootstrap::initializeBackendAuthentication();
$this->scheduler = GeneralUtility::makeInstance(Scheduler::class);
if ((int)$input->getOption('task') > 0) {
$taskUid = (int)$input->getOption('task');
$stopTask = (bool)($input->hasOption('stop') && $input->getOption('stop'));
$force = (bool)($input->hasOption('force') && $input->getOption('force'));
$task = $this->getTask($taskUid, $stopTask || $force);
if ($this->scheduler->isValidTaskObject($task)) {
if ($stopTask) {
$this->stopTask($task);
} else {
$this->scheduler->executeTask($task);
}
}
// Record the run in the system registry
$this->scheduler->recordLastRun('cli-by-id');
} else {
$this->loopTasks();
$overwrittenTaskList = $input->getOption('task');
if ($overwrittenTaskList !== []) {
$this->overwrittenTaskList = $overwrittenTaskList;
}
$this->forceExecution = (bool)$input->getOption('force');
$this->stopTasks = $this->shouldStopTasks((bool)$input->getOption('stop'));
$this->loopTasks();
return 0;
}
/**
* Checks if the tasks should be stopped instead of beeing executed.
*
* Stopping is only performed when the --stop option is provided together with the --task option.
*
* @param bool $stopOption
* @return bool
*/
protected function shouldStopTasks(bool $stopOption): bool
{
if (!$stopOption) {
return false;
}
if ($this->overwrittenTaskList !== []) {
return true;
}
if ($this->io->isVerbose()) {
$this->io->warning('Stopping tasks is only possible when the --task option is provided.');
}
return false;
}
/**
* Stop task
*
* @param AbstractTask $task
*/
protected function stopTask($task)
protected function stopTask(AbstractTask $task)
{
if ($this->scheduler->isValidTaskObject($task)) {
$task->unmarkAllExecutions();
$task->unmarkAllExecutions();
if ($this->io->isVeryVerbose()) {
$this->io->writeln(sprintf('Task #%d was stopped', $task->getTaskUid()));
}
}
......@@ -118,18 +161,17 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f')
* Return task a task for a given UID
*
* @param int $taskUid
* @param bool $force fetch the task regardless if it is queued for execution
* @return AbstractTask
*/
protected function getTask(int $taskUid, bool $force)
protected function getTask(int $taskUid)
{
$force = $this->stopTasks || $this->forceExecution;
if ($force) {
$task = $this->scheduler->fetchTask($taskUid);
} else {
$whereClause = 'uid = ' . (int)$taskUid . ' AND nextexecution != 0 AND nextexecution <= ' . $GLOBALS['EXEC_TIME'];
[$task] = $this->scheduler->fetchTasksWithCondition($whereClause);
return $this->scheduler->fetchTask($taskUid);
}
$whereClause = 'uid = ' . (int)$taskUid . ' AND nextexecution != 0 AND nextexecution <= ' . $GLOBALS['EXEC_TIME'];
[$task] = $this->scheduler->fetchTasksWithCondition($whereClause);
return $task;
}
......@@ -142,22 +184,80 @@ Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f')
// 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->scheduler->fetchTask();
$task = $this->fetchNextTask();
try {
$this->scheduler->executeTask($task);
$this->executeOrStopTask($task);
} catch (\Exception $e) {
if ($this->io->isVerbose()) {
$this->io->warning($e->getMessage());
}
// 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) {
$this->hasTask = false;
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());
}
continue;
}
} while ($this->hasTask);
// Record the run in the system registry
$this->scheduler->recordLastRun();
}
/**
* When the --task option is provided, the next task is fetched from the provided task UIDs. Depending
* on the --force option the task is fechted even if it is not marked for execution.
*
* 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
{
if ($this->overwrittenTaskList === null) {
return $this->scheduler->fetchTask();
}
if (count($this->overwrittenTaskList) === 0) {
throw new \OutOfBoundsException('No more tasks to execute', 1547675594);
}
$taskUid = (int)array_shift($this->overwrittenTaskList);
$task = $this->getTask($taskUid);
if (!$this->scheduler->isValidTaskObject($task)) {
throw new \UnexpectedValueException(
sprintf('The task #%d is not scheduled for execution or does not exist.', $taskUid),
1547675557
);
}
return $task;
}
/**
* When in stop mode the given task is stopped. Otherwise the task is executed.
*
* @param AbstractTask $task
*/
protected function executeOrStopTask(AbstractTask $task): void
{
if ($this->stopTasks) {
$this->stopTask($task);
return;
}
$this->scheduler->executeTask($task);
if ($this->io->isVeryVerbose()) {
$this->io->writeln(sprintf('Task #%d was executed', $task->getTaskUid()));
}
}
}
......@@ -336,7 +336,7 @@ class Scheduler implements SingletonInterface, LoggerAwareInterface
if (empty($row)) {
if (empty($uid)) {
// No uid was passed and no overdue task was found
throw new \OutOfBoundsException('No tasks available for execution', 1247827244);
throw new \OutOfBoundsException('No (more) tasks available for execution', 1247827244);
}
// Although a uid was passed, no task with given was found
throw new \OutOfBoundsException('No task with id ' . $uid . ' found', 1422044826);
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment