MDL-79128 adhoc_task: Implement failed ad-hoc task cleanup

Co-authored-by: Stevani Andolo <stevani.andolo@moodle.com>
This commit is contained in:
Huong Nguyen 2023-11-28 12:09:14 +07:00
parent 6c047c40b7
commit f13392d230
5 changed files with 140 additions and 0 deletions

View file

@ -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.

View file

@ -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';

View file

@ -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
*/
@ -1717,4 +1723,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],
);
}
}

View file

@ -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();
}
}

View file

@ -245,6 +245,110 @@ class adhoc_task_test extends \advanced_testcase {
$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