Skip to content

Commit

Permalink
MDL-67211 Tasks: Record when a task is running.
Browse files Browse the repository at this point in the history
Co-authored-by: Sam Marshall <s.marshall@open.ac.uk>
  • Loading branch information
golenkovm and sammarshallou committed Aug 25, 2020
1 parent a04309d commit b465a54
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 6 deletions.
3 changes: 3 additions & 0 deletions admin/classes/task_log_table.php
Expand Up @@ -57,6 +57,8 @@ public function __construct(string $filter = '', int $resultfilter = null) {
'userid' => get_string('user', 'admin'),
'timestart' => get_string('task_starttime', 'admin'),
'duration' => get_string('task_duration', 'admin'),
'hostname' => get_string('hostname', 'tool_task'),
'pid' => get_string('pid', 'tool_task'),
'db' => get_string('task_dbstats', 'admin'),
'result' => get_string('task_result', 'admin'),
'actions' => '',
Expand Down Expand Up @@ -132,6 +134,7 @@ public function query_db($pagesize, $useinitialsbar = true) {

$sql = "SELECT
tl.id, tl.type, tl.component, tl.classname, tl.userid, tl.timestart, tl.timeend,
tl.hostname, tl.pid,
tl.dbreads, tl.dbwrites, tl.result,
tl.dbreads + tl.dbwrites AS db,
tl.timeend - tl.timestart AS duration,
Expand Down
2 changes: 2 additions & 0 deletions admin/cli/scheduled_task.php
Expand Up @@ -121,6 +121,8 @@
exit(1);
}

\core\task\manager::scheduled_task_starting($task);

// Increase memory limit.
raise_memory_limit(MEMORY_EXTRA);

Expand Down
2 changes: 2 additions & 0 deletions admin/tool/task/lang/en/tool_task.php
Expand Up @@ -45,8 +45,10 @@
$string['enablerunnow_desc'] = 'Allows administrators to run a single scheduled task immediately, rather than waiting for it to run as scheduled. The feature requires \'Path to PHP CLI\' (pathtophp) to be set in System paths. The task runs on the web server, so you may wish to disable this feature to avoid potential performance issues.';
$string['faildelay'] = 'Fail delay';
$string['fromcomponent'] = 'From component: {$a}';
$string['hostname'] = 'Host name';
$string['lastruntime'] = 'Last run';
$string['nextruntime'] = 'Next run';
$string['pid'] = 'PID';
$string['plugindisabled'] = 'Plugin disabled';
$string['pluginname'] = 'Scheduled task configuration';
$string['resettasktodefaults'] = 'Reset task schedule to defaults';
Expand Down
2 changes: 2 additions & 0 deletions lib/classes/task/database_logger.php
Expand Up @@ -75,6 +75,8 @@ public static function store_log_for_task(task_base $task, string $logpath, bool
'dbwrites' => $dbwrites,
'result' => (int) $failed,
'output' => file_get_contents($logpath),
'hostname' => $task->get_hostname(),
'pid' => $task->get_pid(),
];

if (is_a($task, adhoc_task::class) && $userid = $task->get_userid()) {
Expand Down
146 changes: 141 additions & 5 deletions lib/classes/task/manager.php
Expand Up @@ -255,6 +255,9 @@ public static function record_from_scheduled_task($task) {
$record->dayofweek = $task->get_day_of_week();
$record->month = $task->get_month();
$record->disabled = $task->get_disabled();
$record->timestarted = $task->get_timestarted();
$record->hostname = $task->get_hostname();
$record->pid = $task->get_pid();

return $record;
}
Expand All @@ -276,6 +279,9 @@ public static function record_from_adhoc_task($task) {
$record->customdata = $task->get_custom_data_as_string();
$record->userid = $task->get_userid();
$record->timecreated = time();
$record->timestarted = $task->get_timestarted();
$record->hostname = $task->get_hostname();
$record->pid = $task->get_pid();

return $record;
}
Expand Down Expand Up @@ -313,6 +319,15 @@ public static function adhoc_task_from_record($record) {
if (isset($record->userid)) {
$task->set_userid($record->userid);
}
if (isset($record->timestarted)) {
$task->set_timestarted($record->timestarted);
}
if (isset($record->hostname)) {
$task->set_hostname($record->hostname);
}
if (isset($record->pid)) {
$task->set_pid($record->pid);
}

return $task;
}
Expand Down Expand Up @@ -367,6 +382,15 @@ public static function scheduled_task_from_record($record, $expandr = true) {
if (isset($record->disabled)) {
$task->set_disabled($record->disabled);
}
if (isset($record->timestarted)) {
$task->set_timestarted($record->timestarted);
}
if (isset($record->hostname)) {
$task->set_hostname($record->hostname);
}
if (isset($record->pid)) {
$task->set_pid($record->pid);
}

return $task;
}
Expand Down Expand Up @@ -709,6 +733,9 @@ public static function get_next_scheduled_task($timestart) {
*/
public static function adhoc_task_failed(adhoc_task $task) {
global $DB;
// Finalise the log output.
logmanager::finalise_log(true);

$delay = $task->get_fail_delay();

// Reschedule task with exponential fall off for failing tasks.
Expand All @@ -724,6 +751,9 @@ public static function adhoc_task_failed(adhoc_task $task) {
}

// Reschedule and then release the locks.
$task->set_timestarted();
$task->set_hostname();
$task->set_pid();
$task->set_next_run_time(time() + $delay);
$task->set_fail_delay($delay);
$record = self::record_from_adhoc_task($task);
Expand All @@ -734,9 +764,31 @@ public static function adhoc_task_failed(adhoc_task $task) {
$task->get_cron_lock()->release();
}
$task->get_lock()->release();
}

// Finalise the log output.
logmanager::finalise_log(true);
/**
* Records that a adhoc task is starting to run.
*
* @param adhoc_task $task Task that is starting
* @param int $time Start time (leave blank for now)
* @throws \dml_exception
* @throws \coding_exception
*/
public static function adhoc_task_starting(adhoc_task $task, int $time = 0) {
global $DB;
$pid = (int)getmypid();
$hostname = (string)gethostname();

if (empty($time)) {
$time = time();
}

$task->set_timestarted($time);
$task->set_hostname($hostname);
$task->set_pid($pid);

$record = self::record_from_adhoc_task($task);
$DB->update_record('task_adhoc', $record);
}

/**
Expand All @@ -749,6 +801,9 @@ public static function adhoc_task_complete(adhoc_task $task) {

// Finalise the log output.
logmanager::finalise_log();
$task->set_timestarted();
$task->set_hostname();
$task->set_pid();

// Delete the adhoc task record - it is finished.
$DB->delete_records('task_adhoc', array('id' => $task->get_id()));
Expand All @@ -768,6 +823,8 @@ public static function adhoc_task_complete(adhoc_task $task) {
*/
public static function scheduled_task_failed(scheduled_task $task) {
global $DB;
// Finalise the log output.
logmanager::finalise_log(true);

$delay = $task->get_fail_delay();

Expand All @@ -783,20 +840,24 @@ public static function scheduled_task_failed(scheduled_task $task) {
$delay = 86400;
}

$task->set_timestarted();
$task->set_hostname();
$task->set_pid();

$classname = self::get_canonical_class_name($task);

$record = $DB->get_record('task_scheduled', array('classname' => $classname));
$record->nextruntime = time() + $delay;
$record->faildelay = $delay;
$record->timestarted = null;
$record->hostname = null;
$record->pid = null;
$DB->update_record('task_scheduled', $record);

if ($task->is_blocking()) {
$task->get_cron_lock()->release();
}
$task->get_lock()->release();

// Finalise the log output.
logmanager::finalise_log(true);
}

/**
Expand All @@ -816,6 +877,34 @@ public static function clear_fail_delay(scheduled_task $task) {
$DB->update_record('task_scheduled', $record);
}

/**
* Records that a scheduled task is starting to run.
*
* @param scheduled_task $task Task that is starting
* @param int $time Start time (0 = current)
* @throws \dml_exception If the task doesn't exist
*/
public static function scheduled_task_starting(scheduled_task $task, int $time = 0) {
global $DB;
$pid = (int)getmypid();
$hostname = (string)gethostname();

if (!$time) {
$time = time();
}

$task->set_timestarted($time);
$task->set_hostname($hostname);
$task->set_pid($pid);

$classname = self::get_canonical_class_name($task);
$record = $DB->get_record('task_scheduled', ['classname' => $classname], '*', MUST_EXIST);
$record->timestarted = $time;
$record->hostname = $hostname;
$record->pid = $pid;
$DB->update_record('task_scheduled', $record);
}

/**
* This function indicates that a scheduled task was completed successfully and should be rescheduled.
*
Expand All @@ -826,13 +915,19 @@ public static function scheduled_task_complete(scheduled_task $task) {

// Finalise the log output.
logmanager::finalise_log();
$task->set_timestarted();
$task->set_hostname();
$task->set_pid();

$classname = self::get_canonical_class_name($task);
$record = $DB->get_record('task_scheduled', array('classname' => $classname));
if ($record) {
$record->lastruntime = time();
$record->faildelay = 0;
$record->nextruntime = $task->get_next_scheduled_time();
$record->timestarted = null;
$record->hostname = null;
$record->pid = null;

$DB->update_record('task_scheduled', $record);
}
Expand All @@ -844,6 +939,47 @@ public static function scheduled_task_complete(scheduled_task $task) {
$task->get_lock()->release();
}

/**
* Gets a list of currently-running tasks.
*
* @param string $sort Sorting method
* @return array Array of scheduled and adhoc tasks
* @throws \dml_exception
*/
public static function get_running_tasks($sort = ''): array {
global $DB;
if (empty($sort)) {
$sort = 'timestarted ASC, classname ASC';
}
$params = ['now1' => time(), 'now2' => time()];

$sql = "SELECT subquery.*
FROM (SELECT concat('s', ts.id) as uniqueid,
ts.id,
'scheduled' as type,
ts.classname,
(:now1 - ts.timestarted) as time,
ts.timestarted,
ts.hostname,
ts.pid
FROM {task_scheduled} ts
WHERE ts.timestarted IS NOT NULL
UNION ALL
SELECT concat('a', ta.id) as uniqueid,
ta.id,
'adhoc' as type,
ta.classname,
(:now2 - ta.timestarted) as time,
ta.timestarted,
ta.hostname,
ta.pid
FROM {task_adhoc} ta
WHERE ta.timestarted IS NOT NULL) subquery
ORDER BY " . $sort;

return $DB->get_records_sql($sql, $params);
}

/**
* This function is used to indicate that any long running cron processes should exit at the
* next opportunity and restart. This is because something (e.g. DB changes) has changed and
Expand Down
57 changes: 57 additions & 0 deletions lib/classes/task/task_base.php
Expand Up @@ -50,6 +50,15 @@ abstract class task_base {
/** @var int $nextruntime - When this task is due to run next */
private $nextruntime = 0;

/** @var int $timestarted - When this task was started */
private $timestarted = null;

/** @var string $hostname - Hostname where this task was started and PHP process ID */
private $hostname = null;

/** @var int $pid - PHP process ID that is running the task */
private $pid = null;

/**
* Set the current lock for this task.
* @param \core\lock\lock $lock
Expand Down Expand Up @@ -151,4 +160,52 @@ public function get_fail_delay() {
* Throw exceptions on errors (the job will be retried).
*/
public abstract function execute();

/**
* Setter for $timestarted.
* @param int $timestarted
*/
public function set_timestarted($timestarted = null) {
$this->timestarted = $timestarted;
}

/**
* Getter for $timestarted.
* @return int
*/
public function get_timestarted() {
return $this->timestarted;
}

/**
* Setter for $hostname.
* @param string $hostname
*/
public function set_hostname($hostname = null) {
$this->hostname = $hostname;
}

/**
* Getter for $hostname.
* @return string
*/
public function get_hostname() {
return $this->hostname;
}

/**
* Setter for $pid.
* @param int $pid
*/
public function set_pid($pid = null) {
$this->pid = $pid;
}

/**
* Getter for $pid.
* @return int
*/
public function get_pid() {
return $this->pid;
}
}
2 changes: 2 additions & 0 deletions lib/cronlib.php
Expand Up @@ -237,6 +237,7 @@ function cron_run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true)
function cron_run_inner_scheduled_task(\core\task\task_base $task) {
global $CFG, $DB;

\core\task\manager::scheduled_task_starting($task);
\core\task\logmanager::start_logging($task);

$fullname = $task->get_name() . ' (' . get_class($task) . ')';
Expand Down Expand Up @@ -295,6 +296,7 @@ function cron_run_inner_scheduled_task(\core\task\task_base $task) {
function cron_run_inner_adhoc_task(\core\task\adhoc_task $task) {
global $DB, $CFG;

\core\task\manager::adhoc_task_starting($task);
\core\task\logmanager::start_logging($task);

mtrace("Execute adhoc task: " . get_class($task));
Expand Down

0 comments on commit b465a54

Please sign in to comment.