Merge branch 'MDL-79128-main' of https://github.com/HuongNV13/moodle
This commit is contained in:
@@ -390,6 +390,17 @@ if ($hassiteconfig) {
|
||||
30 * MINSECS
|
||||
)
|
||||
);
|
||||
|
||||
$temp->add(
|
||||
new admin_setting_configduration(
|
||||
'task_adhoc_failed_retention',
|
||||
new lang_string('task_adhoc_failed_retention', 'admin'),
|
||||
new lang_string('task_adhoc_failed_retention_desc', 'admin'),
|
||||
\core\task\manager::ADHOC_TASK_FAILED_RETENTION,
|
||||
WEEKSECS
|
||||
)
|
||||
);
|
||||
|
||||
$ADMIN->add('taskconfig', $temp);
|
||||
|
||||
// Task log configuration.
|
||||
|
||||
@@ -119,7 +119,9 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||
|
||||
// Prepares the next run time cell contents.
|
||||
$nextrun = '';
|
||||
if ($stats['due'] > 0) {
|
||||
if ($stats['stop']) {
|
||||
$nextrun = get_string('never', 'admin');
|
||||
} else if ($stats['due'] > 0) {
|
||||
$nextrun = get_string('asap', 'tool_task');
|
||||
} else if ($stats['nextruntime']) {
|
||||
$nextrun = userdate($stats['nextruntime']);
|
||||
@@ -271,7 +273,11 @@ class tool_task_renderer extends plugin_renderer_base {
|
||||
if (!$started) {
|
||||
$nextruntime = $task->get_next_run_time();
|
||||
$due = $nextruntime < $now;
|
||||
$nextrun = $due ? userdate($nextruntime) : get_string('asap', 'tool_task');
|
||||
if ($task->get_attempts_available() > 0) {
|
||||
$nextrun = $due ? userdate($nextruntime) : get_string('asap', 'tool_task');
|
||||
} else {
|
||||
$nextrun = get_string('never', 'admin');
|
||||
}
|
||||
|
||||
if ($wantruntasks && ($faildelay || $due)) {
|
||||
$nextrun .= ' '.html_writer::div(
|
||||
|
||||
@@ -1362,6 +1362,8 @@ $string['task_scheduled_max_runtime'] = 'Scheduled task runner lifetime';
|
||||
$string['task_scheduled_max_runtime_desc'] = 'The age of a scheduled task runner before it is freed.';
|
||||
$string['task_adhoc_concurrency_limit'] = 'Ad hoc task concurrency limit';
|
||||
$string['task_adhoc_concurrency_limit_desc'] = 'The number of ad hoc task runners allowed to run concurrently. If the limit is high then scheduled tasks may not run regularly when there are lots of ad hoc tasks. A setting of 0 will disable processing of ad hoc tasks completely.';
|
||||
$string['task_adhoc_failed_retention'] = 'Failed ad hoc task retention period';
|
||||
$string['task_adhoc_failed_retention_desc'] = 'The maximum period that failed ad hoc tasks should remain in the queue once they have reached their retry limit.';
|
||||
$string['task_adhoc_max_runtime'] = 'Ad hoc task runner lifetime';
|
||||
$string['task_adhoc_max_runtime_desc'] = 'The age of an ad hoc task runner before it is freed. A low duration is recommended as there is no limit to the number of ad hoc tasks queued. If this number is too high and you have a large ad hoc task queue then scheduled tasks may not be run regularly.';
|
||||
$string['task_endtime'] = 'End time';
|
||||
|
||||
@@ -363,6 +363,9 @@ class cron {
|
||||
$where .= ' AND classname = :classname';
|
||||
$params['classname'] = \core\task\manager::get_canonical_class_name($classname);
|
||||
}
|
||||
|
||||
// Only rerun the failed tasks that allow to be re-tried or have the remaining attempts available.
|
||||
$where .= 'AND (attemptsavailable > 0 OR attemptsavailable IS NULL)';
|
||||
$tasks = $DB->get_records_sql("SELECT * from {task_adhoc} WHERE $where", $params);
|
||||
foreach ($tasks as $t) {
|
||||
self::run_adhoc_task($t->id);
|
||||
|
||||
@@ -46,6 +46,9 @@ abstract class adhoc_task extends task_base {
|
||||
/** @var \core\lock\lock The concurrency task lock for this task. */
|
||||
private $concurrencylock = null;
|
||||
|
||||
/** @var integer|null $attemptsavailable - The remaining attempts of the task, or null for unlimited. */
|
||||
private $attemptsavailable = null;
|
||||
|
||||
/**
|
||||
* Provide default implementation of the task name for backward compatibility. Extending classes are expected to implement
|
||||
* this method to provide a descriptive name for the task (shown to admins)
|
||||
@@ -171,4 +174,32 @@ abstract class adhoc_task extends task_base {
|
||||
$this->concurrencylock->release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the remaining attempts of the task.
|
||||
*
|
||||
* @param int|null $attemptsavailable Number of the remaining attempts of the task, or null for unlimited.
|
||||
*/
|
||||
public function set_attempts_available(?int $attemptsavailable): void {
|
||||
$this->attemptsavailable = $attemptsavailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining attempts of the task.
|
||||
*
|
||||
* @return int|null Number of the remaining attempts of the task, or null for unlimited.
|
||||
*/
|
||||
public function get_attempts_available(): ?int {
|
||||
return $this->attemptsavailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to indicate if the task should be re-run if it fails.
|
||||
* By default, tasks will be retried until they succeed, other tasks can override this method to change this behaviour.
|
||||
*
|
||||
* @return bool true if the task should be retried until it succeeds, false otherwise.
|
||||
*/
|
||||
public function retry_until_success(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,12 @@ class manager {
|
||||
*/
|
||||
const ADHOC_TASK_QUEUE_MODE_FILLING = 1;
|
||||
|
||||
/**
|
||||
* @var int Used to set the retention period for adhoc tasks that have failed and to be cleaned up.
|
||||
* The number is in week unit. The default value is 4 weeks.
|
||||
*/
|
||||
const ADHOC_TASK_FAILED_RETENTION = 4 * WEEKSECS;
|
||||
|
||||
/**
|
||||
* @var array A cached queue of adhoc tasks
|
||||
*/
|
||||
@@ -229,6 +235,9 @@ class manager {
|
||||
$record->nextruntime = time() - 1;
|
||||
}
|
||||
|
||||
// Check if the task is allowed to be retried or not.
|
||||
$record->attemptsavailable = $task->retry_until_success() ? null : 1;
|
||||
|
||||
// Check if the same task is already scheduled.
|
||||
if ($checkforexisting && self::task_is_scheduled($task)) {
|
||||
return false;
|
||||
@@ -311,6 +320,7 @@ class manager {
|
||||
$record->timestarted = $task->get_timestarted();
|
||||
$record->hostname = $task->get_hostname();
|
||||
$record->pid = $task->get_pid();
|
||||
$record->attemptsavailable = $task->get_attempts_available();
|
||||
|
||||
return $record;
|
||||
}
|
||||
@@ -357,6 +367,9 @@ class manager {
|
||||
if (isset($record->pid)) {
|
||||
$task->set_pid($record->pid);
|
||||
}
|
||||
if (isset($record->attemptsavailable)) {
|
||||
$task->set_attempts_available($record->attemptsavailable);
|
||||
}
|
||||
|
||||
return $task;
|
||||
}
|
||||
@@ -530,6 +543,7 @@ class manager {
|
||||
'failed' => 0,
|
||||
'running' => 0,
|
||||
'due' => 0,
|
||||
'stop' => false,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -551,6 +565,11 @@ class manager {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the task as stopped if it has no attempts available.
|
||||
if (isset($r->attemptsavailable) && $r->attemptsavailable == 0) {
|
||||
$classsummary['stop'] = true;
|
||||
}
|
||||
|
||||
$summary[$r->component][$r->classname] = $classsummary;
|
||||
}
|
||||
return $summary;
|
||||
@@ -778,6 +797,7 @@ class manager {
|
||||
'SELECT classname, COALESCE(COUNT(*), 0) running, MIN(timestarted) earliest
|
||||
FROM {task_adhoc}
|
||||
WHERE timestarted IS NOT NULL
|
||||
AND (attemptsavailable > 0 OR attemptsavailable IS NULL)
|
||||
AND nextruntime < :timestart
|
||||
GROUP BY classname
|
||||
ORDER BY running ASC, earliest DESC',
|
||||
@@ -932,10 +952,12 @@ class manager {
|
||||
SELECT classname, COUNT(*) running, MIN(timestarted) earliest
|
||||
FROM {task_adhoc} run
|
||||
WHERE timestarted IS NOT NULL
|
||||
AND (attemptsavailable > 0 OR attemptsavailable IS NULL)
|
||||
GROUP BY classname
|
||||
) run ON run.classname = q.classname
|
||||
WHERE nextruntime < :timestart
|
||||
AND q.timestarted IS NULL " .
|
||||
AND q.timestarted IS NULL
|
||||
AND (q.attemptsavailable > 0 OR q.attemptsavailable IS NULL) " .
|
||||
(!empty($pertasksql) ? "AND (" . $pertasksql . ") " : "") .
|
||||
($runmax ? "AND (COALESCE(run.running, 0)) < :runmax " : "") .
|
||||
"ORDER BY COALESCE(run.running, 0) ASC, run.earliest DESC, q.nextruntime ASC, q.id ASC",
|
||||
@@ -1106,6 +1128,9 @@ class manager {
|
||||
$task->set_pid();
|
||||
$task->set_next_run_time(time() + $delay);
|
||||
$task->set_fail_delay($delay);
|
||||
if (!$task->retry_until_success() && $task->get_attempts_available() > 0) {
|
||||
$task->set_attempts_available($task->get_attempts_available() - 1);
|
||||
}
|
||||
$record = self::record_from_adhoc_task($task);
|
||||
$DB->update_record('task_adhoc', $record);
|
||||
|
||||
@@ -1704,4 +1729,18 @@ class manager {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up failed adhoc tasks.
|
||||
*/
|
||||
public static function clean_failed_adhoc_tasks(): void {
|
||||
global $CFG, $DB;
|
||||
$difftime = !empty($CFG->task_adhoc_failed_retention) ?
|
||||
$CFG->task_adhoc_failed_retention : static::ADHOC_TASK_FAILED_RETENTION;
|
||||
$DB->delete_records_select(
|
||||
table: 'task_adhoc',
|
||||
select: 'attemptsavailable = 0 AND timestarted < :time',
|
||||
params: ['time' => time() - $difftime],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,8 @@ class task_log_cleanup_task extends scheduled_task {
|
||||
if (is_a($logger, database_logger::class, true)) {
|
||||
$logger::cleanup();
|
||||
}
|
||||
|
||||
// Clean failed ad-hoc tasks.
|
||||
manager::clean_failed_adhoc_tasks();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20231004" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20231127" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -3505,6 +3505,7 @@
|
||||
<FIELD NAME="timestarted" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Time when the task was started"/>
|
||||
<FIELD NAME="hostname" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Hostname where the task is running"/>
|
||||
<FIELD NAME="pid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="PHP process ID that is running the task"/>
|
||||
<FIELD NAME="attemptsavailable" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="The remaining attempts for this task"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
|
||||
@@ -864,5 +864,38 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2023120100.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2023121800.02) {
|
||||
// Define field attemptsavailable to be added to task_adhoc.
|
||||
$table = new xmldb_table('task_adhoc');
|
||||
$field = new xmldb_field(
|
||||
name: 'attemptsavailable',
|
||||
type: XMLDB_TYPE_INTEGER,
|
||||
precision: '1',
|
||||
unsigned: null,
|
||||
notnull: null,
|
||||
sequence: null,
|
||||
default: null,
|
||||
previous: 'pid',
|
||||
);
|
||||
|
||||
// Conditionally launch add field attemptsavailable.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Set attemptsavailable to 0 for the tasks that have not been run before.
|
||||
// Set attemptsavailable to 1 for the tasks that have been run and failed before.
|
||||
$DB->execute('
|
||||
UPDATE {task_adhoc}
|
||||
SET attemptsavailable = CASE
|
||||
WHEN faildelay = 0 THEN 1
|
||||
WHEN faildelay > 0 THEN 0
|
||||
END
|
||||
');
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2023121800.02);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Vendored
+19
@@ -107,6 +107,25 @@ class adhoc_test4_task extends adhoc_test_task {
|
||||
class adhoc_test5_task extends adhoc_test_task {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for no-retry adhoc task.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Huong Nguyen <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class no_retry_adhoc_task extends adhoc_test_task {
|
||||
|
||||
/**
|
||||
* Prevent the task from retrying.
|
||||
* @return bool
|
||||
*/
|
||||
public function retry_until_success(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class scheduled_test_task extends \core\task\scheduled_task {
|
||||
public function get_name() {
|
||||
return "Test task";
|
||||
|
||||
@@ -142,6 +142,213 @@ class adhoc_task_test extends \advanced_testcase {
|
||||
$this->assertNull(manager::get_next_adhoc_task($now));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adhoc task failure retry backoff.
|
||||
*
|
||||
* @covers ::queue_adhoc_task
|
||||
* @covers ::get_next_adhoc_task
|
||||
* @covers ::adhoc_task_failed
|
||||
*/
|
||||
public function test_adhoc_task_with_retry_flag(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$now = time();
|
||||
// Create a normal adhoc task.
|
||||
$task = new adhoc_test_task();
|
||||
$taskid1 = manager::queue_adhoc_task(task: $task);
|
||||
|
||||
// This is a normal task, so it should have unlimited attempts. The remaining available attempts should be null.
|
||||
$attemptsavailable = $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'attemptsavailable',
|
||||
conditions: ['id' => $taskid1]
|
||||
);
|
||||
$this->assertNull(actual: $attemptsavailable);
|
||||
|
||||
// Get the task from the scheduler, execute it, and mark it as failed.
|
||||
$task = manager::get_next_adhoc_task(timestart: $now);
|
||||
$taskid1 = $task->get_id();
|
||||
$task->execute();
|
||||
manager::adhoc_task_failed(task: $task);
|
||||
|
||||
// This is a normal task, so it should have unlimited attempts. The remaining available attempts should be null.
|
||||
$attemptsavailable = $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'attemptsavailable',
|
||||
conditions: ['id' => $taskid1]
|
||||
);
|
||||
$this->assertNull(actual: $attemptsavailable);
|
||||
|
||||
// Create a no-retry adhoc task.
|
||||
$now = time();
|
||||
$task = new no_retry_adhoc_task();
|
||||
$taskid2 = manager::queue_adhoc_task(task: $task);
|
||||
|
||||
// This is no-retry task, so it should have only 1 attempt available.
|
||||
$attemptsavailable = $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'attemptsavailable',
|
||||
conditions: ['id' => $taskid2]
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: 1,
|
||||
actual: $attemptsavailable,
|
||||
);
|
||||
|
||||
// Get the task from the scheduler, execute it, and mark it as failed.
|
||||
$task = manager::get_next_adhoc_task(timestart: $now);
|
||||
$taskid2 = $task->get_id();
|
||||
$task->execute();
|
||||
manager::adhoc_task_failed(task: $task);
|
||||
|
||||
// This is no-retry task, the remaining available attempts should be reduced to 0.
|
||||
$attemptsavailable = $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'attemptsavailable',
|
||||
conditions: ['id' => $taskid2]
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: 0,
|
||||
actual: $attemptsavailable,
|
||||
);
|
||||
|
||||
// There will be two records in the task_adhoc table, one for each task.
|
||||
$this->assertEquals(
|
||||
expected: 2,
|
||||
actual: $DB->count_records(table: 'task_adhoc')
|
||||
);
|
||||
// But get_next_adhoc_task() should return only the allowed re-try task.
|
||||
// The no-retry task should not be returned because it has no remaining attempts.
|
||||
do {
|
||||
$task = manager::get_next_adhoc_task(timestart: $now + 86400);
|
||||
if ($task) {
|
||||
manager::adhoc_task_failed(task: $task);
|
||||
$this->assertEquals(
|
||||
expected: $taskid1,
|
||||
actual: $task->get_id(),
|
||||
);
|
||||
}
|
||||
} while ($task);
|
||||
|
||||
// Mark the normal task as complete.
|
||||
$task = manager::get_adhoc_task(taskid: $taskid1);
|
||||
manager::adhoc_task_complete($task);
|
||||
|
||||
// There will be one record in the task_adhoc table.
|
||||
$this->assertEquals(
|
||||
expected: 1,
|
||||
actual: $DB->count_records(table: 'task_adhoc')
|
||||
);
|
||||
|
||||
// But get_next_adhoc_task() should return nothing.
|
||||
$this->assertNull(manager::get_next_adhoc_task(timestart: $now + 86400));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test adhoc task failure cleanup.
|
||||
*
|
||||
* @covers ::queue_adhoc_task
|
||||
* @covers ::get_next_adhoc_task
|
||||
* @covers ::adhoc_task_failed
|
||||
* @covers ::clean_failed_adhoc_tasks
|
||||
*/
|
||||
public function test_adhoc_task_clean_up(): void {
|
||||
global $DB, $CFG;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create two no-retry adhoc tasks.
|
||||
$task1 = new no_retry_adhoc_task();
|
||||
$taskid1 = manager::queue_adhoc_task(task: $task1);
|
||||
$task2 = new no_retry_adhoc_task();
|
||||
$taskid2 = manager::queue_adhoc_task(task: $task2);
|
||||
|
||||
// Get the tasks and mark it as failed.
|
||||
$task = manager::get_adhoc_task($taskid1);
|
||||
manager::adhoc_task_failed(task: $task);
|
||||
$task = manager::get_adhoc_task($taskid2);
|
||||
manager::adhoc_task_failed(task: $task);
|
||||
|
||||
// These are no-retry tasks, the remaining available attempts should be reduced to 0.
|
||||
$this->assertEquals(
|
||||
expected: 0,
|
||||
actual: $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'attemptsavailable',
|
||||
conditions: ['id' => $taskid1],
|
||||
),
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: 0,
|
||||
actual: $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'attemptsavailable',
|
||||
conditions: ['id' => $taskid2],
|
||||
),
|
||||
);
|
||||
|
||||
// There will be two records in the task_adhoc table.
|
||||
$this->assertEquals(
|
||||
expected: 2,
|
||||
actual: $DB->count_records(table: 'task_adhoc'),
|
||||
);
|
||||
|
||||
// Clean up failed adhoc tasks. This will clean nothing because the tasks are not old enough.
|
||||
manager::clean_failed_adhoc_tasks();
|
||||
|
||||
// There will be two records in the task_adhoc table.
|
||||
$this->assertEquals(
|
||||
expected: 2,
|
||||
actual: $DB->count_records(table: 'task_adhoc'),
|
||||
);
|
||||
|
||||
// Update the time of the task2 to be older more than 2 days.
|
||||
$DB->set_field(
|
||||
table: 'task_adhoc',
|
||||
newfield: 'timestarted',
|
||||
newvalue: time() - (DAYSECS * 2) - 10, // Plus 10 seconds to make sure it is older than 2 days.
|
||||
conditions: ['id' => $taskid2],
|
||||
);
|
||||
|
||||
// Clean up failed adhoc tasks. This will clean nothing because the tasks are not old enough.
|
||||
manager::clean_failed_adhoc_tasks();
|
||||
|
||||
// There will be two records in the task_adhoc table.
|
||||
$this->assertEquals(
|
||||
expected: 2,
|
||||
actual: $DB->count_records(table: 'task_adhoc'),
|
||||
);
|
||||
|
||||
// Update the time of the task1 to be older than the cleanup time.
|
||||
$DB->set_field(
|
||||
table: 'task_adhoc',
|
||||
newfield: 'timestarted',
|
||||
newvalue: time() - $CFG->task_adhoc_failed_retention - 10, // Plus 10 seconds to make sure it is older than the retention time.
|
||||
conditions: ['id' => $taskid1],
|
||||
);
|
||||
|
||||
// Clean up failed adhoc tasks. task1 should be cleaned now.
|
||||
manager::clean_failed_adhoc_tasks();
|
||||
|
||||
// There will be one record in the task_adhoc table.
|
||||
$this->assertEquals(
|
||||
expected: 1,
|
||||
actual: $DB->count_records(table: 'task_adhoc'),
|
||||
);
|
||||
|
||||
// Update the duration of the Failed ad hoc task retention period to one day.
|
||||
$CFG->task_adhoc_failed_retention = DAYSECS;
|
||||
|
||||
// Clean up failed adhoc tasks. task2 should be cleaned now.
|
||||
manager::clean_failed_adhoc_tasks();
|
||||
|
||||
// The task_adhoc table should be empty now.
|
||||
$this->assertEquals(
|
||||
expected: 0,
|
||||
actual: $DB->count_records(table: 'task_adhoc'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test future adhoc task execution.
|
||||
* @covers ::get_next_adhoc_task
|
||||
|
||||
@@ -20,6 +20,11 @@ information provided here is intended especially for developers.
|
||||
* The options for `format_string()`, and `format_text()` are now checked for incorrectly passed context objects.
|
||||
Please note that this was never an accepted value but previously failed silently.
|
||||
* The current page language is available in new `core/config` language property for Javascript modules
|
||||
* The following functions have been added to adhoc_task class:
|
||||
- retry_until_success(): Used to indicate if the task should be re-run if it fails
|
||||
By default, tasks will be retried until they succeed, other tasks can override this method to change this behaviour.
|
||||
- set_attempts_available(): Used to set the number of attempts available for the task
|
||||
- get_attempts_available(): Used to get the number of attempts available for the task.
|
||||
|
||||
=== 4.3 ===
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2023121800.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2023121800.02; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.4dev (Build: 20231215)'; // Human-friendly version name
|
||||
|
||||
Reference in New Issue
Block a user