Merge branch 'MDL-77186-master' of https://github.com/andrewnicols/moodle

This commit is contained in:
Jake Dallimore
2023-03-15 12:00:42 +08:00
46 changed files with 1108 additions and 611 deletions
+2 -3
View File
@@ -27,7 +27,6 @@ define('CLI_SCRIPT', true);
require(__DIR__ . '/../../config.php');
require_once("{$CFG->libdir}/clilib.php");
require_once("{$CFG->libdir}/cronlib.php");
list($options, $unrecognized) = cli_get_params(
[
@@ -120,7 +119,7 @@ core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Emulate normal session - we use admin account by default.
cron_setup_user();
\core\cron::setup_user();
$humantimenow = date('r', time());
$keepalive = (int)$options['keep-alive'];
@@ -128,4 +127,4 @@ $keepalive = (int)$options['keep-alive'];
\core\local\cli\shutdown::script_supports_graceful_exit();
mtrace("Server Time: {$humantimenow}\n");
cron_run_adhoc_tasks(time(), $keepalive, $checklimits);
\core\cron::run_adhoc_tasks(time(), $keepalive, $checklimits);
+4 -5
View File
@@ -30,7 +30,6 @@ define('CLI_SCRIPT', true);
require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/clilib.php'); // cli only functions
require_once($CFG->libdir.'/cronlib.php');
// now get cli options
list($options, $unrecognized) = cli_get_params(array('help'=>false),
@@ -80,10 +79,10 @@ if (!empty($CFG->showcrondebugging)) {
$starttime = microtime();
/// emulate normal session
cron_setup_user();
// Emulate normal session.
\core\cron::setup_user();
/// Start output log
// Start output log.
$timenow = time();
mtrace("Server Time: ".date('r',$timenow)."\n\n");
@@ -96,4 +95,4 @@ backup_cron_automated_helper::run_automated_backup(backup_cron_automated_helper:
mtrace("Automated cron backups completed correctly");
$difftime = microtime_diff($starttime, microtime());
mtrace("Execution took ".$difftime." seconds");
mtrace("Execution took ".$difftime." seconds");
+21 -13
View File
@@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -30,12 +29,11 @@
define('CLI_SCRIPT', true);
require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/clilib.php'); // cli only functions
require_once($CFG->libdir.'/cronlib.php');
require(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/clilib.php');
// now get cli options
list($options, $unrecognized) = cli_get_params(
// Now get cli option.
[$options, $unrecognized] = cli_get_params(
[
'help' => false,
'stop' => false,
@@ -44,7 +42,9 @@ list($options, $unrecognized) = cli_get_params(
'enable' => false,
'disable' => false,
'disable-wait' => false,
], [
'keep-alive' => null,
],
[
'h' => 'help',
's' => 'stop',
'l' => 'list',
@@ -52,6 +52,7 @@ list($options, $unrecognized) = cli_get_params(
'e' => 'enable',
'd' => 'disable',
'w' => 'disable-wait',
'k' => 'keep-alive',
]
);
@@ -62,7 +63,7 @@ if ($unrecognized) {
if ($options['help']) {
$help =
"Execute periodic cron actions.
"Execute periodic cron actions.
Options:
-h, --help Print out this help
@@ -72,6 +73,9 @@ Options:
-e, --enable Enable cron
-d, --disable Disable cron
-w, --disable-wait=600 Disable cron and wait until all tasks finished or fail after N seconds (optional param)
-k, --keep-alive=N Keep this script alive for N seconds and poll for new tasks
The default value can be set by administrators in:
Site administration > Server > Tasks > Task processing > Keep alive time
Example:
\$sudo -u www-data /usr/bin/php admin/cli/cron.php
@@ -105,7 +109,8 @@ if ($options['list']) {
$tasks = \core\task\manager::get_running_tasks();
mtrace('The list of currently running tasks:');
$format = "%7s %-12s %-9s %-20s %-52s\n";
printf ($format,
printf(
$format,
'PID',
'HOST',
'TYPE',
@@ -113,7 +118,8 @@ if ($options['list']) {
'CLASSNAME'
);
foreach ($tasks as $task) {
printf ($format,
printf(
$format,
$task->pid,
substr($task->hostname, 0, 12),
$task->type,
@@ -137,7 +143,7 @@ if ($wait = $options['disable-wait']) {
set_config('cron_enabled', 0);
\core\task\manager::clear_static_caches();
mtrace('Cron has been disabled for the site.');
mtrace('Allocating '. format_time($waitsec) . ' for the tasks to finish.');
mtrace('Allocating ' . format_time($waitsec) . ' for the tasks to finish.');
$lastcount = 0;
while ($wait) {
@@ -151,7 +157,7 @@ if ($wait = $options['disable-wait']) {
if (time() - $started >= $waitsec) {
mtrace('');
mtrace('Wait time ('. format_time($waitsec) . ') elapsed, but ' . count($tasks) . ' task(s) still running.');
mtrace('Wait time (' . format_time($waitsec) . ') elapsed, but ' . count($tasks) . ' task(s) still running.');
mtrace('Exiting with code 1.');
exit(1);
}
@@ -175,4 +181,6 @@ if (!get_config('core', 'cron_enabled') && !$options['force']) {
\core\local\cli\shutdown::script_supports_graceful_exit();
cron_run();
$keepalive = $options['keep-alive'];
\core\cron::run_main_process($keepalive);
-1
View File
@@ -28,7 +28,6 @@ define('CLI_SCRIPT', true);
require(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/clilib.php');
require_once($CFG->libdir . '/cronlib.php');
list($options, $unrecognized) = cli_get_params(
[
+2 -3
View File
@@ -27,7 +27,6 @@ define('CLI_SCRIPT', true);
require(__DIR__ . '/../../config.php');
require_once("$CFG->libdir/clilib.php");
require_once("$CFG->libdir/cronlib.php");
list($options, $unrecognized) = cli_get_params(
[
@@ -142,7 +141,7 @@ if ($execute = $options['execute']) {
raise_memory_limit(MEMORY_EXTRA);
// Emulate normal session - we use admin account by default.
cron_setup_user();
\core\cron::setup_user();
// Execute the task.
\core\local\cli\shutdown::script_supports_graceful_exit();
@@ -164,5 +163,5 @@ if ($execute = $options['execute']) {
$task->set_cron_lock($cronlock);
}
cron_run_inner_scheduled_task($task);
\core\cron::run_inner_scheduled_task($task);
}
+6 -7
View File
@@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@@ -36,6 +35,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// phpcs:ignoreFile moodle.Files.MoodleInternal.MoodleInternalGlobalState
if (defined('STDIN')) {
fwrite(STDERR, "ERROR: This script no longer supports CLI, please use admin/cli/cron.php instead\n");
@@ -49,8 +49,7 @@ define('WEB_CRON_EMULATED_CLI', 'defined'); // ugly ugly hack, do not use elsewh
define('NO_OUTPUT_BUFFERING', true);
require('../config.php');
require_once($CFG->libdir.'/clilib.php');
require_once($CFG->libdir.'/cronlib.php');
require_once($CFG->libdir . '/clilib.php');
// extra safety
\core\session\manager::write_close();
@@ -71,11 +70,11 @@ if (!empty($CFG->cronremotepassword)) {
}
}
// send mime type and encoding
// Send mime type and encoding.
@header('Content-Type: text/plain; charset=utf-8');
// we do not want html markup in emulated CLI
// We do not want html markup in emulated CLI.
@ini_set('html_errors', 'off');
// execute the cron
cron_run();
// Execute the cron, disabling keepalive.
\core\cron::run_main_process(0);
+14
View File
@@ -331,6 +331,20 @@ if ($hassiteconfig) {
$setting->set_updatedcallback('theme_reset_static_caches');
$temp->add($setting);
$setting = new admin_setting_configduration(
'cron_keepalive',
new lang_string('cron_keepalive', 'admin'),
new lang_string('cron_keepalive_desc', 'admin'),
\core\cron::DEFAULT_MAIN_PROCESS_KEEPALIVE,
// The default unit is minutes.
MINSECS,
);
// Set an upper limit.
$setting->set_max_duration(\core\cron::MAX_MAIN_PROCESS_KEEPALIVE);
$temp->add($setting);
$temp->add(
new admin_setting_configtext(
'task_scheduled_concurrency_limit',
+1 -1
View File
@@ -101,7 +101,7 @@ if (!file_exists($source)) {
}
// Emulate normal session - we use admin account by default.
cron_setup_user();
\core\cron::setup_user();
// Get the file list.
$files = [];
@@ -366,7 +366,7 @@ class manager {
}
// Record the user that this script is currently being run as. This is important when re-processing existing
// messages, as cron_setup_user is called multiple times.
// messages, as \core\cron::setup_user is called multiple times.
$originaluser = $USER;
$envelope = $message->getEnvelope();
@@ -433,7 +433,7 @@ class manager {
// Process the message as the user.
$user = $this->addressmanager->get_data()->user;
mtrace("-- Processing the message as user {$user->id} ({$user->username}).");
cron_setup_user($user);
\core\cron::setup_user($user);
// Process and retrieve the message data for this message.
// This includes fetching the full content, as well as all headers, and attachments.
@@ -459,7 +459,7 @@ class manager {
// Returning to normal cron user.
mtrace("-- Returning to the original user.");
cron_setup_user($originaluser);
\core\cron::setup_user($originaluser);
return;
}
@@ -478,7 +478,7 @@ class manager {
// Returning to normal cron user.
mtrace("-- Returning to the original user.");
cron_setup_user($originaluser);
\core\cron::setup_user($originaluser);
return;
} catch (\Exception $e) {
// An unknown error occurred. The user is not informed, but the administrator is.
@@ -487,7 +487,7 @@ class manager {
// Returning to normal cron user.
mtrace("-- Returning to the original user.");
cron_setup_user($originaluser);
\core\cron::setup_user($originaluser);
return;
}
@@ -501,7 +501,7 @@ class manager {
// Returning to normal cron user.
mtrace("-- Returning to the original user.");
cron_setup_user($originaluser);
\core\cron::setup_user($originaluser);
mtrace("-- Finished processing " . $message->getUid());
-2
View File
@@ -26,8 +26,6 @@ define('NO_OUTPUT_BUFFERING', true);
require('../../../config.php');
require_once($CFG->libdir.'/cronlib.php');
// Basic security checks.
require_admin();
$context = context_system::instance();
-2
View File
@@ -28,8 +28,6 @@ define('NO_OUTPUT_BUFFERING', true);
require('../../../config.php');
require_once($CFG->libdir.'/cronlib.php');
/**
* Function used to handle mtrace by outputting the text to normal browser window.
*
+1 -1
View File
@@ -184,7 +184,7 @@ if ($options['restorefile'] && !file_exists($options['restorefile'])) {
$processoroptions['restorefile'] = $options['restorefile'];
// Emulate normal session.
cron_setup_user();
\core\cron::setup_user();
// Let's get started!
$content = file_get_contents($options['file']);
+1 -1
View File
@@ -36,7 +36,7 @@ core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Emulate normal session - we use admin account by default, set language to the site language.
cron_setup_user();
\core\cron::setup_user();
$USER->lang = $CFG->lang;
$clihelper = new \tool_uploaduser\cli_helper();
-1
View File
@@ -30,7 +30,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
require_once($CFG->libdir.'/cronlib.php');
require_once($CFG->libdir . '/completionlib.php');
/**
@@ -123,7 +123,7 @@ abstract class backup_cron_automated_helper {
mtrace("Skipping deleted courses", '...');
mtrace(sprintf("%d courses", self::remove_deleted_courses_from_schedule()));
mtrace('Running required automated backups...');
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
mtrace("Getting admin info");
$admin = get_admin();
@@ -68,7 +68,7 @@ class content_notification_task extends adhoc_task {
from user with id {$userfrom->id}.");
foreach ($users as $user) {
cron_setup_user($user, $course);
\core\cron::setup_user($user, $course);
$cm = get_fast_modinfo($course)->cms[$cm->id];
@@ -53,11 +53,11 @@ class course_delete_modules extends \core\task\adhoc_task {
// Set the proper user.
if ($this->get_custom_data()->userid !== $this->get_custom_data()->realuserid) {
$realuser = \core_user::get_user($this->get_custom_data()->realuserid, '*', MUST_EXIST);
cron_setup_user($realuser);
\core\cron::setup_user($realuser);
\core\session\manager::loginas($this->get_custom_data()->userid, \context_system::instance(), false);
} else {
$user = \core_user::get_user($this->get_custom_data()->userid, '*', MUST_EXIST);
cron_setup_user($user);
\core\cron::setup_user($user);
}
$cms = $this->get_custom_data()->cms;
+4
View File
@@ -230,6 +230,8 @@ $string['configdndallowtextandlinks'] = 'Enable or disable the dragging and drop
$string['configdoclang'] = 'This language will be used in links for the documentation pages.';
$string['configdocroot'] = 'Defines the path to Moodle Docs for providing context-specific documentation via \'Help and documentation\' links in the footer of each page. If the field is left blank, links will not be displayed.';
$string['configdoctonewwindow'] = 'If enabled, then links to Moodle Docs will be shown in a new window.';
$string['configduration_low'] = 'This value is too low. The minimum value is {$a}.';
$string['configduration_high'] = 'This value is too high. The maximum value is {$a}.';
$string['configeditordictionary'] = 'This value will be used if aspell doesn\'t have dictionary for users own language.';
$string['configeditorfontlist'] = 'Select the fonts that should appear in the editor\'s drop-down list.';
$string['configemailchangeconfirmation'] = 'Require an email confirmation step when users change their email address in their profile.';
@@ -442,6 +444,8 @@ $string['cron'] = 'Cron';
$string['cron_enabled'] = 'Enable cron';
$string['cron_enabled_desc'] = 'Cron should normally be enabled, however this setting allows it to be disabled temporarily, for example before a server restart. If disabled, the system is prevented from starting new background tasks. Note that the cron should not be disabled for a long time, as this will prevent important functionality from working.';
$string['cron_help'] = 'The cron.php script runs a number of tasks at different scheduled intervals, such as sending forum post notification emails. The script should be run regularly - ideally every minute.';
$string['cron_keepalive'] = 'Keep alive';
$string['cron_keepalive_desc'] = 'The amount of time to keep polling for additional tasks. This setting is useful for ensuring that cron is always running.<br><br>If you use dedicated task runners then you should set this value to an 0, otherwise we recommend setting this to around a value similar to your adhoc task concurrency limit. Longer values should be avoided and the maximum value is 15 minutes.';
$string['cron_link'] = 'admin/cron';
$string['cronclionly'] = 'Cron execution via command line only';
$string['cronerrorclionly'] = 'Sorry, internet access to this page has been disabled by the administrator.';
+58 -7
View File
@@ -3874,6 +3874,12 @@ class admin_setting_configduration extends admin_setting {
/** @var callable|null Validation function */
protected $validatefunction = null;
/** @var int The minimum allowed value */
protected int $minduration = 0;
/** @var null|int The maximum allowed value */
protected null|int $maxduration = null;
/**
* Constructor
* @param string $name unique ascii name, either 'mysetting' for settings that in config,
@@ -3896,6 +3902,31 @@ class admin_setting_configduration extends admin_setting {
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
/**
* Set the minimum allowed value.
* This must be at least 0.
*
* @param int $duration
*/
public function set_min_duration(int $duration): void {
if ($duration < 0) {
throw new coding_exception('The minimum duration must be at least 0.');
}
$this->minduration = $duration;
}
/**
* Set the maximum allowed value.
*
* A value of null will disable the maximum duration value.
*
* @param int|null $duration
*/
public function set_max_duration(?int $duration): void {
$this->maxduration = $duration;
}
/**
* Sets a validate function.
*
@@ -3918,15 +3949,23 @@ class admin_setting_configduration extends admin_setting {
* @since Moodle 3.10
*/
protected function validate_setting(int $data): string {
if ($data < $this->minduration) {
return get_string(
'configduration_low',
'admin',
self::get_duration_text($this->minduration, get_string('numseconds', 'core', 0))
);
}
if ($this->maxduration && $data > $this->maxduration) {
return get_string('configduration_high', 'admin', self::get_duration_text($this->maxduration));
}
// If validation function is specified, call it now.
if ($this->validatefunction) {
return call_user_func($this->validatefunction, $data);
} else {
if ($data < 0) {
return get_string('errorsetting', 'admin');
}
return '';
}
return '';
}
/**
@@ -3948,10 +3987,14 @@ class admin_setting_configduration extends admin_setting {
* Converts seconds to some more user friendly string.
* @static
* @param int $seconds
* @param null|string The value to use when the duration is empty. If not specified, a "None" value is used.
* @return string
*/
protected static function get_duration_text($seconds) {
protected static function get_duration_text(int $seconds, ?string $emptyvalue = null): string {
if (empty($seconds)) {
if ($emptyvalue !== null) {
return $emptyvalue;
}
return get_string('none');
}
$data = self::parse_seconds($seconds);
@@ -4043,7 +4086,15 @@ class admin_setting_configduration extends admin_setting {
}
$inputid = $this->get_id() . 'v';
$units = self::get_units();
$units = array_filter(self::get_units(), function($unit): bool {
if (!$this->maxduration) {
// No duration limit. All units are valid.
return true;
}
return $unit <= $this->maxduration;
}, ARRAY_FILTER_USE_KEY);
$defaultunit = $this->defaultunit;
$context = (object) [
+612
View File
@@ -0,0 +1,612 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
use coding_exception;
use core_php_time_limit;
use moodle_exception;
use stdClass;
// Disable the moodle.PHP.ForbiddenFunctions.FoundWithAlternative sniff for this file.
// It detects uses of error_log() which are valid in this file.
// phpcs:disable moodle.PHP.ForbiddenFunctions.FoundWithAlternative
/**
* Cron and adhoc task functionality.
*
* @package core
* @copyright 2023 Andrew Lyons <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cron {
/** @var ?stdClass A copy of the standard cron 'user' */
protected static ?stdClass $cronuser = null;
/** @var ?stdClass The cron user's session data */
protected static ?stdClass $cronsession = null;
/**
* Use a default value of 3 minutes.
* The recommended cron frequency is every minute, and the default adhoc concurrency is 3.
* A default value of 3 minutes allows all adhoc tasks to be run concurrently at their default value.
*
* @var int The default keepalive value for the main cron runner
*/
public const DEFAULT_MAIN_PROCESS_KEEPALIVE = 3 * MINSECS;
/**
* @var int The max keepalive value for the main cron runner
*/
public const MAX_MAIN_PROCESS_KEEPALIVE = 15 * MINSECS;
/**
* Execute cron tasks
*
* @param int|null $keepalive The keepalive time for this cron run.
*/
public static function run_main_process(?int $keepalive = null): void {
global $CFG, $DB;
if (CLI_MAINTENANCE) {
echo "CLI maintenance mode active, cron execution suspended.\n";
exit(1);
}
if (moodle_needs_upgrading()) {
echo "Moodle upgrade pending, cron execution suspended.\n";
exit(1);
}
require_once($CFG->libdir . '/adminlib.php');
if (!empty($CFG->showcronsql)) {
$DB->set_debug(true);
}
if (!empty($CFG->showcrondebugging)) {
set_debugging(DEBUG_DEVELOPER, true);
}
core_php_time_limit::raise();
// Increase memory limit.
raise_memory_limit(MEMORY_EXTRA);
// Emulate normal session. - we use admin account by default.
self::setup_user();
// Start output log.
$timenow = time();
mtrace("Server Time: " . date('r', $timenow) . "\n\n");
// Record start time and interval between the last cron runs.
$laststart = get_config('tool_task', 'lastcronstart');
set_config('lastcronstart', $timenow, 'tool_task');
if ($laststart) {
// Record the interval between last two runs (always store at least 1 second).
set_config('lastcroninterval', max(1, $timenow - $laststart), 'tool_task');
}
// Determine the time when the cron should finish.
if ($keepalive === null) {
$keepalive = get_config('core', 'cron_keepalive');
if ($keepalive === false) {
$keepalive = self::DEFAULT_MAIN_PROCESS_KEEPALIVE;
}
}
if ($keepalive > self::MAX_MAIN_PROCESS_KEEPALIVE) {
// Attempt to prevent abnormally long keepalives.
mtrace("Cron keepalive time is too long, reducing to 15 minutes.");
$keepalive = self::MAX_MAIN_PROCESS_KEEPALIVE;
}
// Calculate the finish time based on the start time and keepalive.
$finishtime = $timenow + $keepalive;
do {
$startruntime = microtime();
// Run all scheduled tasks.
self::run_scheduled_tasks($timenow);
// Run adhoc tasks.
self::run_adhoc_tasks($timenow);
mtrace("Cron run completed correctly");
gc_collect_cycles();
$completiontime = date('H:i:s');
$difftime = microtime_diff($startruntime, microtime());
$memoryused = display_size(memory_get_usage());
$message = "Cron completed at {$completiontime} in {$difftime} seconds. Memory used: {$memoryused}.";
// Check if we should continue to run.
// Only continue to run if:
// - The finish time has not been reached; and
// - The graceful exit flag has not been set; and
// - The static caches have not been cleared since the start of the cron run.
$remaining = $finishtime - time();
$runagain = $remaining > 0;
$runagain = $runagain && !\core\local\cli\shutdown::should_gracefully_exit();
$runagain = $runagain && !\core\task\manager::static_caches_cleared_since($timenow);
if ($runagain) {
$message .= " Continuing to check for tasks for {$remaining} more seconds.";
mtrace($message);
sleep(1);
// Re-check the graceful exit and cache clear flags after sleeping as these may have changed.
$runagain = $runagain && !\core\local\cli\shutdown::should_gracefully_exit();
$runagain = $runagain && !\core\task\manager::static_caches_cleared_since($timenow);
} else {
mtrace($message);
}
} while ($runagain);
}
/**
* Execute all queued scheduled tasks, applying necessary concurrency limits and time limits.
*
* @param int $timenow The time this process started.
* @throws \moodle_exception
*/
public static function run_scheduled_tasks(int $timenow): void {
// Allow a restriction on the number of scheduled task runners at once.
$cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
$maxruns = get_config('core', 'task_scheduled_concurrency_limit');
$maxruntime = get_config('core', 'task_scheduled_max_runtime');
$scheduledlock = null;
for ($run = 0; $run < $maxruns; $run++) {
// If we can't get a lock instantly it means runner N is already running
// so fail as fast as possible and try N+1 so we don't limit the speed at
// which we bring new runners into the pool.
if ($scheduledlock = $cronlockfactory->get_lock("scheduled_task_runner_{$run}", 0)) {
break;
}
}
if (!$scheduledlock) {
mtrace("Skipping processing of scheduled tasks. Concurrency limit reached.");
return;
}
$starttime = time();
// Run all scheduled tasks.
try {
while (
!\core\local\cli\shutdown::should_gracefully_exit() &&
!\core\task\manager::static_caches_cleared_since($timenow) &&
$task = \core\task\manager::get_next_scheduled_task($timenow)
) {
self::run_inner_scheduled_task($task);
unset($task);
if ((time() - $starttime) > $maxruntime) {
mtrace("Stopping processing of scheduled tasks as time limit has been reached.");
break;
}
}
} finally {
// Release the scheduled task runner lock.
$scheduledlock->release();
}
}
/**
* Execute all queued adhoc tasks, applying necessary concurrency limits and time limits.
*
* @param int $timenow The time this process started.
* @param int $keepalive Keep this public static function alive for N seconds and poll for new adhoc tasks.
* @param bool $checklimits Should we check limits?
* @throws \moodle_exception
*/
public static function run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true) {
// 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;
if ($checklimits) {
for ($run = 0; $run < $maxruns; $run++) {
// If we can't get a lock instantly it means runner N is already running
// so fail as fast as possible and try N+1 so we don't limit the speed at
// which we bring new runners into the pool.
if ($adhoclock = $cronlockfactory->get_lock("adhoc_task_runner_{$run}", 0)) {
break;
}
}
if (!$adhoclock) {
mtrace("Skipping processing of adhoc tasks. Concurrency limit reached.");
return;
}
}
$humantimenow = date('r', $timenow);
$finishtime = $timenow + $keepalive;
$waiting = false;
$taskcount = 0;
// Run all adhoc tasks.
while (
!\core\local\cli\shutdown::should_gracefully_exit() &&
!\core\task\manager::static_caches_cleared_since($timenow)
) {
if ($checklimits && (time() - $timenow) >= $maxruntime) {
if ($waiting) {
$waiting = false;
mtrace('');
}
mtrace("Stopping processing of adhoc tasks as time limit has been reached.");
break;
}
try {
$task = \core\task\manager::get_next_adhoc_task(time(), $checklimits);
} catch (\Throwable $e) {
if ($adhoclock) {
// Release the adhoc task runner lock.
$adhoclock->release();
}
throw $e;
}
if ($task) {
if ($waiting) {
mtrace('');
}
$waiting = false;
self::run_inner_adhoc_task($task);
self::set_process_title("Waiting for next adhoc task");
$taskcount++;
unset($task);
} else {
$timeleft = $finishtime - time();
if ($timeleft <= 0) {
break;
}
if (!$waiting) {
mtrace('Waiting for more adhoc tasks to be queued ', '');
} else {
mtrace('.', '');
}
$waiting = true;
self::set_process_title("Waiting {$timeleft}s for next adhoc task");
sleep(1);
}
}
if ($waiting) {
mtrace('');
}
mtrace("Ran {$taskcount} adhoc tasks found at {$humantimenow}");
if ($adhoclock) {
// Release the adhoc task runner lock.
$adhoclock->release();
}
}
/**
* Shared code that handles running of a single scheduled task within the cron.
*
* Not intended for calling directly outside of this library!
*
* @param \core\task\task_base $task
*/
public static function run_inner_scheduled_task(\core\task\task_base $task) {
global $CFG, $DB;
$debuglevel = $CFG->debug;
\core\task\manager::scheduled_task_starting($task);
\core\task\logmanager::start_logging($task);
$fullname = $task->get_name() . ' (' . get_class($task) . ')';
mtrace('Execute scheduled task: ' . $fullname);
self::set_process_title('Scheduled task: ' . get_class($task));
self::trace_time_and_memory();
$predbqueries = null;
$predbqueries = $DB->perf_get_queries();
$pretime = microtime(1);
try {
get_mailer('buffer');
self::prepare_core_renderer();
// Temporarily increase debug level if task has failed and debugging isn't already at maximum.
if ($debuglevel !== DEBUG_DEVELOPER && $faildelay = $task->get_fail_delay()) {
mtrace('Debugging increased temporarily due to faildelay of ' . $faildelay);
set_debugging(DEBUG_DEVELOPER);
}
$task->execute();
if ($DB->is_transaction_started()) {
throw new coding_exception("Task left transaction open");
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace('Scheduled task complete: ' . $fullname);
\core\task\manager::scheduled_task_complete($task);
} catch (\Throwable $e) {
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . get_class($task));
$DB->force_transaction_rollback();
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace('Scheduled task failed: ' . $fullname . ',' . $e->getMessage());
if ($CFG->debugdeveloper) {
if (!empty($e->debuginfo)) {
mtrace("Debug info:");
mtrace($e->debuginfo);
}
mtrace("Backtrace:");
mtrace(format_backtrace($e->getTrace(), true));
}
\core\task\manager::scheduled_task_failed($task);
} finally {
// Reset debugging if it changed.
if ($CFG->debug !== $debuglevel) {
set_debugging($debuglevel);
}
// Reset back to the standard admin user.
self::setup_user();
self::set_process_title('Waiting for next scheduled task');
self::prepare_core_renderer(true);
}
get_mailer('close');
}
/**
* Shared code that handles running of a single adhoc task within the cron.
*
* @param \core\task\adhoc_task $task
*/
public static function run_inner_adhoc_task(\core\task\adhoc_task $task) {
global $CFG, $DB;
$debuglevel = $CFG->debug;
\core\task\manager::adhoc_task_starting($task);
\core\task\logmanager::start_logging($task);
mtrace("Execute adhoc task: " . get_class($task));
mtrace("Adhoc task id: " . $task->get_id());
mtrace("Adhoc task custom data: " . $task->get_custom_data_as_string());
self::set_process_title('Adhoc task: ' . $task->get_id() . ' ' . get_class($task));
self::trace_time_and_memory();
$predbqueries = null;
$predbqueries = $DB->perf_get_queries();
$pretime = microtime(1);
if ($userid = $task->get_userid()) {
// This task has a userid specified.
if ($user = \core_user::get_user($userid)) {
// User found. Check that they are suitable.
try {
\core_user::require_active_user($user, true, true);
} catch (moodle_exception $e) {
mtrace("User {$userid} cannot be used to run an adhoc task: " . get_class($task) . ". Cancelling task.");
$user = null;
}
} else {
// Unable to find the user for this task.
// A user missing in the database will never reappear.
mtrace("User {$userid} could not be found for adhoc task: " . get_class($task) . ". Cancelling task.");
}
if (empty($user)) {
// A user missing in the database will never reappear so the task needs to be failed to ensure that locks are
// removed, and then removed to prevent future runs.
// A task running as a user should only be run as that user.
\core\task\manager::adhoc_task_failed($task);
$DB->delete_records('task_adhoc', ['id' => $task->get_id()]);
return;
}
self::setup_user($user);
}
try {
get_mailer('buffer');
self::prepare_core_renderer();
// Temporarily increase debug level if task has failed and debugging isn't already at maximum.
if ($debuglevel !== DEBUG_DEVELOPER && $faildelay = $task->get_fail_delay()) {
mtrace('Debugging increased temporarily due to faildelay of ' . $faildelay);
set_debugging(DEBUG_DEVELOPER);
}
$task->execute();
if ($DB->is_transaction_started()) {
throw new coding_exception("Task left transaction open");
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace("Adhoc task complete: " . get_class($task));
\core\task\manager::adhoc_task_complete($task);
} catch (\Throwable $e) {
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . get_class($task));
$DB->force_transaction_rollback();
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace("Adhoc task failed: " . get_class($task) . "," . $e->getMessage());
if ($CFG->debugdeveloper) {
if (!empty($e->debuginfo)) {
mtrace("Debug info:");
mtrace($e->debuginfo);
}
mtrace("Backtrace:");
mtrace(format_backtrace($e->getTrace(), true));
}
\core\task\manager::adhoc_task_failed($task);
} finally {
// Reset debug level if it changed.
if ($CFG->debug !== $debuglevel) {
set_debugging($debuglevel);
}
// Reset back to the standard admin user.
self::setup_user();
self::prepare_core_renderer(true);
}
get_mailer('close');
}
/**
* Sets the process title
*
* This makes it very easy for a sysadmin to immediately see what task
* a cron process is running at any given moment.
*
* @param string $title process status title
*/
public static function set_process_title(string $title) {
global $CFG;
if (CLI_SCRIPT) {
require_once($CFG->libdir . '/clilib.php');
$datetime = userdate(time(), '%b %d, %H:%M:%S');
cli_set_process_title_suffix("$datetime $title");
}
}
/**
* Output some standard information during cron runs. Specifically current time
* and memory usage. This method also does gc_collect_cycles() (before displaying
* memory usage) to try to help PHP manage memory better.
*/
public static function trace_time_and_memory() {
gc_collect_cycles();
mtrace('... started ' . date('H:i:s') . '. Current memory use ' . display_size(memory_get_usage()) . '.');
}
/**
* Prepare the output renderer for the cron run.
*
* This involves creating a new $PAGE, and $OUTPUT fresh for each task and prevents any one task from influencing
* any other.
*
* @param bool $restore Whether to restore the original PAGE and OUTPUT
*/
public static function prepare_core_renderer($restore = false) {
global $OUTPUT, $PAGE;
// Store the original PAGE and OUTPUT values so that they can be reset at a later point to the original.
// This should not normally be required, but may be used in places such as the scheduled task tool's "Run now"
// functionality.
static $page = null;
static $output = null;
if (null === $page) {
$page = $PAGE;
}
if (null === $output) {
$output = $OUTPUT;
}
if (!empty($restore)) {
$PAGE = $page;
$page = null;
$OUTPUT = $output;
$output = null;
} else {
// Setup a new General renderer.
// Cron tasks may produce output to be used in web, so we must use the appropriate renderer target.
// This allows correct use of templates, etc.
$PAGE = new \moodle_page();
$OUTPUT = new \core_renderer($PAGE, RENDERER_TARGET_GENERAL);
}
}
/**
* Sets up a user and course environment in cron.
* Do not use outside of cron script!
*
* Please note that this function stores cache data statically.
* @see reset_user_cache() to reset this cache.
*
* @param null|stdClass $user full user object, null means default cron user (admin)
* @param null|stdClass $course full course record, null means $SITE
* @param null|bool $leavepagealone If specified, stops it messing with global page object
*/
public static function setup_user(?stdClass $user = null, ?stdClass $course = null, bool $leavepagealone = false): void {
// This function uses the $GLOBALS super global. Disable the VariableNameLowerCase sniff for this function.
// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase
global $CFG, $SITE, $PAGE;
if (!CLI_SCRIPT && !$leavepagealone) {
throw new coding_exception('It is not possible to use \core\cron\setup_user() in normal requests!');
}
if (empty(self::$cronuser)) {
// The cron user is essentially the admin user, but with some value removed.
// We ginore the timezone language, and locale preferences - use the site default instead.
self::$cronuser = get_admin();
self::$cronuser->timezone = $CFG->timezone;
self::$cronuser->lang = '';
self::$cronuser->theme = '';
unset(self::$cronuser->description);
self::$cronsession = new stdClass();
}
if (!$user) {
// Cached default cron user (==modified admin for now).
\core\session\manager::init_empty_session();
\core\session\manager::set_user(self::$cronuser);
$GLOBALS['SESSION'] = self::$cronsession;
} else {
// Emulate real user session - needed for caps in cron.
if ($GLOBALS['USER']->id != $user->id) {
\core\session\manager::init_empty_session();
\core\session\manager::set_user($user);
}
}
// TODO MDL-19774 relying on global $PAGE in cron is a bad idea.
// Temporary hack so that cron does not give fatal errors.
if (!$leavepagealone) {
$PAGE = new \moodle_page();
$PAGE->set_course($course ?? $SITE);
}
// TODO: it should be possible to improve perf by caching some limited number of users here.
// phpcs:enable
}
/**
* Resets the cache for the cron user used by `setup_user()`.
*/
public static function reset_user_cache(): void {
self::$cronuser = null;
self::$cronsession = null;
\core\session\manager::init_empty_session();
}
}
+1 -1
View File
@@ -53,7 +53,7 @@ class plagiarism_cron_task extends scheduled_task {
$plagiarismplugin = new $plagiarismclass;
if (method_exists($plagiarismplugin, 'cron')) {
mtrace('Processing cron function for plagiarism_plugin_' . $plugin . '...', '');
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
mtrace('It has been detected the class ' . $plagiarismclass . ' has a legacy cron method
implemented. Plagiarism plugins should implement their own schedule tasks.', '');
$plagiarismplugin->cron();
+6 -439
View File
@@ -23,443 +23,10 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Execute cron tasks
*/
function cron_run() {
global $DB, $CFG, $OUTPUT;
defined('MOODLE_INTERNAL') || die;
if (CLI_MAINTENANCE) {
echo "CLI maintenance mode active, cron execution suspended.\n";
exit(1);
}
if (moodle_needs_upgrading()) {
echo "Moodle upgrade pending, cron execution suspended.\n";
exit(1);
}
require_once($CFG->libdir.'/adminlib.php');
if (!empty($CFG->showcronsql)) {
$DB->set_debug(true);
}
if (!empty($CFG->showcrondebugging)) {
set_debugging(DEBUG_DEVELOPER, true);
}
core_php_time_limit::raise();
$starttime = microtime();
// Increase memory limit
raise_memory_limit(MEMORY_EXTRA);
// Emulate normal session - we use admin accoutn by default
cron_setup_user();
// Start output log
$timenow = time();
mtrace("Server Time: ".date('r', $timenow)."\n\n");
// Record start time and interval between the last cron runs.
$laststart = get_config('tool_task', 'lastcronstart');
set_config('lastcronstart', $timenow, 'tool_task');
if ($laststart) {
// Record the interval between last two runs (always store at least 1 second).
set_config('lastcroninterval', max(1, $timenow - $laststart), 'tool_task');
}
// Run all scheduled tasks.
cron_run_scheduled_tasks($timenow);
// Run adhoc tasks.
cron_run_adhoc_tasks($timenow);
mtrace("Cron script completed correctly");
gc_collect_cycles();
mtrace('Cron completed at ' . date('H:i:s') . '. Memory used ' . display_size(memory_get_usage()) . '.');
$difftime = microtime_diff($starttime, microtime());
mtrace("Execution took ".$difftime." seconds");
}
/**
* Execute all queued scheduled tasks, applying necessary concurrency limits and time limits.
*
* @param int $timenow The time this process started.
* @throws \moodle_exception
*/
function cron_run_scheduled_tasks(int $timenow) {
// Allow a restriction on the number of scheduled task runners at once.
$cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
$maxruns = get_config('core', 'task_scheduled_concurrency_limit');
$maxruntime = get_config('core', 'task_scheduled_max_runtime');
$scheduledlock = null;
for ($run = 0; $run < $maxruns; $run++) {
// If we can't get a lock instantly it means runner N is already running
// so fail as fast as possible and try N+1 so we don't limit the speed at
// which we bring new runners into the pool.
if ($scheduledlock = $cronlockfactory->get_lock("scheduled_task_runner_{$run}", 0)) {
break;
}
}
if (!$scheduledlock) {
mtrace("Skipping processing of scheduled tasks. Concurrency limit reached.");
return;
}
$starttime = time();
// Run all scheduled tasks.
try {
while (!\core\local\cli\shutdown::should_gracefully_exit() &&
!\core\task\manager::static_caches_cleared_since($timenow) &&
$task = \core\task\manager::get_next_scheduled_task($timenow)) {
cron_run_inner_scheduled_task($task);
unset($task);
if ((time() - $starttime) > $maxruntime) {
mtrace("Stopping processing of scheduled tasks as time limit has been reached.");
break;
}
}
} finally {
// Release the scheduled task runner lock.
$scheduledlock->release();
}
}
/**
* Execute all queued adhoc tasks, applying necessary concurrency limits and time limits.
*
* @param int $timenow The time this process started.
* @param int $keepalive Keep this function alive for N seconds and poll for new adhoc tasks.
* @param bool $checklimits Should we check limits?
* @throws \moodle_exception
*/
function cron_run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true) {
// 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');
if ($checklimits) {
$adhoclock = null;
for ($run = 0; $run < $maxruns; $run++) {
// If we can't get a lock instantly it means runner N is already running
// so fail as fast as possible and try N+1 so we don't limit the speed at
// which we bring new runners into the pool.
if ($adhoclock = $cronlockfactory->get_lock("adhoc_task_runner_{$run}", 0)) {
break;
}
}
if (!$adhoclock) {
mtrace("Skipping processing of adhoc tasks. Concurrency limit reached.");
return;
}
}
$humantimenow = date('r', $timenow);
$finishtime = $timenow + $keepalive;
$waiting = false;
$taskcount = 0;
// Run all adhoc tasks.
while (!\core\local\cli\shutdown::should_gracefully_exit() &&
!\core\task\manager::static_caches_cleared_since($timenow)) {
if ($checklimits && (time() - $timenow) >= $maxruntime) {
if ($waiting) {
$waiting = false;
mtrace('');
}
mtrace("Stopping processing of adhoc tasks as time limit has been reached.");
break;
}
try {
$task = \core\task\manager::get_next_adhoc_task(time(), $checklimits);
} catch (\Throwable $e) {
if ($adhoclock) {
// Release the adhoc task runner lock.
$adhoclock->release();
}
throw $e;
}
if ($task) {
if ($waiting) {
mtrace('');
}
$waiting = false;
cron_run_inner_adhoc_task($task);
cron_set_process_title("Waiting for next adhoc task");
$taskcount++;
unset($task);
} else {
$timeleft = $finishtime - time();
if ($timeleft <= 0) {
break;
}
if (!$waiting) {
mtrace('Waiting for more adhoc tasks to be queued ', '');
} else {
mtrace('.', '');
}
$waiting = true;
cron_set_process_title("Waiting {$timeleft}s for next adhoc task");
sleep(1);
}
}
if ($waiting) {
mtrace('');
}
mtrace("Ran {$taskcount} adhoc tasks found at {$humantimenow}");
if ($adhoclock) {
// Release the adhoc task runner lock.
$adhoclock->release();
}
}
/**
* Shared code that handles running of a single scheduled task within the cron.
*
* Not intended for calling directly outside of this library!
*
* @param \core\task\task_base $task
*/
function cron_run_inner_scheduled_task(\core\task\task_base $task) {
global $CFG, $DB;
$debuglevel = $CFG->debug;
\core\task\manager::scheduled_task_starting($task);
\core\task\logmanager::start_logging($task);
$fullname = $task->get_name() . ' (' . get_class($task) . ')';
mtrace('Execute scheduled task: ' . $fullname);
cron_set_process_title('Scheduled task: ' . get_class($task));
cron_trace_time_and_memory();
$predbqueries = null;
$predbqueries = $DB->perf_get_queries();
$pretime = microtime(1);
try {
get_mailer('buffer');
cron_prepare_core_renderer();
// Temporarily increase debug level if task has failed and debugging isn't already at maximum.
if ($debuglevel !== DEBUG_DEVELOPER && $faildelay = $task->get_fail_delay()) {
mtrace('Debugging increased temporarily due to faildelay of ' . $faildelay);
set_debugging(DEBUG_DEVELOPER);
}
$task->execute();
if ($DB->is_transaction_started()) {
throw new coding_exception("Task left transaction open");
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace('Scheduled task complete: ' . $fullname);
\core\task\manager::scheduled_task_complete($task);
} catch (\Throwable $e) {
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . get_class($task));
$DB->force_transaction_rollback();
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace('Scheduled task failed: ' . $fullname . ',' . $e->getMessage());
if ($CFG->debugdeveloper) {
if (!empty($e->debuginfo)) {
mtrace("Debug info:");
mtrace($e->debuginfo);
}
mtrace("Backtrace:");
mtrace(format_backtrace($e->getTrace(), true));
}
\core\task\manager::scheduled_task_failed($task);
} finally {
// Reset debugging if it changed.
if ($CFG->debug !== $debuglevel) {
set_debugging($debuglevel);
}
// Reset back to the standard admin user.
cron_setup_user();
cron_set_process_title('Waiting for next scheduled task');
cron_prepare_core_renderer(true);
}
get_mailer('close');
}
/**
* Shared code that handles running of a single adhoc task within the cron.
*
* @param \core\task\adhoc_task $task
*/
function cron_run_inner_adhoc_task(\core\task\adhoc_task $task) {
global $CFG, $DB;
$debuglevel = $CFG->debug;
\core\task\manager::adhoc_task_starting($task);
\core\task\logmanager::start_logging($task);
mtrace("Execute adhoc task: " . get_class($task));
mtrace("Adhoc task id: " . $task->get_id());
mtrace("Adhoc task custom data: " . $task->get_custom_data_as_string());
cron_set_process_title('Adhoc task: ' . $task->get_id() . ' ' . get_class($task));
cron_trace_time_and_memory();
$predbqueries = null;
$predbqueries = $DB->perf_get_queries();
$pretime = microtime(1);
if ($userid = $task->get_userid()) {
// This task has a userid specified.
if ($user = \core_user::get_user($userid)) {
// User found. Check that they are suitable.
try {
\core_user::require_active_user($user, true, true);
} catch (moodle_exception $e) {
mtrace("User {$userid} cannot be used to run an adhoc task: " . get_class($task) . ". Cancelling task.");
$user = null;
}
} else {
// Unable to find the user for this task.
// A user missing in the database will never reappear.
mtrace("User {$userid} could not be found for adhoc task: " . get_class($task) . ". Cancelling task.");
}
if (empty($user)) {
// A user missing in the database will never reappear so the task needs to be failed to ensure that locks are removed,
// and then removed to prevent future runs.
// A task running as a user should only be run as that user.
\core\task\manager::adhoc_task_failed($task);
$DB->delete_records('task_adhoc', ['id' => $task->get_id()]);
return;
}
cron_setup_user($user);
}
try {
get_mailer('buffer');
cron_prepare_core_renderer();
// Temporarily increase debug level if task has failed and debugging isn't already at maximum.
if ($debuglevel !== DEBUG_DEVELOPER && $faildelay = $task->get_fail_delay()) {
mtrace('Debugging increased temporarily due to faildelay of ' . $faildelay);
set_debugging(DEBUG_DEVELOPER);
}
$task->execute();
if ($DB->is_transaction_started()) {
throw new coding_exception("Task left transaction open");
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace("Adhoc task complete: " . get_class($task));
\core\task\manager::adhoc_task_complete($task);
} catch (\Throwable $e) {
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . get_class($task));
$DB->force_transaction_rollback();
}
if (isset($predbqueries)) {
mtrace("... used " . ($DB->perf_get_queries() - $predbqueries) . " dbqueries");
mtrace("... used " . (microtime(1) - $pretime) . " seconds");
}
mtrace("Adhoc task failed: " . get_class($task) . "," . $e->getMessage());
if ($CFG->debugdeveloper) {
if (!empty($e->debuginfo)) {
mtrace("Debug info:");
mtrace($e->debuginfo);
}
mtrace("Backtrace:");
mtrace(format_backtrace($e->getTrace(), true));
}
\core\task\manager::adhoc_task_failed($task);
} finally {
// Reset debug level if it changed.
if ($CFG->debug !== $debuglevel) {
set_debugging($debuglevel);
}
// Reset back to the standard admin user.
cron_setup_user();
cron_prepare_core_renderer(true);
}
get_mailer('close');
}
/**
* Sets the process title
*
* This makes it very easy for a sysadmin to immediately see what task
* a cron process is running at any given moment.
*
* @param string $title process status title
*/
function cron_set_process_title(string $title) {
global $CFG;
if (CLI_SCRIPT) {
require_once($CFG->libdir . '/clilib.php');
$datetime = userdate(time(), '%b %d, %H:%M:%S');
cli_set_process_title_suffix("$datetime $title");
}
}
/**
* Output some standard information during cron runs. Specifically current time
* and memory usage. This method also does gc_collect_cycles() (before displaying
* memory usage) to try to help PHP manage memory better.
*/
function cron_trace_time_and_memory() {
gc_collect_cycles();
mtrace('... started ' . date('H:i:s') . '. Current memory use ' . display_size(memory_get_usage()) . '.');
}
/**
* Prepare the output renderer for the cron run.
*
* This involves creating a new $PAGE, and $OUTPUT fresh for each task and prevents any one task from influencing
* any other.
*
* @param bool $restore Whether to restore the original PAGE and OUTPUT
*/
function cron_prepare_core_renderer($restore = false) {
global $OUTPUT, $PAGE;
// Store the original PAGE and OUTPUT values so that they can be reset at a later point to the original.
// This should not normally be required, but may be used in places such as the scheduled task tool's "Run now"
// functionality.
static $page = null;
static $output = null;
if (null === $page) {
$page = $PAGE;
}
if (null === $output) {
$output = $OUTPUT;
}
if (!empty($restore)) {
$PAGE = $page;
$page = null;
$OUTPUT = $output;
$output = null;
} else {
// Setup a new General renderer.
// Cron tasks may produce output to be used in web, so we must use the appropriate renderer target.
// This allows correct use of templates, etc.
$PAGE = new \moodle_page();
$OUTPUT = new \core_renderer($PAGE, RENDERER_TARGET_GENERAL);
}
}
debugging(
'Inclusion of lib/cronlib.php is no longer required. ' .
'Please update your code to use the new \\core\\cron class.',
DEBUG_DEVELOPER
);
+124 -1
View File
@@ -2838,7 +2838,7 @@ function cron_execute_plugin_type($plugintype, $description = null) {
mtrace('Processing cron function for ' . $component . '...');
debugging("Use of legacy cron is deprecated ($cronfunction). Please use scheduled tasks.", DEBUG_DEVELOPER);
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$pre_dbqueries = $DB->perf_get_queries();
$pre_time = microtime(true);
@@ -3671,3 +3671,126 @@ function print_error($errorcode, $module = 'error', $link = '', $a = null, $debu
"Please throw a new moodle_exception instance instead.", DEBUG_DEVELOPER);
throw new \moodle_exception($errorcode, $module, $link, $a, $debuginfo);
}
/**
* Execute cron tasks
*
* @param int|null $keepalive The keepalive time for this cron run.
* @deprecated since 4.2 Use \core\cron::run_main_process() instead.
*/
function cron_run(?int $keepalive = null): void {
debugging(
'The cron_run() function is deprecated. Please use \core\cron::run_main_process() instead.',
DEBUG_DEVELOPER
);
\core\cron::run_main_process($keepalive);
}
/**
* Execute all queued scheduled tasks, applying necessary concurrency limits and time limits.
*
* @param int $timenow The time this process started.
* @deprecated since 4.2 Use \core\cron::run_scheduled_tasks() instead.
*/
function cron_run_scheduled_tasks(int $timenow) {
debugging(
'The cron_run_scheduled_tasks() function is deprecated. Please use \core\cron::run_scheduled_tasks() instead.',
DEBUG_DEVELOPER
);
\core\cron::run_scheduled_tasks($timenow);
}
/**
* Execute all queued adhoc tasks, applying necessary concurrency limits and time limits.
*
* @param int $timenow The time this process started.
* @param int $keepalive Keep this function alive for N seconds and poll for new adhoc tasks.
* @param bool $checklimits Should we check limits?
* @deprecated since 4.2 Use \core\cron::run_adhoc_tasks() instead.
*/
function cron_run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true) {
debugging(
'The cron_run_adhoc_tasks() function is deprecated. Please use \core\cron::run_adhoc_tasks() instead.',
DEBUG_DEVELOPER
);
\core\cron::run_adhoc_tasks($timenow, $keepalive, $checklimits);
}
/**
* Shared code that handles running of a single scheduled task within the cron.
*
* Not intended for calling directly outside of this library!
*
* @param \core\task\task_base $task
* @deprecated since 4.2 Use \core\cron::run_inner_scheduled_task() instead.
*/
function cron_run_inner_scheduled_task(\core\task\task_base $task) {
debugging(
'The cron_run_inner_scheduled_task() function is deprecated. Please use \core\cron::run_inner_scheduled_task() instead.',
DEBUG_DEVELOPER
);
\core\cron::run_inner_scheduled_task($task);
}
/**
* Shared code that handles running of a single adhoc task within the cron.
*
* @param \core\task\adhoc_task $task
* @deprecated since 4.2 Use \core\cron::run_inner_adhoc_task() instead.
*/
function cron_run_inner_adhoc_task(\core\task\adhoc_task $task) {
debugging(
'The cron_run_inner_adhoc_task() function is deprecated. Please use \core\cron::run_inner_adhoc_task() instead.',
DEBUG_DEVELOPER
);
\core\cron::run_inner_adhoc_task($task);
}
/**
* Sets the process title
*
* This makes it very easy for a sysadmin to immediately see what task
* a cron process is running at any given moment.
*
* @param string $title process status title
* @deprecated since 4.2 Use \core\cron::set_process_title() instead.
*/
function cron_set_process_title(string $title) {
debugging(
'The cron_set_process_title() function is deprecated. Please use \core\cron::set_process_title() instead.',
DEBUG_DEVELOPER
);
\core\cron::set_process_title($title);
}
/**
* Output some standard information during cron runs. Specifically current time
* and memory usage. This method also does gc_collect_cycles() (before displaying
* memory usage) to try to help PHP manage memory better.
*
* @deprecated since 4.2 Use \core\cron::trace_time_and_memory() instead.
*/
function cron_trace_time_and_memory() {
debugging(
'The cron_trace_time_and_memory() function is deprecated. Please use \core\cron::trace_time_and_memory() instead.',
DEBUG_DEVELOPER
);
\core\cron::trace_time_and_memory();
}
/**
* Prepare the output renderer for the cron run.
*
* This involves creating a new $PAGE, and $OUTPUT fresh for each task and prevents any one task from influencing
* any other.
*
* @param bool $restore Whether to restore the original PAGE and OUTPUT
* @deprecated since 4.2 Use \core\cron::prepare_core_renderer() instead.
*/
function cron_prepare_core_renderer($restore = false) {
debugging(
'The cron_prepare_core_renderer() function is deprecated. Please use \core\cron::prepare_core_renderer() instead.',
DEBUG_DEVELOPER
);
\core\cron::prepare_core_renderer($restore);
}
+4 -5
View File
@@ -2250,12 +2250,11 @@ class file_storage {
*/
public function cron() {
global $CFG, $DB;
require_once($CFG->libdir.'/cronlib.php');
// find out all stale draft areas (older than 4 days) and purge them
// those are identified by time stamp of the /. root dir
mtrace('Deleting old draft files... ', '');
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$old = time() - 60*60*24*4;
$sql = "SELECT *
FROM {files}
@@ -2272,7 +2271,7 @@ class file_storage {
// * preview files in the core preview filearea without the existing original file.
// * document converted files in core documentconversion filearea without the existing original file.
mtrace('Deleting orphaned preview, and document conversion files... ', '');
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$sql = "SELECT p.*
FROM {files} p
LEFT JOIN {files} o ON (p.filename = o.contenthash)
@@ -2299,7 +2298,7 @@ class file_storage {
require_once($CFG->libdir.'/filelib.php');
// Delete files that are associated with a context that no longer exists.
mtrace('Cleaning up files from deleted contexts... ', '');
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$sql = "SELECT DISTINCT f.contextid
FROM {files} f
LEFT OUTER JOIN {context} c ON f.contextid = c.id
@@ -2315,7 +2314,7 @@ class file_storage {
mtrace('done.');
mtrace('Call filesystem cron tasks.', '');
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$this->filesystem->cron();
mtrace('done.');
}
+2 -3
View File
@@ -689,8 +689,7 @@ abstract class advanced_testcase extends base_testcase {
* @param int $matchuserid The userid to match.
*/
protected function runAdhocTasks($matchclass = '', $matchuserid = null) {
global $CFG, $DB;
require_once($CFG->libdir.'/cronlib.php');
global $DB;
$params = [];
if (!empty($matchclass)) {
@@ -729,7 +728,7 @@ abstract class advanced_testcase extends base_testcase {
$task->set_cron_lock($cronlock);
}
cron_prepare_core_renderer();
\core\cron::prepare_core_renderer();
$this->setUser($user);
$task->execute();
+8 -49
View File
@@ -176,61 +176,20 @@ function get_moodle_cookie() {
* value 'reset' means reset internal static caches.
* @param stdClass $course full course record, null means $SITE
* @param bool $leavepagealone If specified, stops it messing with global page object
* @deprecated since 4.2. Use \core\core::setup_user() instead.
* @return void
*/
function cron_setup_user($user = null, $course = null, $leavepagealone = false) {
global $CFG, $SITE, $PAGE;
if (!CLI_SCRIPT && !$leavepagealone) {
throw new coding_exception('Function cron_setup_user() cannot be used in normal requests!');
}
static $cronuser = NULL;
static $cronsession = NULL;
debugging(
'The cron_setup_user() function is deprecated. ' .
'Please use \core\cron::setup_user() and reset_user_cache() as appropriate instead.',
DEBUG_DEVELOPER
);
if ($user === 'reset') {
$cronuser = null;
$cronsession = null;
\core\session\manager::init_empty_session();
\core\cron::reset_user_cache();
return;
}
if (empty($cronuser)) {
/// ignore admins timezone, language and locale - use site default instead!
$cronuser = get_admin();
$cronuser->timezone = $CFG->timezone;
$cronuser->lang = '';
$cronuser->theme = '';
unset($cronuser->description);
$cronsession = new stdClass();
}
if (!$user) {
// Cached default cron user (==modified admin for now).
\core\session\manager::init_empty_session();
\core\session\manager::set_user($cronuser);
$GLOBALS['SESSION'] = $cronsession;
} else {
// Emulate real user session - needed for caps in cron.
if ($GLOBALS['USER']->id != $user->id) {
\core\session\manager::init_empty_session();
\core\session\manager::set_user($user);
}
}
// TODO MDL-19774 relying on global $PAGE in cron is a bad idea.
// Temporary hack so that cron does not give fatal errors.
if (!$leavepagealone) {
$PAGE = new moodle_page();
if ($course) {
$PAGE->set_course($course);
} else {
$PAGE->set_course($SITE);
}
}
// TODO: it should be possible to improve perf by caching some limited number of users here ;-)
\core\cron::setup_user($user, $course, $leavepagealone);
}
+4 -4
View File
@@ -164,7 +164,7 @@ function stats_cron_daily($maxdays=1) {
$defaultfproleid = (int)$CFG->defaultfrontpageroleid;
mtrace("Running daily statistics gathering, starting at $timestart:");
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$days = 0;
$total = 0;
@@ -664,7 +664,7 @@ function stats_cron_weekly() {
$DB->delete_records_select('stats_user_weekly', "timeend > $timestart");
mtrace("Running weekly statistics gathering, starting at $timestart:");
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$weeks = 0;
while ($now > $nextstartweek) {
@@ -807,7 +807,7 @@ function stats_cron_monthly() {
mtrace("Running monthly statistics gathering, starting at $timestart:");
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$months = 0;
while ($now > $nextstartmonth) {
@@ -1068,7 +1068,7 @@ function stats_get_next_month_start($time) {
function stats_clean_old() {
global $DB;
mtrace("Running stats cleanup tasks...");
cron_trace_time_and_memory();
\core\cron::trace_time_and_memory();
$deletebefore = stats_get_base_monthly();
// delete dailies older than 3 months (to be safe)
+7 -11
View File
@@ -1108,9 +1108,6 @@ EOF;
* @param string $taskname Name of task e.g. 'mod_whatever\task\do_something'
*/
public function i_run_the_scheduled_task($taskname) {
global $CFG;
require_once("{$CFG->libdir}/cronlib.php");
$task = \core\task\manager::get_scheduled_task($taskname);
if (!$task) {
throw new DriverException('The "' . $taskname . '" scheduled task does not exist');
@@ -1118,7 +1115,7 @@ EOF;
// Do setup for cron task.
raise_memory_limit(MEMORY_EXTRA);
cron_setup_user();
\core\cron::setup_user();
// Get lock.
$cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
@@ -1138,7 +1135,7 @@ EOF;
try {
// Prepare the renderer.
cron_prepare_core_renderer();
\core\cron::prepare_core_renderer();
// Discard task output as not appropriate for Behat output!
ob_start();
@@ -1146,13 +1143,13 @@ EOF;
ob_end_clean();
// Restore the previous renderer.
cron_prepare_core_renderer(true);
\core\cron::prepare_core_renderer(true);
// Mark task complete.
\core\task\manager::scheduled_task_complete($task);
} catch (Exception $e) {
// Restore the previous renderer.
cron_prepare_core_renderer(true);
\core\cron::prepare_core_renderer(true);
// Mark task failed and throw exception.
\core\task\manager::scheduled_task_failed($task);
@@ -1175,11 +1172,10 @@ EOF;
* @throws DriverException
*/
public function i_run_all_adhoc_tasks() {
global $CFG, $DB;
require_once("{$CFG->libdir}/cronlib.php");
global $DB;
// Do setup for cron task.
cron_setup_user();
\core\cron::setup_user();
// Discard task output as not appropriate for Behat output!
ob_start();
@@ -1193,7 +1189,7 @@ EOF;
ob_clean();
// Run the task.
cron_run_inner_adhoc_task($task);
\core\cron::run_inner_adhoc_task($task);
// Check whether the task record still exists.
// If a task was successful it will be removed.
+159
View File
@@ -0,0 +1,159 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
/**
* Tests for core\cron.
*
* @package core
* @copyright 2023 Andrew Nicols <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core\cron
*/
class cron_test extends \advanced_testcase {
/**
* Reset relevant caches between tests.
*/
public function setUp(): void {
cron::reset_user_cache();
}
/**
* Test the setup_user function.
*
* @covers ::setup_user
* @covers ::reset_user_cache
*/
public function test_setup_user(): void {
// This function uses the $GLOBALS super global. Disable the VariableNameLowerCase sniff for this function.
// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase
global $PAGE, $USER, $SESSION, $SITE, $CFG;
$this->resetAfterTest();
$admin = get_admin();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
cron::setup_user();
$this->assertSame($admin->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertSame($CFG->timezone, $USER->timezone);
$this->assertSame('', $USER->lang);
$this->assertSame('', $USER->theme);
$SESSION->test1 = true;
$adminsession = $SESSION;
$adminuser = $USER;
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user(null, $course);
$this->assertSame($admin->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($course->id));
$this->assertSame($adminsession, $SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user($user1);
$this->assertSame($user1->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertObjectNotHasAttribute('test1', $SESSION);
$this->assertEmpty((array)$SESSION);
$usersession1 = $SESSION;
$SESSION->test2 = true;
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user($user1);
$this->assertSame($user1->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertSame($usersession1, $SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user($user2);
$this->assertSame($user2->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($usersession1, $SESSION);
$this->assertEmpty((array)$SESSION);
$usersession2 = $SESSION;
$usersession2->test3 = true;
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user($user2, $course);
$this->assertSame($user2->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($course->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($usersession1, $SESSION);
$this->assertSame($usersession2, $SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user($user1);
$this->assertSame($user1->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($usersession1, $SESSION);
$this->assertEmpty((array)$SESSION);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user();
$this->assertSame($admin->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertSame($adminsession, $SESSION);
$this->assertSame($adminuser, $USER);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::reset_user_cache();
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron::setup_user();
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($adminuser, $USER);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
// phpcs:enable
}
}
+20
View File
@@ -26,12 +26,20 @@ namespace core;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sessionlib_test extends \advanced_testcase {
/**
* @covers ::cron_setup_user
*/
public function test_cron_setup_user() {
// This function uses the $GLOBALS super global. Disable the VariableNameLowerCase sniff for this function.
// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase
global $PAGE, $USER, $SESSION, $SITE, $CFG;
$this->resetAfterTest();
// NOTE: this function contains some static caches, let's reset first.
cron_setup_user('reset');
$this->assertDebuggingCalledCount(1);
$admin = get_admin();
$user1 = $this->getDataGenerator()->create_user();
@@ -39,6 +47,7 @@ class sessionlib_test extends \advanced_testcase {
$course = $this->getDataGenerator()->create_course();
cron_setup_user();
$this->assertDebuggingCalledCount(1);
$this->assertSame($admin->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertSame($CFG->timezone, $USER->timezone);
@@ -53,6 +62,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user(null, $course);
$this->assertDebuggingCalledCount(1);
$this->assertSame($admin->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($course->id));
$this->assertSame($adminsession, $SESSION);
@@ -62,6 +72,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user($user1);
$this->assertDebuggingCalledCount(1);
$this->assertSame($user1->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
@@ -75,6 +86,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user($user1);
$this->assertDebuggingCalledCount(1);
$this->assertSame($user1->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
@@ -85,6 +97,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user($user2);
$this->assertDebuggingCalledCount(1);
$this->assertSame($user2->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
@@ -98,6 +111,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user($user2, $course);
$this->assertDebuggingCalledCount(1);
$this->assertSame($user2->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($course->id));
$this->assertNotSame($adminsession, $SESSION);
@@ -109,6 +123,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user($user1);
$this->assertDebuggingCalledCount(1);
$this->assertSame($user1->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertNotSame($adminsession, $SESSION);
@@ -120,6 +135,7 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user();
$this->assertDebuggingCalledCount(1);
$this->assertSame($admin->id, $USER->id);
$this->assertSame($PAGE->context, \context_course::instance($SITE->id));
$this->assertSame($adminsession, $SESSION);
@@ -130,18 +146,22 @@ class sessionlib_test extends \advanced_testcase {
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user('reset');
$this->assertDebuggingCalledCount(1);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
cron_setup_user();
$this->assertDebuggingCalledCount(1);
$this->assertNotSame($adminsession, $SESSION);
$this->assertNotSame($adminuser, $USER);
$this->assertSame($GLOBALS['SESSION'], $_SESSION['SESSION']);
$this->assertSame($GLOBALS['SESSION'], $SESSION);
$this->assertSame($GLOBALS['USER'], $_SESSION['USER']);
$this->assertSame($GLOBALS['USER'], $USER);
// phpcs:enable
}
/**
-1
View File
@@ -21,7 +21,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/statslib.php');
require_once($CFG->libdir . '/cronlib.php');
require_once(__DIR__ . '/fixtures/stats_events.php');
/**
@@ -14,22 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir.'/cronlib.php');
namespace core\task;
/**
* Unit tests for the cron.
* Unit tests for the file_temp_cleanup task.
*
* @package core
* @category test
* @copyright 2013 Tim Gusak <tim.gusak@remote-learner.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core\task\file_temp_cleanup_task
*/
class cronlib_test extends \basic_testcase {
class file_temp_cleanup_task_test extends \basic_testcase {
/**
* Data provider for cron_delete_from_temp.
@@ -144,6 +140,7 @@ class cronlib_test extends \basic_testcase {
* @dataProvider cron_delete_from_temp_provider
* @param array $nodes List of files and directories
* @param array $expected The expected results
* @covers ::execute
*/
public function test_cron_delete_from_temp($nodes, $expected) {
global $CFG;
+1
View File
@@ -91,6 +91,7 @@ information provided here is intended especially for developers.
- groups_get_members()
- groups_get_groups_members()
groups_print_activity_menu() will now only return groups where particiation == true.
* Cron utility functions have been moved from the global namespaces to the \core\cron class.
=== 4.1 ===
@@ -73,7 +73,7 @@ class send_email_task extends scheduled_task {
// Keep track of which emails failed to send.
$users = $this->get_unique_users();
foreach ($users as $user) {
cron_setup_user($user);
\core\cron::setup_user($user);
$hascontent = false;
$renderable = new \message_email\output\email_digest($user);
@@ -99,7 +99,7 @@ class send_email_task extends scheduled_task {
}
}
}
cron_setup_user();
\core\cron::setup_user();
$users->close();
}
@@ -344,7 +344,7 @@ class feedback_test extends \advanced_testcase {
public function test_conversion_task() {
$this->require_ghostscript();
$this->resetAfterTest();
cron_setup_user();
\core\cron::setup_user();
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+2 -2
View File
@@ -2744,7 +2744,7 @@ class assign {
// Override the language and timezone of the "current" user, so that
// mail is customised for the receiver.
cron_setup_user($user, $course);
\core\cron::setup_user($user, $course);
// Context lookups are already cached.
$coursecontext = context_course::instance($course->id);
@@ -2824,7 +2824,7 @@ class assign {
}
mtrace('Done processing ' . count($submissions) . ' assignment submissions');
cron_setup_user();
\core\cron::setup_user();
// Free up memory just to be sure.
unset($courses);
+12 -12
View File
@@ -1562,7 +1562,7 @@ class locallib_test extends \advanced_testcase {
$this->resetAfterTest();
// First run cron so there are no messages waiting to be sent (from other tests).
cron_setup_user();
\core\cron::setup_user();
\assign::cron();
$course = $this->getDataGenerator()->create_course();
@@ -1580,7 +1580,7 @@ class locallib_test extends \advanced_testcase {
$this->mark_submission($teacher, $assign, $student, 50.0);
$this->expectOutputRegex('/Done processing 1 assignment submissions/');
cron_setup_user();
\core\cron::setup_user();
$sink = $this->redirectMessages();
\assign::cron();
$messages = $sink->get_messages();
@@ -1604,7 +1604,7 @@ class locallib_test extends \advanced_testcase {
$this->resetAfterTest();
// First run cron so there are no messages waiting to be sent (from other tests).
cron_setup_user();
\core\cron::setup_user();
\assign::cron();
$course = $this->getDataGenerator()->create_course();
@@ -1623,7 +1623,7 @@ class locallib_test extends \advanced_testcase {
'sendstudentnotifications' => 0,
]);
cron_setup_user();
\core\cron::setup_user();
$sink = $this->redirectMessages();
\assign::cron();
$messages = $sink->get_messages();
@@ -1635,7 +1635,7 @@ class locallib_test extends \advanced_testcase {
$this->resetAfterTest();
// First run cron so there are no messages waiting to be sent (from other tests).
cron_setup_user();
\core\cron::setup_user();
\assign::cron();
$course = $this->getDataGenerator()->create_course();
@@ -1653,14 +1653,14 @@ class locallib_test extends \advanced_testcase {
$this->mark_submission($teacher, $assign, $student, 50.0);
$this->expectOutputRegex('/Done processing 1 assignment submissions/');
cron_setup_user();
\core\cron::setup_user();
\assign::cron();
// Regrade.
$this->mark_submission($teacher, $assign, $student, 50.0);
$this->expectOutputRegex('/Done processing 1 assignment submissions/');
cron_setup_user();
\core\cron::setup_user();
$sink = $this->redirectMessages();
\assign::cron();
$messages = $sink->get_messages();
@@ -1677,7 +1677,7 @@ class locallib_test extends \advanced_testcase {
$this->resetAfterTest();
// First run cron so there are no messages waiting to be sent (from other tests).
cron_setup_user();
\core\cron::setup_user();
\assign::cron();
$course = $this->getDataGenerator()->create_course();
@@ -1700,7 +1700,7 @@ class locallib_test extends \advanced_testcase {
'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
]);
cron_setup_user();
\core\cron::setup_user();
$sink = $this->redirectMessages();
\assign::cron();
$messages = $sink->get_messages();
@@ -1714,7 +1714,7 @@ class locallib_test extends \advanced_testcase {
$assign->testable_apply_grade_to_user($submission, $student->id, 0);
// Now run cron and see that one message was sent.
cron_setup_user();
\core\cron::setup_user();
$sink = $this->redirectMessages();
$this->expectOutputRegex('/Done processing 1 assignment submissions/');
\assign::cron();
@@ -1729,7 +1729,7 @@ class locallib_test extends \advanced_testcase {
$this->resetAfterTest();
// First run cron so there are no messages waiting to be sent (from other tests).
cron_setup_user();
\core\cron::setup_user();
\assign::cron();
$course = $this->getDataGenerator()->create_course();
@@ -1750,7 +1750,7 @@ class locallib_test extends \advanced_testcase {
\phpunit_util::stop_message_redirection();
// Now run cron and see that one message was sent.
cron_setup_user();
\core\cron::setup_user();
$this->preventResetByRollback();
$sink = $this->redirectEvents();
$this->expectOutputRegex('/Done processing 1 assignment submissions/');
@@ -183,7 +183,7 @@ abstract class send_notification extends adhoc_task {
foreach ($this->get_recipients() as $recipient) {
try {
\core_user::require_active_user($recipient, true, true);
cron_setup_user($recipient);
\core\cron::setup_user($recipient);
} catch (moodle_exception $e) {
// Skip sending.
continue;
@@ -192,7 +192,7 @@ abstract class send_notification extends adhoc_task {
$this->send_notification_to_current_user();
}
cron_setup_user();
\core\cron::setup_user();
}
/**
+1 -1
View File
@@ -329,7 +329,7 @@ class cron_task extends \core\task\scheduled_task {
$send = false;
// Setup this user so that the capabilities are cached, and environment matches receiving user.
cron_setup_user($user);
\core\cron::setup_user($user);
list($individualpostdata, $digestpostdata) = $this->fetch_posts_for_user($user);
+1 -1
View File
@@ -36,7 +36,7 @@ trait mod_forum_tests_cron_trait {
// Note, we cannot use expectOutputRegex because it only allows for a single RegExp.
ob_start();
cron_setup_user();
\core\cron::setup_user();
$cron = new \mod_forum\task\cron_task();
$cron->execute();
$output = ob_get_contents();
@@ -54,7 +54,7 @@ class scheduled_allocator_test extends \advanced_testcase {
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
cron_setup_user();
\core\cron::setup_user();
// Let the students add submissions.
$this->workshop->switch_phase(\workshop::PHASE_SUBMISSION);
@@ -94,7 +94,7 @@ class scheduled_allocator_test extends \advanced_testcase {
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
cron_setup_user();
\core\cron::setup_user();
// Let the students add submissions.
$this->workshop->switch_phase(\workshop::PHASE_SUBMISSION);
-1
View File
@@ -32,7 +32,6 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/workshop/lib.php'); // Include the code to test.
require_once($CFG->dirroot . '/mod/workshop/locallib.php'); // Include the code to test.
require_once($CFG->dirroot . '/lib/cronlib.php'); // Include the code to test.
require_once(__DIR__ . '/../fixtures/testable.php');
+2 -2
View File
@@ -60,7 +60,7 @@ class cron_task_test extends \advanced_testcase {
// Execute the cron.
ob_start();
cron_setup_user();
\core\cron::setup_user();
$cron = new \mod_workshop\task\cron_task();
$cron->execute();
$output = ob_get_contents();
@@ -96,7 +96,7 @@ class cron_task_test extends \advanced_testcase {
// Execute the cron.
$eventsink = $this->redirectEvents();
ob_start();
cron_setup_user();
\core\cron::setup_user();
$cron = new \mod_workshop\task\cron_task();
$cron->execute();
ob_end_clean();
+4 -4
View File
@@ -75,10 +75,10 @@ class send_schedule extends adhoc_task {
// Handle schedule configuration as to who the report should be viewed as.
if ($scheduleuserviewas === schedule::REPORT_VIEWAS_CREATOR) {
cron_setup_user(core_user::get_user($schedule->get('usercreated')));
\core\cron::setup_user(core_user::get_user($schedule->get('usercreated')));
$scheduleattachment = helper::get_schedule_report_file($schedule);
} else if ($scheduleuserviewas !== schedule::REPORT_VIEWAS_RECIPIENT) {
cron_setup_user(core_user::get_user($scheduleuserviewas));
\core\cron::setup_user(core_user::get_user($scheduleuserviewas));
$scheduleattachment = helper::get_schedule_report_file($schedule);
}
@@ -98,7 +98,7 @@ class send_schedule extends adhoc_task {
if ($scheduleattachment !== null) {
helper::send_schedule_message($schedule, $user, $scheduleattachment);
} else {
cron_setup_user($user);
\core\cron::setup_user($user);
if ($schedulereportempty === schedule::REPORT_EMPTY_DONT_SEND &&
helper::get_schedule_report_count($schedule) === 0) {
@@ -123,6 +123,6 @@ class send_schedule extends adhoc_task {
$this->log_finish('Sending schedule complete');
// Restore cron user to original state.
cron_setup_user($originaluser);
\core\cron::setup_user($originaluser);
}
}
+1 -1
View File
@@ -306,7 +306,7 @@ abstract class engine {
$now = manager::get_current_time();
if ($now - $lastprogress >= manager::DISPLAY_INDEXING_PROGRESS_EVERY) {
$lastprogress = $now;
// The first date format is the same used in cron_trace_time_and_memory().
// The first date format is the same used in \core\cron::trace_time_and_memory().
$options['progress']->output(date('H:i:s', (int)$now) . ': Done to ' . userdate(
$lastindexeddoc, get_string('strftimedatetimeshort', 'langconfig')), 1);
}
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2023031400.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2023031400.02; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.2dev+ (Build: 20230314)'; // Human-friendly version name