mirror of
https://github.com/moodle/moodle.git
synced 2025-04-15 05:25:08 +02:00
Merge branch 'MDL-67648-master' of https://github.com/cameron1729/moodle
This commit is contained in:
commit
0460147cb5
@ -36,6 +36,31 @@ define('CORE_TASK_TASKS_FILENAME', 'db/tasks.php');
|
||||
*/
|
||||
class manager {
|
||||
|
||||
/**
|
||||
* @var int Used to tell the adhoc task queue to fairly distribute tasks.
|
||||
*/
|
||||
const ADHOC_TASK_QUEUE_MODE_DISTRIBUTING = 0;
|
||||
|
||||
/**
|
||||
* @var int Used to tell the adhoc task queue to try and fill unused capacity.
|
||||
*/
|
||||
const ADHOC_TASK_QUEUE_MODE_FILLING = 1;
|
||||
|
||||
/**
|
||||
* @var array A cached queue of adhoc tasks
|
||||
*/
|
||||
public static $miniqueue;
|
||||
|
||||
/**
|
||||
* @var int The last recorded number of unique adhoc tasks.
|
||||
*/
|
||||
public static $numtasks;
|
||||
|
||||
/**
|
||||
* @var string Used to determine if the adhoc task queue is distributing or filling capacity.
|
||||
*/
|
||||
public static $mode;
|
||||
|
||||
/**
|
||||
* Given a component name, will load the list of tasks in the db/tasks.php file for that component.
|
||||
*
|
||||
@ -542,8 +567,13 @@ class manager {
|
||||
*
|
||||
* @param array $records array of task records
|
||||
* @param array $records array of same task records shuffled
|
||||
* @deprecated since Moodle 4.1 MDL-67648 - please do not use this method anymore.
|
||||
* @todo MDL-74843 This method will be deleted in Moodle 4.5
|
||||
* @see \core\task\manager::get_next_adhoc_task
|
||||
*/
|
||||
public static function ensure_adhoc_task_qos(array $records): array {
|
||||
debugging('The method \core\task\manager::ensure_adhoc_task_qos is deprecated.
|
||||
Please use \core\task\manager::get_next_adhoc_task instead.', DEBUG_DEVELOPER);
|
||||
|
||||
$count = count($records);
|
||||
if ($count == 0) {
|
||||
@ -621,16 +651,114 @@ class manager {
|
||||
public static function get_next_adhoc_task($timestart, $checklimits = true) {
|
||||
global $DB;
|
||||
|
||||
$where = '(nextruntime IS NULL OR nextruntime < :timestart1)';
|
||||
$params = array('timestart1' => $timestart);
|
||||
$records = $DB->get_records_select('task_adhoc', $where, $params, 'nextruntime ASC, id ASC', '*', 0, 2000);
|
||||
$records = self::ensure_adhoc_task_qos($records);
|
||||
$concurrencylimit = get_config('core', 'task_adhoc_concurrency_limit');
|
||||
$cachedqueuesize = 1200;
|
||||
|
||||
$uniquetasksinqueue = array_map(
|
||||
['\core\task\manager', 'adhoc_task_from_record'],
|
||||
$DB->get_records_sql(
|
||||
'SELECT classname FROM {task_adhoc} WHERE nextruntime < :timestart GROUP BY classname',
|
||||
['timestart' => $timestart]
|
||||
)
|
||||
);
|
||||
|
||||
if (!isset(self::$numtasks) || self::$numtasks !== count($uniquetasksinqueue)) {
|
||||
self::$numtasks = count($uniquetasksinqueue);
|
||||
self::$miniqueue = [];
|
||||
}
|
||||
|
||||
$concurrencylimits = [];
|
||||
if ($checklimits) {
|
||||
$concurrencylimits = array_map(
|
||||
function ($task) {
|
||||
return $task->get_concurrency_limit();
|
||||
},
|
||||
$uniquetasksinqueue
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* The maximum number of cron runners that an individual task is allowed to use.
|
||||
* For example if the concurrency limit is 20 and there are 5 unique types of tasks
|
||||
* in the queue, each task should not be allowed to consume more than 3 (i.e., ⌊20/6⌋).
|
||||
* The + 1 is needed to prevent the queue from becoming full of only one type of class.
|
||||
* i.e., if it wasn't there and there were 20 tasks of the same type in the queue, every
|
||||
* runner would become consumed with the same (potentially long-running task) and no more
|
||||
* tasks can run. This way, some resources are always available if some new types
|
||||
* of tasks enter the queue.
|
||||
*
|
||||
* We use the short-ternary to force the value to 1 in the case when the number of tasks
|
||||
* exceeds the runners (e.g., there are 8 tasks and 4 runners, ⌊4/(8+1)⌋ = 0).
|
||||
*/
|
||||
$slots = floor($concurrencylimit / (count($uniquetasksinqueue) + 1)) ?: 1;
|
||||
if (empty(self::$miniqueue)) {
|
||||
self::$mode = self::ADHOC_TASK_QUEUE_MODE_DISTRIBUTING;
|
||||
self::$miniqueue = self::get_candidate_adhoc_tasks(
|
||||
$timestart,
|
||||
$cachedqueuesize,
|
||||
$slots,
|
||||
$concurrencylimits
|
||||
);
|
||||
}
|
||||
|
||||
// The query to cache tasks is expensive on big data sets, so we use this cheap
|
||||
// query to get the ordering (which is the interesting part about the main query)
|
||||
// We can use this information to filter the cache and also order it.
|
||||
$runningtasks = $DB->get_records_sql(
|
||||
'SELECT classname, COALESCE(COUNT(*), 0) running, MIN(timestarted) earliest
|
||||
FROM {task_adhoc}
|
||||
WHERE timestarted IS NOT NULL
|
||||
AND nextruntime < :timestart
|
||||
GROUP BY classname
|
||||
ORDER BY running ASC, earliest DESC',
|
||||
['timestart' => $timestart]
|
||||
);
|
||||
|
||||
/*
|
||||
* Each runner has a cache, so the same task can be in multiple runners' caches.
|
||||
* We need to check that each task we have cached hasn't gone over its fair number
|
||||
* of slots. This filtering is only applied during distributing mode as when we are
|
||||
* filling capacity we intend for fast tasks to go over their slot limit.
|
||||
*/
|
||||
if (self::$mode === self::ADHOC_TASK_QUEUE_MODE_DISTRIBUTING) {
|
||||
self::$miniqueue = array_filter(
|
||||
self::$miniqueue,
|
||||
function (\stdClass $task) use ($runningtasks, $slots) {
|
||||
return !array_key_exists($task->classname, $runningtasks) || $runningtasks[$task->classname]->running < $slots;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* If this happens that means each task has consumed its fair share of capacity, but there's still
|
||||
* runners left over (and we are one of them). Fetch tasks without checking slot limits.
|
||||
*/
|
||||
if (empty(self::$miniqueue) && array_sum(array_column($runningtasks, 'running')) < $concurrencylimit) {
|
||||
self::$mode = self::ADHOC_TASK_QUEUE_MODE_FILLING;
|
||||
self::$miniqueue = self::get_candidate_adhoc_tasks(
|
||||
$timestart,
|
||||
$cachedqueuesize,
|
||||
false,
|
||||
$concurrencylimits
|
||||
);
|
||||
}
|
||||
|
||||
// Used below to order the cache.
|
||||
$ordering = array_flip(array_keys($runningtasks));
|
||||
|
||||
// Order the queue so it's consistent with the ordering from the DB.
|
||||
usort(
|
||||
self::$miniqueue,
|
||||
function ($a, $b) use ($ordering) {
|
||||
return ($ordering[$a->classname] ?? -1) - ($ordering[$b->classname] ?? -1);
|
||||
}
|
||||
);
|
||||
|
||||
$cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
|
||||
|
||||
$skipclasses = array();
|
||||
|
||||
foreach ($records as $record) {
|
||||
foreach (self::$miniqueue as $taskid => $record) {
|
||||
|
||||
if (in_array($record->classname, $skipclasses)) {
|
||||
// Skip the task if it can't be started due to per-task concurrency limit.
|
||||
@ -643,6 +771,7 @@ class manager {
|
||||
$record = $DB->get_record('task_adhoc', array('id' => $record->id));
|
||||
if (!$record) {
|
||||
$lock->release();
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -650,6 +779,7 @@ class manager {
|
||||
// Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
|
||||
if (!$task) {
|
||||
$lock->release();
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -661,6 +791,7 @@ class manager {
|
||||
// Unable to obtain a concurrency lock.
|
||||
mtrace("Skipping $record->classname adhoc task class as the per-task limit of $tasklimit is reached.");
|
||||
$skipclasses[] = $record->classname;
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
$lock->release();
|
||||
continue;
|
||||
}
|
||||
@ -679,13 +810,76 @@ class manager {
|
||||
} else {
|
||||
$task->set_cron_lock($cronlock);
|
||||
}
|
||||
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
return $task;
|
||||
} else {
|
||||
unset(self::$miniqueue[$taskid]);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of candidate adhoc tasks to run.
|
||||
*
|
||||
* @param int $timestart Only return tasks where nextruntime is less than this value
|
||||
* @param int $limit Limit the list to this many results
|
||||
* @param int|null $runmax Only return tasks that have less than this value currently running
|
||||
* @param array $pertasklimits An array of classname => limit specifying how many instance of a task may be returned
|
||||
* @return array Array of candidate tasks
|
||||
*/
|
||||
public static function get_candidate_adhoc_tasks(
|
||||
int $timestart,
|
||||
int $limit,
|
||||
?int $runmax,
|
||||
array $pertasklimits = []
|
||||
): array {
|
||||
global $DB;
|
||||
|
||||
$pertaskclauses = array_map(
|
||||
function (string $class, int $limit, int $index): array {
|
||||
$limitcheck = $limit > 0 ? " AND COALESCE(run.running, 0) < :running_$index" : "";
|
||||
$limitparam = $limit > 0 ? ["running_$index" => $limit] : [];
|
||||
|
||||
return [
|
||||
"sql" => "(q.classname = :classname_$index" . $limitcheck . ")",
|
||||
"params" => ["classname_$index" => $class] + $limitparam
|
||||
];
|
||||
},
|
||||
array_keys($pertasklimits),
|
||||
$pertasklimits,
|
||||
$pertasklimits ? range(1, count($pertasklimits)) : []
|
||||
);
|
||||
|
||||
$pertasksql = implode(" OR ", array_column($pertaskclauses, 'sql'));
|
||||
$pertaskparams = $pertaskclauses ? array_merge(...array_column($pertaskclauses, 'params')) : [];
|
||||
|
||||
$params = ['timestart' => $timestart] +
|
||||
($runmax ? ['runmax' => $runmax] : []) +
|
||||
$pertaskparams;
|
||||
|
||||
return $DB->get_records_sql(
|
||||
"SELECT q.id, q.classname, q.timestarted, COALESCE(run.running, 0) running, run.earliest
|
||||
FROM {task_adhoc} q
|
||||
LEFT JOIN (
|
||||
SELECT classname, COUNT(*) running, MIN(timestarted) earliest
|
||||
FROM {task_adhoc} run
|
||||
WHERE timestarted IS NOT NULL
|
||||
GROUP BY classname
|
||||
) run ON run.classname = q.classname
|
||||
WHERE nextruntime < :timestart
|
||||
AND q.timestarted 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",
|
||||
$params,
|
||||
0,
|
||||
$limit
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20220510" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20220524" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -3448,6 +3448,7 @@
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="nextruntime_idx" UNIQUE="false" FIELDS="nextruntime"/>
|
||||
<INDEX NAME="timestarted_idx" UNIQUE="false" FIELDS="timestarted"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="task_log" COMMENT="The log table for all tasks">
|
||||
|
@ -4515,5 +4515,20 @@ privatefiles,moodle|/user/files.php';
|
||||
upgrade_main_savepoint(true, 2022052500.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2022052700.01) {
|
||||
|
||||
// Define index timestarted_idx (not unique) to be added to task_adhoc.
|
||||
$table = new xmldb_table('task_adhoc');
|
||||
$index = new xmldb_index('timestarted_idx', XMLDB_INDEX_NOTUNIQUE, ['timestarted']);
|
||||
|
||||
// Conditionally launch add index timestarted_idx.
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022052700.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
68
lib/tests/fixtures/task_fixtures.php
vendored
68
lib/tests/fixtures/task_fixtures.php
vendored
@ -26,14 +26,76 @@
|
||||
namespace core\task;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test class.
|
||||
*
|
||||
* @copyright 2022 Catalyst IT Australia Pty Ltd
|
||||
* @author Cameron Ball <cameron@cameron1729.xyz>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class adhoc_test_task extends \core\task\adhoc_task {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int|null $nextruntime Next run time
|
||||
* @param int|null $timestarted Time started
|
||||
*/
|
||||
public function __construct(?int $nextruntime = null, ?int $timestarted = null) {
|
||||
if ($nextruntime) {
|
||||
$this->set_next_run_time($nextruntime);
|
||||
}
|
||||
|
||||
if ($timestarted) {
|
||||
$this->set_timestarted($timestarted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute.
|
||||
*/
|
||||
public function execute() {
|
||||
}
|
||||
}
|
||||
|
||||
class adhoc_test2_task extends \core\task\adhoc_task {
|
||||
public function execute() {
|
||||
}
|
||||
/**
|
||||
* Test class.
|
||||
*
|
||||
* @copyright 2022 Catalyst IT Australia Pty Ltd
|
||||
* @author Cameron Ball <cameron@cameron1729.xyz>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class adhoc_test2_task extends adhoc_test_task {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class.
|
||||
*
|
||||
* @copyright 2022 Catalyst IT Australia Pty Ltd
|
||||
* @author Cameron Ball <cameron@cameron1729.xyz>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class adhoc_test3_task extends adhoc_test_task {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class.
|
||||
*
|
||||
* @copyright 2022 Catalyst IT Australia Pty Ltd
|
||||
* @author Cameron Ball <cameron@cameron1729.xyz>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class adhoc_test4_task extends adhoc_test_task {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class.
|
||||
*
|
||||
* @copyright 2022 Catalyst IT Australia Pty Ltd
|
||||
* @author Cameron Ball <cameron@cameron1729.xyz>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class adhoc_test5_task extends adhoc_test_task {
|
||||
}
|
||||
|
||||
class scheduled_test_task extends \core\task\scheduled_task {
|
||||
|
@ -22,7 +22,10 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once(__DIR__ . '/fixtures/task_fixtures.php');
|
||||
|
||||
/**
|
||||
* This file contains the unit tests for the task manager.
|
||||
@ -30,168 +33,216 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @copyright 2019 Brendan Heywood <brendan@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_task_manager_testcase extends advanced_testcase {
|
||||
class task_manager_test extends \advanced_testcase {
|
||||
|
||||
public function test_ensure_adhoc_task_qos_provider() {
|
||||
/**
|
||||
* Data provider for test_get_candidate_adhoc_tasks.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_get_candidate_adhoc_tasks_provider(): array {
|
||||
return [
|
||||
[
|
||||
[],
|
||||
[],
|
||||
],
|
||||
// A queue with a lopside initial load that needs to be staggered.
|
||||
[
|
||||
[
|
||||
(object)['id' => 1, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 2, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 3, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 10, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 11, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 12, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 13, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 14, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 15, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
],
|
||||
[
|
||||
(object)['id' => 1, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 2, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 3, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 10, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 11, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 12, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 13, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 14, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 15, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
],
|
||||
],
|
||||
// The same lopsided queue but now the first item is gone.
|
||||
[
|
||||
[
|
||||
(object)['id' => 2, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 3, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
],
|
||||
[
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 2, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 3, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
],
|
||||
],
|
||||
// The same lopsided queue but now the first two items is gone.
|
||||
[
|
||||
[
|
||||
(object)['id' => 3, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
],
|
||||
[
|
||||
(object)['id' => 3, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
],
|
||||
],
|
||||
// The same lopsided queue but now the first three items are gone.
|
||||
[
|
||||
[
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
],
|
||||
[
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 4, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
'concurrencylimit' => 5,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null)
|
||||
],
|
||||
'expected' => [
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
],
|
||||
[
|
||||
(object)['id' => 5, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 7, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
|
||||
(object)['id' => 6, 'classname' => '\core\task\asynchronous_backup_task'],
|
||||
(object)['id' => 8, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
|
||||
(object)['id' => 9, 'classname' => '\tool_dataprivacy\task\process_data_request_task'],
|
||||
'concurrencylimit' => 5,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null)
|
||||
],
|
||||
'expected' => [
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class
|
||||
]
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 1,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null)
|
||||
],
|
||||
'expected' => []
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 2,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null)
|
||||
],
|
||||
'expected' => []
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 2,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test2_task(time() - 20, time()),
|
||||
new adhoc_test2_task(time() - 20, time()),
|
||||
new adhoc_test3_task(time() - 20, null)
|
||||
],
|
||||
'expected' => [adhoc_test3_task::class]
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 2,
|
||||
'limit' => 2,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test2_task(time() - 20, null),
|
||||
],
|
||||
'expected' => [
|
||||
adhoc_test_task::class,
|
||||
adhoc_test_task::class
|
||||
]
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 2,
|
||||
'limit' => 2,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test2_task(time() - 20, null),
|
||||
],
|
||||
'expected' => [
|
||||
adhoc_test2_task::class
|
||||
]
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 3,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test2_task(time() - 20, time()),
|
||||
new adhoc_test2_task(time() - 20, time()),
|
||||
new adhoc_test2_task(time() - 20, null),
|
||||
new adhoc_test3_task(time() - 20, time()),
|
||||
new adhoc_test3_task(time() - 20, time()),
|
||||
new adhoc_test3_task(time() - 20, null),
|
||||
new adhoc_test4_task(time() - 20, time()),
|
||||
new adhoc_test4_task(time() - 20, time()),
|
||||
new adhoc_test4_task(time() - 20, null),
|
||||
new adhoc_test5_task(time() - 20, time()),
|
||||
new adhoc_test5_task(time() - 20, time()),
|
||||
new adhoc_test5_task(time() - 20, null),
|
||||
],
|
||||
'expected' => [
|
||||
adhoc_test_task::class,
|
||||
adhoc_test2_task::class,
|
||||
adhoc_test3_task::class,
|
||||
adhoc_test4_task::class,
|
||||
adhoc_test5_task::class
|
||||
]
|
||||
],
|
||||
[
|
||||
'concurrencylimit' => 3,
|
||||
'limit' => 100,
|
||||
'pertasklimits' => [
|
||||
'adhoc_test_task' => 2,
|
||||
'adhoc_test2_task' => 2,
|
||||
'adhoc_test3_task' => 2,
|
||||
'adhoc_test4_task' => 2,
|
||||
'adhoc_test5_task' => 2
|
||||
],
|
||||
'tasks' => [
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, time()),
|
||||
new adhoc_test_task(time() - 20, null),
|
||||
new adhoc_test2_task(time() - 20, time()),
|
||||
new adhoc_test2_task(time() - 20, time()),
|
||||
new adhoc_test2_task(time() - 20, null),
|
||||
new adhoc_test3_task(time() - 20, time()),
|
||||
new adhoc_test3_task(time() - 20, time()),
|
||||
new adhoc_test3_task(time() - 20, null),
|
||||
new adhoc_test4_task(time() - 20, time()),
|
||||
new adhoc_test4_task(time() - 20, time()),
|
||||
new adhoc_test4_task(time() - 20, null),
|
||||
new adhoc_test5_task(time() - 20, time()),
|
||||
new adhoc_test5_task(time() - 20, time()),
|
||||
new adhoc_test5_task(time() - 20, null),
|
||||
],
|
||||
'expected' => []
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a list of tasks into a simpler string
|
||||
* Test that the candidate adhoc tasks are returned in the right order.
|
||||
*
|
||||
* @param array $input array of tasks
|
||||
* @return string list of task ids
|
||||
*/
|
||||
function flatten($tasks) {
|
||||
$list = '';
|
||||
foreach ($tasks as $id => $task) {
|
||||
$list .= ' ' . $task->id;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the Quality of Service reordering works.
|
||||
* @dataProvider test_get_candidate_adhoc_tasks_provider
|
||||
*
|
||||
* @dataProvider test_ensure_adhoc_task_qos_provider
|
||||
*
|
||||
* @param array $input array of tasks
|
||||
* @param array $expected array of reordered tasks
|
||||
* @param int $concurrencylimit The max number of runners each task can consume
|
||||
* @param int $limit SQL limit
|
||||
* @param array $pertasklimits Per-task limits
|
||||
* @param array $tasks Array of tasks to put in DB and retrieve
|
||||
* @param array $expected Array of expected classnames
|
||||
* @return void
|
||||
* @covers \manager::get_candidate_adhoc_tasks
|
||||
*/
|
||||
public function test_ensure_adhoc_task_qos(array $input, array $expected) {
|
||||
public function test_get_candidate_adhoc_tasks(
|
||||
int $concurrencylimit,
|
||||
int $limit,
|
||||
array $pertasklimits,
|
||||
array $tasks,
|
||||
array $expected
|
||||
): void {
|
||||
$this->resetAfterTest();
|
||||
$result = \core\task\manager::ensure_adhoc_task_qos($input);
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
manager::queue_adhoc_task($task);
|
||||
}
|
||||
|
||||
$result = $this->flatten($result);
|
||||
$expected = $this->flatten($expected);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
$candidates = manager::get_candidate_adhoc_tasks(time(), $limit, $concurrencylimit, $pertasklimits);
|
||||
$this->assertEquals(
|
||||
array_map(
|
||||
function(string $classname): string {
|
||||
return '\\' . $classname;
|
||||
},
|
||||
$expected
|
||||
),
|
||||
array_column($candidates, 'classname')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
This files describes API changes in core libraries and APIs,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 4.1 ===
|
||||
|
||||
* The method ensure_adhoc_task_qos() in lib/classes/task/manager.php has been deprecated, please use get_next_adhoc_task()
|
||||
instead.
|
||||
|
||||
=== 4.0 ===
|
||||
|
||||
* To better detect wrong floats (like, for example, unformatted, using local-dependent separators ones) a number of
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2022052700.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2022052700.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.1dev (Build: 20220527)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user