You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
diff --git a/administrator/components/com_scheduler/src/Scheduler/Scheduler.php b/administrator/components/com_scheduler/src/Scheduler/Scheduler.php
index a0688d560eaf..22fd589a13eb 100644
--- a/administrator/components/com_scheduler/src/Scheduler/Scheduler.php+++ b/administrator/components/com_scheduler/src/Scheduler/Scheduler.php@@ -39,10 +39,11 @@
class Scheduler
{
private const LOG_TEXT = [
- Status::OK => 'COM_SCHEDULER_SCHEDULER_TASK_COMPLETE',- Status::NO_LOCK => 'COM_SCHEDULER_SCHEDULER_TASK_LOCKED',- Status::NO_RUN => 'COM_SCHEDULER_SCHEDULER_TASK_UNLOCKED',- Status::NO_ROUTINE => 'COM_SCHEDULER_SCHEDULER_TASK_ROUTINE_NA',+ Status::OK => 'COM_SCHEDULER_SCHEDULER_TASK_COMPLETE',+ Status::WILL_RESUME => 'COM_SCHEDULER_SCHEDULER_TASK_WILL_RESUME',+ Status::NO_LOCK => 'COM_SCHEDULER_SCHEDULER_TASK_LOCKED',+ Status::NO_RUN => 'COM_SCHEDULER_SCHEDULER_TASK_UNLOCKED',+ Status::NO_ROUTINE => 'COM_SCHEDULER_SCHEDULER_TASK_ROUTINE_NA',
];
/**
@@ -158,7 +159,7 @@ public function runTask(array $options): ?Task
if (\array_key_exists($exitCode, self::LOG_TEXT))
{
- $level = $exitCode === Status::OK ? 'info' : 'warning';+ $level = in_array($exitCode, [Status::OK, Status::WILL_RESUME]) ? 'info' : 'warning';
$task->log(Text::sprintf(self::LOG_TEXT[$exitCode], $taskId, $duration, $netDuration), $level);
return $task;
diff --git a/administrator/components/com_scheduler/src/Task/Status.php b/administrator/components/com_scheduler/src/Task/Status.php
index 09dddc449379..50f4673577de 100644
--- a/administrator/components/com_scheduler/src/Task/Status.php+++ b/administrator/components/com_scheduler/src/Task/Status.php@@ -67,6 +67,19 @@ abstract class Status
*/
public const KNOCKOUT = 5;
+ /**+ * Exit code used when a task needs to resume (reschedule it to run a.s.a.p.).+ *+ * Use this for long running tasks, e.g. batch processing of hundreds or thousands of files,+ * sending newsletters with thousands of subscribers etc. These are tasks which might run out of+ * memory and/or hit a time limit when lazy scheduling or web triggering of tasks is being used.+ * Split them into smaller batches which return Status::WILL_RESUME. When the last batch is+ * executed return Status::OK.+ *+ * @since 4.1.0+ */+ public const WILL_RESUME = 123;+
/**
* Exit code used when a task times out.
*
diff --git a/administrator/components/com_scheduler/src/Task/Task.php b/administrator/components/com_scheduler/src/Task/Task.php
index df94e24d8c97..fdc0b3e98720 100644
--- a/administrator/components/com_scheduler/src/Task/Task.php+++ b/administrator/components/com_scheduler/src/Task/Task.php@@ -116,9 +116,10 @@ class Task implements LoggerAwareInterface
* @since 4.1.0
*/
protected const EVENTS_MAP = [
- Status::OK => 'onTaskExecuteSuccess',- Status::NO_ROUTINE => 'onTaskRoutineNotFound',- 'NA' => 'onTaskExecuteFailure',+ Status::OK => 'onTaskExecuteSuccess',+ Status::NO_ROUTINE => 'onTaskRoutineNotFound',+ Status::WILL_RESUME => 'onTaskRoutineWillResume',+ 'NA' => 'onTaskExecuteFailure',
];
/**
@@ -246,11 +247,29 @@ public function run(): bool
// @todo make the ExecRuleHelper usage less ugly, perhaps it should be composed into Task
// Update object state.
$this->set('last_execution', Factory::getDate('@' . (int) $this->snapshot['taskStart'])->toSql());
- $this->set('next_execution', (new ExecRuleHelper($this->taskRegistry->toObject()))->nextExec());
$this->set('last_exit_code', $this->snapshot['status']);
- $this->set('times_executed', $this->get('times_executed') + 1);- if ($this->snapshot['status'] !== Status::OK)+ if ($this->snapshot['status'] !== Status::WILL_RESUME)+ {+ $this->set('next_execution', (new ExecRuleHelper($this->taskRegistry->toObject()))->nextExec());+ $this->set('times_executed', $this->get('times_executed') + 1);+ }+ else+ {+ /**+ * Resumable tasks need special handling.+ *+ * They are rescheduled as soon as possible to let their next step to be executed without+ * a very large temporal gap to the previous step.+ *+ * Moreover, the times executed does NOT increase for each step. It will increase once,+ * after the last step, when they return Status::OK.+ */+ $this->set('next_execution', Factory::getDate('now', 'UTC')->sub(new \DateInterval('PT1M'))->toSql());+ }++ // The only acceptable "successful" statuses are either clean exit or resuming execution.+ if (!in_array($this->snapshot['status'], [Status::WILL_RESUME, Status::OK]))
{
$this->set('times_failed', $this->get('times_failed') + 1);
}
@@ -391,7 +410,7 @@ public function releaseLock(bool $update = true): bool
->bind(':times_executed', $timesExecuted)
->bind(':times_failed', $timesFailed);
- if ($exitCode !== Status::OK)+ if (!in_array($exitCode, [Status::OK, Status::WILL_RESUME]))
{
$query->set('times_failed = t.times_failed + 1');
}
@@ -495,7 +514,7 @@ protected function dispatchExitEvent(): void
*/
public function isSuccess(): bool
{
- return ($this->snapshot['status'] ?? null) === Status::OK;+ return in_array(($this->snapshot['status'] ?? null), [Status::OK, Status::WILL_RESUME]);
}
/**
diff --git a/administrator/language/en-GB/com_scheduler.ini b/administrator/language/en-GB/com_scheduler.ini
index 6c31fa2ce9cf..efd4818f5882 100644
--- a/administrator/language/en-GB/com_scheduler.ini+++ b/administrator/language/en-GB/com_scheduler.ini@@ -109,6 +109,7 @@ COM_SCHEDULER_SCHEDULER_TASK_ROUTINE_NA="Task#%1$02d has no corresponding plugin
COM_SCHEDULER_SCHEDULER_TASK_START="Running task#%1$02d '%2$s'."
COM_SCHEDULER_SCHEDULER_TASK_UNKNOWN_EXIT="Task#%1$02d exited with code %4$d in %2$.2f (net %3$.2f) seconds."
COM_SCHEDULER_SCHEDULER_TASK_UNLOCKED="Task#%1$02d was unlocked."
+COM_SCHEDULER_SCHEDULER_TASK_WILL_RESUME="Task#%1$02d needs to perform more work."
COM_SCHEDULER_SELECT_INTERVAL_MINUTES="- Select interval in Minutes -"
COM_SCHEDULER_SELECT_TASK_TYPE="Select task, %s"
COM_SCHEDULER_SELECT_TYPE="- Task Type -"
diff --git a/administrator/language/en-GB/plg_task_demotasks.ini b/administrator/language/en-GB/plg_task_demotasks.ini
index 056a9de80e62..695c0d304895 100644
--- a/administrator/language/en-GB/plg_task_demotasks.ini+++ b/administrator/language/en-GB/plg_task_demotasks.ini@@ -9,6 +9,10 @@ PLG_TASK_DEMO_TASKS_STRESS_MEMORY_DESC="What happens to a task when the PHP memo
PLG_TASK_DEMO_TASKS_STRESS_MEMORY_OVERRIDE_DESC="What happens to a task when the system memory is exhausted?"
PLG_TASK_DEMO_TASKS_STRESS_MEMORY_OVERRIDE_TITLE="Stress Memory, Override Limit"
PLG_TASK_DEMO_TASKS_STRESS_MEMORY_TITLE="Stress Memory"
+PLG_TASK_DEMO_TASKS_RESUMABLE_TITLE="Resumable task"+PLG_TASK_DEMO_TASKS_RESUMABLE_DESC="A simple task to demonstrate resumable task behaviour."+PLG_TASK_DEMO_TASKS_RESUMABLE_STEPS_LABEL="Total number of steps"+PLG_TASK_DEMO_TASKS_RESUMABLE_TIMEOUT_LABEL="Delay per step (seconds)"
PLG_TASK_DEMO_TASKS_TASK_SLEEP_DESC="Sleep, do nothing for x seconds."
PLG_TASK_DEMO_TASKS_TASK_SLEEP_ROUTINE_END_LOG_MESSAGE="TestTask1 return code is: %1$d. Processing Time: %2$.2f seconds"
PLG_TASK_DEMO_TASKS_TASK_SLEEP_TITLE="Demo Task - Sleep"
diff --git a/libraries/src/Console/TasksRunCommand.php b/libraries/src/Console/TasksRunCommand.php
index 37ddfb1fbb54..f13993457db9 100644
--- a/libraries/src/Console/TasksRunCommand.php+++ b/libraries/src/Console/TasksRunCommand.php@@ -58,10 +58,11 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in
* load the namespace when it's time to do that (why?)
*/
static $outTextMap = [
- Status::OK => 'Task#%1$02d \'%2$s\' processed in %3$.2f seconds.',- Status::NO_RUN => '<warning>Task#%1$02d \'%2$s\' failed to run. Is it already running?</warning>',- Status::NO_ROUTINE => '<error>Task#%1$02d \'%2$s\' is orphaned! Visit the backend to resolve.</error>',- 'N/A' => '<error>Task#%1$02d \'%2$s\' exited with code %4$d in %3$.2f seconds.</error>',+ Status::OK => 'Task#%1$02d \'%2$s\' processed in %3$.2f seconds.',+ Status::WILL_RESUME => '<notice>Task#%1$02d \'%2$s\' ran for %3$.2f seconds, will resume next time.</notice>',+ Status::NO_RUN => '<warning>Task#%1$02d \'%2$s\' failed to run. Is it already running?</warning>',+ Status::NO_ROUTINE => '<error>Task#%1$02d \'%2$s\' is orphaned! Visit the backend to resolve.</error>',+ 'N/A' => '<error>Task#%1$02d \'%2$s\' exited with code %4$d in %3$.2f seconds.</error>',
];
$this->configureIo($input, $output);
diff --git a/plugins/task/demotasks/demotasks.php b/plugins/task/demotasks/demotasks.php
index 1cc862bf7231..bea812cf49e5 100644
--- a/plugins/task/demotasks/demotasks.php+++ b/plugins/task/demotasks/demotasks.php@@ -13,6 +13,7 @@
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
use Joomla\Component\Scheduler\Administrator\Task\Status;
+use Joomla\Component\Scheduler\Administrator\Task\Task;
use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait;
use Joomla\Event\SubscriberInterface;
@@ -44,6 +45,11 @@ class PlgTaskDemotasks extends CMSPlugin implements SubscriberInterface
'langConstPrefix' => 'PLG_TASK_DEMO_TASKS_STRESS_MEMORY_OVERRIDE',
'method' => 'stressMemoryRemoveLimit',
],
+ 'demoTask_r4.resumable' => [+ 'langConstPrefix' => 'PLG_TASK_DEMO_TASKS_RESUMABLE',+ 'method' => 'resumable',+ 'form' => 'testTaskForm',+ ],
];
/**
@@ -68,6 +74,60 @@ public static function getSubscribedEvents(): array
];
}
+ /**+ * Sample resumable task.+ *+ * Whether the task will resume is random. There's a 40% chance of finishing every time it runs.+ *+ * You can use this as a template to create long running tasks which can detect an impending+ * timeout condition, return Status::WILL_RESUME and resume execution next time they are called.+ *+ * @param ExecuteTaskEvent $event The event we are handling+ *+ * @return integer+ *+ * @since __DEPLOY_VERSION__+ * @throws \Exception+ */+ private function resumable(ExecuteTaskEvent $event): int+ {+ /** @var Task $task */+ $task = $event->getArgument('subject');+ $timeout = (int) $event->getArgument('params')->timeout ?? 1;++ $lastStatus = $task->get('last_exit_code', Status::OK);++ // This is how you detect if you are resuming a task or starting it afresh+ if ($lastStatus === Status::WILL_RESUME)+ {+ $this->logTask(sprintf('Resuming task %d', $task->get('id')));+ }+ else+ {+ $this->logTask(sprintf('Starting new task %d', $task->get('id')));+ }++ // Sample task body; we are simply sleeping for some time.+ $this->logTask(sprintf('Starting %ds timeout', $timeout));+ sleep($timeout);+ $this->logTask(sprintf('%ds timeout over!', $timeout));++ // Should I resume the task in the next step (randomly decided)?+ $willResume = random_int(0, 5) < 4;++ // Log our intention to resume or not and return the appropriate exit code.+ if ($willResume)+ {+ $this->logTask(sprintf('Task %d will resume', $task->get('id')));+ }+ else+ {+ $this->logTask(sprintf('Task %d is now complete', $task->get('id')));+ }++ return $willResume ? Status::WILL_RESUME : Status::OK;+ }+
/**
* @param ExecuteTaskEvent $event The `onExecuteTask` event.
*
The text was updated successfully, but these errors were encountered:
PR w związku ze zmianą oryginału joomla/joomla-cms#36708 Poniżej zmiany w oryginale:
Click to expand the diff!
The text was updated successfully, but these errors were encountered: