MDL-70975 task: Support for running adhoc tasks by id
* CLI adhoc_task.php: new option --id * cron::run_adhoc_task($taskid) for running tasks by id * core\task\manager::get_adhoc_task($taskid) for retreival/locking
This commit is contained in:
+21
-13
@@ -30,13 +30,14 @@ require_once("{$CFG->libdir}/clilib.php");
|
||||
|
||||
list($options, $unrecognized) = cli_get_params(
|
||||
[
|
||||
'execute' => false,
|
||||
'help' => false,
|
||||
'keep-alive' => 0,
|
||||
'showsql' => false,
|
||||
'showdebugging' => false,
|
||||
'execute' => false,
|
||||
'keep-alive' => 0,
|
||||
'ignorelimits' => false,
|
||||
'force' => false,
|
||||
'id' => null,
|
||||
], [
|
||||
'h' => 'help',
|
||||
'e' => 'execute',
|
||||
@@ -51,6 +52,9 @@ if ($unrecognized) {
|
||||
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
|
||||
}
|
||||
|
||||
if ($options['id']) {
|
||||
$options['execute'] = true;
|
||||
}
|
||||
if ($options['help'] or empty($options['execute'])) {
|
||||
$help = <<<EOT
|
||||
Ad hoc cron tasks.
|
||||
@@ -63,9 +67,11 @@ Options:
|
||||
-k, --keep-alive=N Keep this script alive for N seconds and poll for new adhoc tasks
|
||||
-i --ignorelimits Ignore task_adhoc_concurrency_limit and task_adhoc_max_runtime limits
|
||||
-f, --force Run even if cron is disabled
|
||||
--id Run (failed) task with id
|
||||
|
||||
Example:
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/adhoc_task.php --execute
|
||||
\$sudo -u www-data /usr/bin/php admin/cli/adhoc_task.php --id=123456
|
||||
|
||||
EOT;
|
||||
|
||||
@@ -91,10 +97,6 @@ if (moodle_needs_upgrading()) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (empty($options['execute'])) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!get_config('core', 'cron_enabled') && !$options['force']) {
|
||||
mtrace('Cron is disabled. Use --force to override.');
|
||||
exit(1);
|
||||
@@ -111,8 +113,6 @@ if (!empty($CFG->showcrondebugging)) {
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
}
|
||||
|
||||
$checklimits = empty($options['ignorelimits']);
|
||||
|
||||
core_php_time_limit::raise();
|
||||
|
||||
// Increase memory limit.
|
||||
@@ -121,10 +121,18 @@ raise_memory_limit(MEMORY_EXTRA);
|
||||
// Emulate normal session - we use admin account by default.
|
||||
\core\cron::setup_user();
|
||||
|
||||
$humantimenow = date('r', time());
|
||||
$keepalive = (int)$options['keep-alive'];
|
||||
|
||||
\core\local\cli\shutdown::script_supports_graceful_exit();
|
||||
|
||||
$humantimenow = date('r', time());
|
||||
mtrace("Server Time: {$humantimenow}\n");
|
||||
\core\cron::run_adhoc_tasks(time(), $keepalive, $checklimits);
|
||||
|
||||
if (!empty($options['id'])) {
|
||||
$taskid = (int) $options['id'];
|
||||
\core\cron::run_adhoc_task($taskid);
|
||||
} elseif (!empty($options['execute'])) {
|
||||
|
||||
$checklimits = empty($options['ignorelimits']);
|
||||
|
||||
$keepalive = (int)$options['keep-alive'];
|
||||
|
||||
\core\cron::run_adhoc_tasks(time(), $keepalive, $checklimits);
|
||||
}
|
||||
|
||||
@@ -1163,6 +1163,8 @@ $string['interestslist'] = 'List of interests';
|
||||
$string['interestslist_help'] = 'Enter your interests, one by one, which will then be displayed on your profile page as tags.';
|
||||
$string['invalidemail'] = 'Invalid email address';
|
||||
$string['invalidlogin'] = 'Invalid login, please try again';
|
||||
$string['invalidtaskid'] = 'Invalid task ID';
|
||||
$string['invalidtaskclassname'] = 'Invalid task class {$a}';
|
||||
$string['invalidusername'] = 'The username can only contain alphanumeric lowercase characters (letters and numbers), underscore (_), hyphen (-), period (.) or at symbol (@).';
|
||||
$string['invalidusernameupload'] = 'Invalid username';
|
||||
$string['ip_address'] = 'IP address';
|
||||
@@ -2354,6 +2356,7 @@ $string['withdisablednote'] = '{$a} (disabled)';
|
||||
$string['withoutuserdata'] = 'without user data';
|
||||
$string['withselectedusers'] = 'With selected users...';
|
||||
$string['withuserdata'] = 'with user data';
|
||||
$string['wontrunfuturescheduledtask'] = "Won't run task that hasn't failed and is scheduled to run in the future";
|
||||
$string['wordforstudent'] = 'Your word for Student';
|
||||
$string['wordforstudenteg'] = 'eg Student, Participant etc';
|
||||
$string['wordforstudents'] = 'Your word for Students';
|
||||
|
||||
@@ -327,6 +327,21 @@ class cron {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a (failed) adhoc task.
|
||||
*
|
||||
* @param int $taskid
|
||||
*/
|
||||
public static function run_adhoc_task(int $taskid): void {
|
||||
$task = \core\task\manager::get_adhoc_task($taskid);
|
||||
if (!$task->get_fail_delay() && $task->get_next_run_time() > time()) {
|
||||
throw new \moodle_exception('wontrunfuturescheduledtask');
|
||||
}
|
||||
|
||||
self::run_inner_adhoc_task($task);
|
||||
self::set_process_title("Running adhoc task $taskid");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared code that handles running of a single scheduled task within the cron.
|
||||
*
|
||||
|
||||
@@ -474,7 +474,7 @@ class manager {
|
||||
* This function load the adhoc tasks for a given classname.
|
||||
*
|
||||
* @param string $classname
|
||||
* @return \core\task\adhoc_task[]
|
||||
* @return array
|
||||
*/
|
||||
public static function get_adhoc_tasks($classname) {
|
||||
global $DB;
|
||||
@@ -645,10 +645,10 @@ class manager {
|
||||
*
|
||||
* @param int $timestart
|
||||
* @param bool $checklimits Should we check limits?
|
||||
* @return \core\task\adhoc_task or null if not found
|
||||
* @return \core\task\adhoc_task|null
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public static function get_next_adhoc_task($timestart, $checklimits = true) {
|
||||
public static function get_next_adhoc_task(int $timestart, bool $checklimits = true): ?adhoc_task {
|
||||
global $DB;
|
||||
|
||||
$concurrencylimit = get_config('core', 'task_adhoc_concurrency_limit');
|
||||
@@ -797,21 +797,9 @@ class manager {
|
||||
}
|
||||
}
|
||||
|
||||
// The global cron lock is under the most contention so request it
|
||||
// as late as possible and release it as soon as possible.
|
||||
if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) {
|
||||
$lock->release();
|
||||
throw new \moodle_exception('locktimeout');
|
||||
}
|
||||
|
||||
$task->set_lock($lock);
|
||||
if (!$task->is_blocking()) {
|
||||
$cronlock->release();
|
||||
} else {
|
||||
$task->set_cron_lock($cronlock);
|
||||
}
|
||||
|
||||
self::set_locks($task, $lock, $cronlockfactory);
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
|
||||
return $task;
|
||||
} else {
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
@@ -880,6 +868,64 @@ class manager {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will get a (failed) adhoc task by id. The task will be handed out
|
||||
* with an open lock - possibly on the entire cron process. Make sure you call either
|
||||
* {@see ::adhoc_task_failed} or {@see ::adhoc_task_complete} to release the lock and reschedule the task.
|
||||
*
|
||||
* @param int $taskid
|
||||
* @return \core\task\adhoc_task|null
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public static function get_adhoc_task(int $taskid): ?adhoc_task {
|
||||
global $DB;
|
||||
|
||||
$record = $DB->get_record('task_adhoc', array('id' => $taskid));
|
||||
if (!$record) {
|
||||
throw new \moodle_exception('invalidtaskid');
|
||||
}
|
||||
|
||||
$cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
|
||||
|
||||
if ($lock = $cronlockfactory->get_lock('adhoc_' . $record->id, 0)) {
|
||||
$task = self::adhoc_task_from_record($record);
|
||||
// Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
|
||||
if (!$task) {
|
||||
$lock->release();
|
||||
throw new \moodle_exception('invalidtaskclassname');
|
||||
}
|
||||
|
||||
self::set_locks($task, $lock, $cronlockfactory);
|
||||
return $task;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will set locks on the task.
|
||||
*
|
||||
* @param \core\task\adhoc_task $task
|
||||
* @param \core\lock\lock $lock task lock
|
||||
* @param \core\lock\lock_factory $cronlockfactory
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
private static function set_locks($task, $lock, $cronlockfactory): void {
|
||||
// The global cron lock is under the most contention so request it
|
||||
// as late as possible and release it as soon as possible.
|
||||
if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) {
|
||||
$lock->release();
|
||||
throw new \moodle_exception('locktimeout');
|
||||
}
|
||||
|
||||
$task->set_lock($lock);
|
||||
if (!$task->is_blocking()) {
|
||||
$cronlock->release();
|
||||
} else {
|
||||
$task->set_cron_lock($cronlock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will dispatch the next scheduled task in the queue. The task will be handed out
|
||||
* with an open lock - possibly on the entire cron process. Make sure you call either
|
||||
|
||||
@@ -75,6 +75,7 @@ class adhoc_task_test extends \advanced_testcase {
|
||||
* Test adhoc task failure retry backoff.
|
||||
*
|
||||
* @covers ::get_next_adhoc_task
|
||||
* @covers ::get_adhoc_task
|
||||
*/
|
||||
public function test_get_next_adhoc_task_fail_retry() {
|
||||
$this->resetAfterTest(true);
|
||||
@@ -87,17 +88,25 @@ class adhoc_task_test extends \advanced_testcase {
|
||||
|
||||
// Get it from the scheduler, execute it, and mark it as failed.
|
||||
$task = manager::get_next_adhoc_task($now);
|
||||
$taskid = $task->get_id();
|
||||
$task->execute();
|
||||
manager::adhoc_task_failed($task);
|
||||
|
||||
// The task will not be returned immediately.
|
||||
$this->assertNull(manager::get_next_adhoc_task($now));
|
||||
|
||||
// Should get the adhoc task (retry after delay).
|
||||
// Should get the adhoc task (retry after delay). Fail it again.
|
||||
$task = manager::get_next_adhoc_task($now + 120);
|
||||
$this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
|
||||
$this->assertEquals($taskid, $task->get_id());
|
||||
$task->execute();
|
||||
manager::adhoc_task_failed($task);
|
||||
|
||||
// Should get the adhoc task immediately.
|
||||
$task = manager::get_adhoc_task($taskid);
|
||||
$this->assertInstanceOf('\\core\\task\\adhoc_test_task', $task);
|
||||
$this->assertEquals($taskid, $task->get_id());
|
||||
$task->execute();
|
||||
manager::adhoc_task_complete($task);
|
||||
|
||||
// Should not get any task.
|
||||
|
||||
Reference in New Issue
Block a user