Talk:Scheduled Tasks Proposal
Pseudo code proposal
This is just a rough idea to provoke thought:--Tim Hunt 11:06, 9 December 2009 (UTC)
<?php
function handle_cron($maxprocesstime) {
$timestart = time();
$runid = cron_record_starting_processing($timenow);
while (($timenow = time()) < $timestart + $maxprocesstime) {
try {
$task = cron_get_next_task($timenow);
cron_acquire_lock($task);
$next = $task->execute($timenow);
cron_record_task_success($task, $next, $timenow);
cron_release_lock($task);
} catch (Exception $e) {
cron_record_task_failure($task, $timenow, $e);
}
}
cron_record_ending_processing($runid, $timenow);
}
/**
* Instances of this class are rows in the scheduled_tasks table.
*/
abstract class scheduled_task {
protected $id;
protected $plugin; // = 'mod_quiz', 'qtype_multichoice'.
protected $function;
protected $lastruntime;
protected $nextscheduledtime;
protected $priority; // LOW, MEDIUM, HIGH, or, a number like nice 19, or something.
protected abstract function get_start_end_times($timenow);
protected abstract function schedule_next($timenow);
public function execute($timenow) {
$file = get_plugin_dir($this->plugin) . '/cron.php';
if (!is_readable($file)) {
throw new cron_exception('required file does not exist.');
}
if (include_once($file)) {
throw new cron_exception('required file could not be included.');
}
list($start, $end) = next_times($timenow);
$this->$function($start, $end);
return $this->schedule_next();
}
}
class one_off_task extends scheduled_task {
protected abstract function get_start_end_times($timenow) {
return array(null, $timenow);
}
protected abstract function schedule_next($timenow) {
return null;
}
}
class catchup_task extends scheduled_task {
protected $desiredinterval; // seconds
protected abstract function get_start_end_times($timenow) {
return array($lastruntime, $timenow);
}
protected abstract function schedule_next($timenow) {
return $timenow + $desiredinterval;
}
}
class every_day_task extends scheduled_task {
protected $swtichovertime; // Seconds after midnight.
protected $runtime; // Seconds after midnight.
// For now this code assumes $runtime > $swtichovertime, but that is just me
// being lazy. It can be fixed.
protected abstract function get_start_end_times($timenow) {
$midnight = get_midnight_before($this->nextscheduledtime);
$previousmidnight = get_previous_midnight($midnight);
$starttime = $previousmidnight + $swtichovertime;
$endtime = $midnight + $swtichovertime;
return array($starttime, $endtime);
}
protected abstract function schedule_next($timenow) {
$midnight = get_midnight_before($this->nextscheduledtime);
$nextmidnight = get_following_midnight($midnight);
return $nextmidnight + $this->runtime;
}
}
class weekly_task extends scheduled_task {
// ...
}
/* Approximate database tables.
scheduled_tasks
id AUTOINCREMENT
type varchar
plugin varchar
function varchar UNIQUE
lastruntime int
nextscheduledtime int
priority int
custom1 int
custom2 int
customextra text
scheduled_task_locks
id int auto
function fk unique
locktime int
*/
Proposed database changes
We will be implementing a scheduled tasks system in our product, but we need a couple of extra features. In the interest of interoperability and not reinventing cron too many times, here are the features that we need, and the database changes that would be required:
- plugins need to define multiple cron jobs on-the-fly (one of our plugins will allow the user to schedule several tasks with different schedules), and allow plugins to modify those tasks
- ability to specify that a task should run n times, or until [date]
For the first requirement, I'd like to add a taskcode field to the DB, to allow plugins to identify the different tasks that it creates. The callfunction would then be called with taskcode as an argument.
For the second requirement, I think that we can roll the onceoff_tasks table into the main tasks table, and just add extra fields for number of times remaining and/or end date. So onceoff tasks would just be a task with the number of runs remaining set to 1, nextruntime set to the time that it should run, and the cron fields set to (empty? or "*"?). With the taskcode field, we wouldn't need the customdata field -- the task can use the taskcode field to identify the task, and load customdata from its own table.
Here is my proposed table, with changes highlighted:
scheduled_tasks:
Field | Datatype | Comment |
id | integer | sequence |
plugintype | varchar(50) | plugintype - should match the path-style declarations in get_plugin_types (eg question/type, not qtype). Will be null for core tasks. |
pluginname | varchar(50) | name of the plugin. Will be null for core tasks. |
taskcode (new) | varchar(50) | a plugin-specific identifier for the task, used to differentiate between multiple tasks for the same plugin |
callfunction | varchar(200) |
the function to call. |
lastruntime | int(10) | unix timestamp |
nextruntime | int(10) | unix timestamp |
blocking | int(1) | 0 or 1 - whether this task, when running, blocks everything else from running. |
minute | varchar(25) | |
hour | varchar(25) | |
day | varchar(25) | |
month | varchar(25) | |
dayofweek | varchar(25) | |
runsremaining (new) | integer | Number of times that the task will run |
enddate (new) | integer | Task will not run after this date |
customised | integer(1) | 0 or 1 - whether this time differs from what is in code |
Since we can now have multiple tasks with the same callfunction, we can't enforce that it be unique. So we can either use (taskcode,callfunction) or (plugintype,pluginname,taskcode) as the key for locking.
Also, do we need a field to specify which file the callfunction is in?
--Hubert Chathi 14:45, 8 March 2011 (UTC)
- Looks reasonable. I wonder if taskname would be better than taskcode?--Tim Hunt 16:28, 8 March 2011 (UTC)
Yeah, I started with taskname, then thought taskcode might be better. I don't mind either way. --Hubert Chathi 16:38, 8 March 2011 (UTC)