MDL-64347 task: Add restrictions to adhoc task runner
authorAndrew Nicols <andrew@nicols.co.uk>
Fri, 7 Dec 2018 03:25:23 +0000 (11:25 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 16 Jan 2019 09:20:25 +0000 (17:20 +0800)
admin/settings/server.php
lang/en/admin.php
lib/cronlib.php
version.php

index 9102155..1616fc4 100644 (file)
@@ -212,6 +212,27 @@ $ADMIN->add('server', $temp);
 
 
 $ADMIN->add('server', new admin_category('taskconfig', new lang_string('taskadmintitle', 'admin')));
+$temp = new admin_settingpage('taskprocessing', new lang_string('taskprocessing','admin'));
+$temp->add(
+    new admin_setting_configtext(
+        'task_adhoc_concurrency_limit',
+        new lang_string('task_adhoc_concurrency_limit', 'admin'),
+        new lang_string('task_adhoc_concurrency_limit_desc', 'admin'),
+        3,
+        PARAM_INT
+    )
+);
+
+$temp->add(
+    new admin_setting_configduration(
+        'task_adhoc_max_runtime',
+        new lang_string('task_adhoc_max_runtime', 'admin'),
+        new lang_string('task_adhoc_max_runtime_desc', 'admin'),
+        30 * MINSECS
+    )
+);
+$ADMIN->add('taskconfig', $temp);
+
 $temp = new admin_settingpage('tasklogging', new lang_string('tasklogging','admin'));
 $temp->add(
     new admin_setting_configselect(
index e21455d..c3ecd54 100644 (file)
@@ -1165,6 +1165,10 @@ $string['tablesnosave'] = 'Changes in tables above are saved automatically.';
 $string['tabselectedtofront'] = 'On tables with tabs, should the row with the currently selected tab be placed at the front';
 $string['tabselectedtofronttext'] = 'Bring selected tab row to front';
 $string['testsiteupgradewarning'] = 'You are currently using the {$a} test site, to upgrade it properly use the command line interface tool';
+$string['task_adhoc_concurrency_limit'] = 'Adhoc task concurrency limit';
+$string['task_adhoc_concurrency_limit_desc'] = 'The number of adhoc task runners allowed to run concurrently. If the limit is high then scheduled tasks may not run regularly when there are lots of adhoc tasks. A setting of 0 will disable processing of adhoc tasks completely.';
+$string['task_adhoc_max_runtime'] = 'Adhoc task runner lifetime';
+$string['task_adhoc_max_runtime_desc'] = 'The age of an adhoc task runner before it is freed. A low duration is recommended as there is no limit to the number of adhoc tasks queued. If this number is too high and you have a large adhoc task queue then scheduled tasks may not be run regularly.';
 $string['task_logmode'] = 'When to log';
 $string['task_logmode_desc'] = 'You can choose when you wish task logging to take place. By default logs are always captured. You can disable logging entirely, or change to only log tasks which fail.';
 $string['task_logmode_none'] = 'Do not log anything';
@@ -1214,6 +1218,7 @@ $string['taskmessagingcleanup'] = 'Background processing for messaging';
 $string['taskpasswordresetcleanup'] = 'Cleanup password reset attempts';
 $string['taskplagiarismcron'] = 'Background processing for legacy cron in plagiarism plugins';
 $string['taskportfoliocron'] = 'Background processing for portfolio plugins';
+$string['taskprocessing'] = 'Task processing';
 $string['taskquestioncron'] = 'Background processing for question engine';
 $string['taskrefreshsystemtokens'] = 'Refresh OAuth tokens for service accounts';
 $string['taskregistrationcron'] = 'Site registration';
index b11be3f..77d93ab 100644 (file)
@@ -68,12 +68,8 @@ function cron_run() {
         unset($task);
     }
 
-    // Run all adhoc tasks.
-    while (!\core\task\manager::static_caches_cleared_since($timenow) &&
-           $task = \core\task\manager::get_next_adhoc_task($timenow)) {
-        cron_run_inner_adhoc_task($task);
-        unset($task);
-    }
+    // Run adhoc tasks.
+    cron_run_adhoc_tasks($timenow);
 
     mtrace("Cron script completed correctly");
 
@@ -83,6 +79,47 @@ function cron_run() {
     mtrace("Execution took ".$difftime." seconds");
 }
 
+/**
+ * Execute all queued adhoc tasks, applying necessary concurrency limits and time limits.
+ *
+ * @param   int     $timenow The time this process started.
+ */
+function cron_run_adhoc_tasks(int $timenow) {
+    // Allow a restriction on the number of adhoc task runners at once.
+    $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
+    $maxruns = get_config('core', 'task_adhoc_concurrency_limit');
+    $maxruntime = get_config('core', 'task_adhoc_max_runtime');
+
+    $adhoclock = null;
+    for ($run = 0; $run < $maxruns; $run++) {
+        if ($adhoclock = $cronlockfactory->get_lock("adhoc_task_runner_{$run}", 1)) {
+            break;
+        }
+    }
+
+    if (!$adhoclock) {
+        mtrace("Skipping processing of adhoc tasks. Concurrency limit reached.");
+        return;
+    }
+
+    $starttime = time();
+
+    // Run all adhoc tasks.
+    while (!\core\task\manager::static_caches_cleared_since($timenow) &&
+            $task = \core\task\manager::get_next_adhoc_task($timenow)) {
+        cron_run_inner_adhoc_task($task);
+        unset($task);
+
+        if ((time() - $starttime) > $maxruntime) {
+            mtrace("Stopping processing of adhoc tasks as time limit has been reached.");
+            break;
+        }
+    }
+
+    // Release the adhoc task runner lock.
+    $adhoclock->release();
+}
+
 /**
  * Shared code that handles running of a single scheduled task within the cron.
  *
index 84b00cb..cdf0220 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2019011501.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2019011502.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.