Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Talk:Scheduled Tasks Proposal

From MoodleDocs

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) (unique) the function to call. Must be unique, as it will be used for the locking.
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)