Example #1
0
/**
 * Run scheduled tasks according to a cron spec.
 */
function elis_cron()
{
    global $CFG;
    require $CFG->dirroot . '/elis/core/lib/tasklib.php';
    $timenow = time();
    // get all tasks that are (over-)due
    $tasks = get_recordset_select('elis_scheduled_tasks', 'nextruntime <= ' . $timenow, 'nextruntime ASC');
    if (empty($tasks)) {
        return;
    }
    while ($task = rs_fetch_next_record($tasks)) {
        $starttime = microtime();
        mtrace("Running {$task->callfunction}({$task->taskname}) from {$task->plugin}...");
        if ($task->enddate !== null && $task->enddate < $timenow) {
            mtrace('* Cancelling task: past end date');
            delete_records('elis_scheduled_tasks', 'id', $task->id);
            continue;
        }
        // FIXME: check for blocking tasks
        // FIXME: check if task is locked
        // See if some other cron has already run the function while we were
        // doing something else -- if so, skip it.
        $nextrun = get_field('elis_scheduled_tasks', 'nextruntime', 'id', $task->id);
        if ($nextrun > $timenow) {
            mtrace('* Skipped (someone else already ran it)');
            continue;
        }
        // calculate the next run time
        $newtask = new stdClass();
        $newtask->id = $task->id;
        $newtask->lastruntime = time();
        $newtask->nextruntime = cron_next_run_time($newtask->lastruntime, (array) $task);
        // see if we have any runs left
        if ($task->runsremaining !== null) {
            $newtask->runsremaining = $task->runsremaining - 1;
            if ($newtask->runsremaining <= 0) {
                mtrace('* Cancelling task: no runs left');
                delete_records('elis_scheduled_tasks', 'id', $task->id);
            } else {
                update_record('elis_scheduled_tasks', $newtask);
            }
        } else {
            update_record('elis_scheduled_tasks', $newtask);
        }
        // load the file and call the function
        if ($task->callfile) {
            $callfile = $CFG->dirroot . $task->callfile;
            if (!is_readable($callfile)) {
                mtrace('* Skipped (file not found)');
                continue;
            }
            require_once $callfile;
        }
        call_user_func(unserialize($task->callfunction), $task->taskname);
        $difftime = microtime_diff($starttime, microtime());
        mtrace("* {$difftime} seconds");
    }
}
Example #2
0
        if ($nextrun != $job->nextrun) {
            log_info("Too late to run core {$job->callfunction}; skipping.");
            cron_free($job, $start);
            continue;
        }
        log_info("Running core cron " . $job->callfunction);
        $function = $job->callfunction;
        try {
            $function();
        } catch (Exception $e) {
            log_message($e->getMessage(), LOG_LEVEL_WARN, true, true, $e->getFile(), $e->getLine(), $e->getTrace());
            $output = $e instanceof MaharaException ? $e->render_exception() : $e->getMessage();
            echo "{$output}\n";
            // Don't call handle_exception; try to update next run time and free the lock
        }
        $nextrun = cron_next_run_time($start, (array) $job);
        // update next run time
        set_field('cron', 'nextrun', db_format_timestamp($nextrun), 'id', $job->id);
        cron_free($job, $start);
        $now = $fake ? time() - ($realstart - $start) : time();
    }
}
$finish = time();
//Time relative to fake cron time
if (isset($argv[1])) {
    $diff = $realstart - $start;
    $finish = $finish - $diff;
}
log_info('---------- cron finished ' . date('r', $finish) . ' ----------');
function cron_next_run_time($lastrun, $job)
{
 /**
  * Validate that scheduled import is prevented if existing incomplete run exists.
  */
 public function test_importpreventmultipleimports()
 {
     global $CFG, $DB;
     require_once $CFG->dirroot . '/local/datahub/lib.php';
     require_once $CFG->dirroot . '/local/eliscore/lib/tasklib.php';
     $DB->delete_records('local_eliscore_sched_tasks');
     $DB->delete_records(RLIP_SCHEDULE_TABLE);
     $filepath = '/local_datahub_phpunit/';
     $filename = 'userfile2.csv';
     set_config('schedule_files_path', $filepath, 'dhimport_version1elis');
     set_config('user_schedule_file', $filename, 'dhimport_version1elis');
     // Set up the test directory.
     $testdir = $CFG->dataroot . $filepath;
     mkdir($testdir, 0777, true);
     copy(dirname(__FILE__) . "/fixtures/{$filename}", $testdir . $filename);
     // Create the job.
     $data = array('plugin' => 'dhimport_version1elis', 'period' => '5m', 'type' => 'dhimport');
     $taskid1 = rlip_schedule_add_job($data);
     // Run the import with a time in the past so it stops immediately.
     $taskname1 = $DB->get_field('local_eliscore_sched_tasks', 'taskname', array('id' => $taskid1));
     $result1 = run_ipjob($taskname1, -1);
     // Validate the first import run was started.
     $this->assertEquals(true, $result1);
     // Validate the state was saved.
     $config = $DB->get_field(RLIP_SCHEDULE_TABLE, 'config', array('id' => 1));
     $this->assertRegExp('/s:5:"state";/', $config);
     // Create a duplicate job.
     $taskid2 = rlip_schedule_add_job($data);
     // Get the initial duplicate job lastruntime and nextruntime values.
     $initlastruntime = $DB->get_field('local_eliscore_sched_tasks', 'lastruntime', array('id' => $taskid2));
     $initnextruntime = $DB->get_field('local_eliscore_sched_tasks', 'nextruntime', array('id' => $taskid2));
     // Emulate the ELIS cron adjusting the job run times.
     $task = $DB->get_record('local_eliscore_sched_tasks', array('id' => $taskid2));
     $task->lastruntime = time();
     $nextruntime = cron_next_run_time($task->lastruntime, (array) $task);
     $task->nextruntime = $nextruntime;
     $DB->update_record('local_eliscore_sched_tasks', $task);
     // Attempt to do another import run.
     $taskname2 = $DB->get_field('local_eliscore_sched_tasks', 'taskname', array('id' => $taskid2));
     $result2 = run_ipjob($taskname2);
     // Validate that the second import run attempt fails.
     $this->assertEquals(false, $result2);
     // Get the later lastruntime and nextruntime values.
     $lastruntime = $DB->get_field('local_eliscore_sched_tasks', 'lastruntime', array('id' => $taskid2));
     $nextruntime = $DB->get_field('local_eliscore_sched_tasks', 'nextruntime', array('id' => $taskid2));
     // Validate that the job run time values are back to initial values.
     $this->assertEquals($initlastruntime, $lastruntime);
     $this->assertEquals($initnextruntime, $nextruntime);
 }
Example #4
0
/**
 * Run scheduled tasks according to a cron spec.
 *
 * uses  $CFG, $DB
 */
function local_eliscore_cron()
{
    global $CFG, $DB;
    require $CFG->dirroot . '/local/eliscore/lib/tasklib.php';
    $timenow = time();
    // get all tasks that are (over-)due
    $params = array('timenow' => $timenow);
    $tasks = $DB->get_recordset_select('local_eliscore_sched_tasks', 'nextruntime <= :timenow', $params, 'nextruntime ASC');
    $numtasks = $DB->count_records_select('local_eliscore_sched_tasks', 'nextruntime <= :timenow', $params);
    // Check if the maximum cron run time is overridden
    $remtime = ELIS_TASKS_CRONSECS;
    if (isset($CFG->elistaskscronsecs) && is_int($CFG->elistaskscronsecs) && 0 < $CFG->elistaskscronsecs) {
        $remtime = $CFG->elistaskscronsecs;
    }
    if (empty($tasks) || !$tasks->valid()) {
        return;
    }
    foreach ($tasks as $task) {
        $starttime = microtime();
        mtrace("Running {$task->callfunction}({$task->taskname}) from {$task->plugin}...");
        if ($task->enddate !== null && $task->enddate < $timenow) {
            mtrace('* Cancelling task: past end date');
            $DB->delete_records('local_eliscore_sched_tasks', array('id' => $task->id));
            --$numtasks;
            continue;
        }
        // Check for blocking tasks.
        if (!empty($task->blocked) && $timenow < $task->blocked) {
            // Task is still running - do not start another instance of it.
            mtrace("{$task->plugin}: Previous {$task->taskname} process has not yet completed - aborting!");
            continue;
        }
        // FIXME: check if task is locked
        // See if some other cron has already run the function while we were
        // doing something else -- if so, skip it.
        $nextrun = $DB->get_field('local_eliscore_sched_tasks', 'nextruntime', array('id' => $task->id));
        if ($nextrun > $timenow) {
            mtrace('* Skipped (someone else already ran it)');
            --$numtasks;
            continue;
        }
        // calculate the next run time
        $newtask = new stdClass();
        $newtask->id = $task->id;
        $newtask->lastruntime = time();
        $newtask->nextruntime = cron_next_run_time($newtask->lastruntime, (array) $task);
        // see if we have any runs left
        if ($task->runsremaining !== null) {
            $newtask->runsremaining = $task->runsremaining - 1;
            if ($newtask->runsremaining <= 0) {
                mtrace('* Cancelling task: no runs left');
                $DB->delete_records('local_eliscore_sched_tasks', array('id' => $task->id));
            } else {
                $DB->update_record('local_eliscore_sched_tasks', $newtask);
            }
        } else {
            $DB->update_record('local_eliscore_sched_tasks', $newtask);
        }
        // load the file and call the function
        if ($task->callfile) {
            $callfile = $CFG->dirroot . $task->callfile;
            if (!is_readable($callfile)) {
                mtrace('* Skipped (file not found)');
                --$numtasks;
                continue;
            }
            require_once $callfile;
        }
        $starttask = time();
        $denom = $numtasks > 0 ? $numtasks-- : 1;
        // prevent div by 0
        $runtime = floor((double) $remtime / (double) $denom);
        call_user_func(unserialize($task->callfunction), $task->taskname, $runtime);
        $remtime -= time() - $starttask;
        $difftime = microtime_diff($starttime, microtime());
        mtrace("* {$difftime} seconds");
        // TBD: exit if over cron processing time
        if ($remtime <= 0) {
            break;
        }
    }
}
Example #5
0
 /**
  * Validate cron_next_run_time() function for tasks with '00' hour and/or minute
  * @param array $job array of cron task parameters
  * @param array $expnextrun the expected next run time
  * @dataProvider elis_tasks_cron_next_run_time_data
  */
 public function test_elis_tasks_cron_next_run_time($job, $expnextrun)
 {
     $lastrun = $job['lastruntime'];
     $this->assertEquals(make_timestamp($expnextrun[0], $expnextrun[1], $expnextrun[2], $expnextrun[3], $expnextrun[4]), cron_next_run_time(make_timestamp($lastrun[0], $lastrun[1], $lastrun[2], $lastrun[3], $lastrun[4]), $job));
 }
Example #6
0
 public function finish()
 {
     global $USER, $DB;
     $data = $this->unserialize_data(array());
     if (isset($data['schedule_user_id'])) {
         //userid was specifically persisted from the schedule record
         $userid = $data['schedule_user_id'];
     } else {
         //default to the current user
         $userid = $USER->id;
     }
     // Add timemodified to serialized data
     $data['timemodified'] = time();
     $serialized_data = serialize($data);
     // Save to php_report_schedule - id (auto), userid (Moodle userid), report (shortname), config($data plus time() <= currenttime)
     $schedule = new object();
     $schedule->userid = $userid;
     $schedule->report = $data['report'];
     $schedule->config = $serialized_data;
     if (isset($data['schedule_id'])) {
         $schedule->id = $data['schedule_id'];
         $DB->update_record('local_elisreports_schedule', $schedule);
         // Also find and remove any existing task record(s) for the old schedule
         $taskname = 'scheduled_' . $schedule->id;
         $DB->delete_records('local_eliscore_sched_tasks', array('taskname' => $taskname));
     } else {
         $schedule->id = $DB->insert_record('local_elisreports_schedule', $schedule);
     }
     // Save to scheduled_tasks
     $component = 'local_elisreports';
     $callfile = '/local/elisreports/runschedule.php';
     $callfunction = serialize('run_schedule');
     // Calculate nextruntime from schedule
     // Also calculate minute/hour/day/month/dayofweek
     $recurrencetype = $data['recurrencetype'];
     // Simple - hour/minute are from time modified
     if ($recurrencetype == scheduling_workflow::RECURRENCE_SIMPLE) {
         $minute = (int) strftime('%M', $data['timemodified']);
         $hour = (int) strftime('%H', $data['timemodified']);
         $day = '*';
         $month = '*';
         $dayofweek = '*';
     } else {
         // Calendar
         $minute = $data['schedule']['minute'];
         $hour = $data['schedule']['hour'];
         $day = $data['schedule']['day'];
         $month = $data['schedule']['month'];
         $dayofweek = $data['schedule']['dayofweek'];
     }
     $runsremaining = empty($data['schedule']['runsremaining']) ? null : $data['schedule']['runsremaining'];
     // thou startdate is checked in runschedule.php, the confirm form
     // would display an incorrect 'will run next at' time (eg. current time)
     // if we don't calculate it - see below ...
     $startdate = empty($data['startdate']) ? $data['timemodified'] : $data['startdate'];
     $enddate = empty($data['schedule']['enddate']) ? null : $data['schedule']['enddate'];
     $task = new object();
     $task->plugin = $component;
     $task->taskname = 'scheduled_' . $schedule->id;
     $task->callfile = $callfile;
     $task->callfunction = $callfunction;
     $task->lastruntime = 0;
     $task->blocking = 0;
     $task->minute = $minute;
     $task->hour = $hour;
     $task->day = $day;
     $task->month = $month;
     $task->dayofweek = $dayofweek;
     $task->timezone = $data['timezone'];
     $task->enddate = $enddate != null ? $enddate + DAYSECS - 1 : null;
     debug_error_log("schedulelib.php::finish() startdate = {$data['startdate']}; timemodified = {$data['timemodified']}");
     // NOTE: if startdate not set then it already got set to time()
     //       in get_submitted_values_for_step_schedule()
     //       in which case we DO NOT want to add current time of day!
     //       hence the messy check for: !(from_gmt() % DAYSECS)
     if (!empty($data['startdate']) && ($recurrencetype == scheduling_workflow::RECURRENCE_SIMPLE || $startdate < $data['timemodified']) && !(($orig_start = from_gmt($data['startdate'], $data['timezone'])) % DAYSECS)) {
         // they set a startdate, but, we should add current time of day!
         $time_offset = from_gmt($data['timemodified'], $data['timezone']);
         debug_error_log("schedulelib.php::finish() : time_offset = {$time_offset} - adjusting startdate = {$startdate} ({$orig_start}) => " . to_gmt($orig_start + $time_offset % DAYSECS, $data['timezone']));
         $startdate = to_gmt($orig_start + $time_offset % DAYSECS, $data['timezone']);
         /**
          * TBD: if startdate earlier than today ???
          **/
         while ($startdate < $data['timemodified']) {
             $startdate += DAYSECS;
             debug_error_log("schedulelib.php::finish() advancing startdate + day => {$startdate}");
         }
     }
     if ($recurrencetype == scheduling_workflow::RECURRENCE_SIMPLE) {
         $task->nextruntime = $startdate;
     } else {
         $task->nextruntime = cron_next_run_time($startdate - 100, (array) $task);
         // minus [arb. value] above from startdate required to not skip
         // first run! This was probably due to incorrect startdate calc
         // which is now corrected.
     }
     $task->runsremaining = $runsremaining;
     $DB->insert_record('local_eliscore_sched_tasks', $task);
 }
Example #7
0
/**
 * We can not removed all event handlers in table, then add them again
 * because event handlers could be referenced by queued items
 *
 * Note that the absence of the db/events.php event definition file
 * will cause any queued events for the component to be removed from
 * the database.
 *
 * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
 * @return boolean
 */
function elis_tasks_update_definition($component = 'moodle')
{
    // load event definition from events.php
    $filetasks = elis_tasks_load_def($component);
    // load event definitions from db tables
    // if we detect an event being already stored, we discard from this array later
    // the remaining needs to be removed
    $cachedtasks = elis_tasks_get_cached($component);
    foreach ($filetasks as $filetask) {
        // preprocess file task
        $filetask['blocking'] = empty($filetask['blocking']) ? 0 : 1;
        $filetask['minute'] = empty($filetask['minute']) ? '*' : $filetask['minute'];
        $filetask['hour'] = empty($filetask['hour']) ? '*' : $filetask['hour'];
        $filetask['day'] = empty($filetask['day']) ? '*' : $filetask['day'];
        $filetask['month'] = empty($filetask['month']) ? '*' : $filetask['month'];
        $filetask['dayofweek'] = empty($filetask['dayofweek']) ? '*' : $filetask['dayofweek'];
        $callfunction = serialize($filetask['callfunction']);
        if (!empty($cachedtasks[$callfunction])) {
            $cachedtask =& $cachedtasks[$callfunction];
            if ($cachedtask['customized']) {
                // task is customized by the administrator, so don't change it
                unset($cachedtask[$callfunction]);
                continue;
            }
            if ($cachedtask['callfile'] == $filetask['callfile'] && $cachedtask['blocking'] == $filetask['blocking'] && $cachedtask['minute'] == $filetask['minute'] && $cachedtask['hour'] == $filetask['hour'] && $cachedtask['day'] == $filetask['day'] && $cachedtask['month'] == $filetask['month'] && $cachedtask['dayofweek'] == $filetask['dayofweek']) {
                // exact same task already present in db, ignore this entry
                unset($cachedtasks[$callfunction]);
                continue;
            } else {
                // same task matches, this task has been updated, update the datebase
                $task = new object();
                $task->id = $cachedtask['id'];
                $task->callfile = $filetask['callfile'];
                $task->callfunction = $callfunction;
                $task->blocking = $filetask['blocking'];
                $task->minute = $filetask['minute'];
                $task->hour = $filetask['hour'];
                $task->day = $filetask['day'];
                $task->month = $filetask['month'];
                $task->dayofweek = $filetask['dayofweek'];
                $task = addslashes_recursive($task);
                update_record('elis_scheduled_tasks', $task);
                unset($cachedtasks[$callfunction]);
                continue;
            }
        } else {
            // if we are here, this event handler is not present in db (new)
            // add it
            $task = new object();
            $task->plugin = $component;
            $task->callfile = $filetask['callfile'];
            $task->callfunction = $callfunction;
            $task->blocking = $filetask['blocking'];
            $task->minute = $filetask['minute'];
            $task->hour = $filetask['hour'];
            $task->day = $filetask['day'];
            $task->month = $filetask['month'];
            $task->dayofweek = $filetask['dayofweek'];
            $task->timezone = 99;
            $task->nextruntime = cron_next_run_time(time(), (array) $task);
            $task = addslashes_recursive($task);
            insert_record('elis_scheduled_tasks', $task);
        }
    }
    // clean up the left overs, the entries in cachedtasks array at this points are deprecated event handlers
    // and should be removed, delete from db
    elis_tasks_cleanup($component, $cachedtasks);
    return true;
}
Example #8
0
    foreach ($jobs as $job) {
        log_debug("Running core cron " . $job->callfunction);
        $function = $job->callfunction;
        $function();
        $nextrun = cron_next_run_time($now, (array) $job);
        // update next run time
        set_field('cron', 'nextrun', db_format_timestamp($nextrun), 'id', $job->id);
    }
}
// and missed ones...
if ($jobs = get_records_select_array('cron', 'nextrun < ? OR nextrun IS NULL', array(db_format_timestamp($now - MAXRUNAGE)))) {
    foreach ($jobs as $job) {
        if ($job->nextrun) {
            log_warn('core cronjob "' . $job->callfunction . '" didn\'t get run because the nextrun time was too old');
        }
        $nextrun = cron_next_run_time($now, (array) $job);
        // update next run time
        set_field('cron', 'nextrun', db_format_timestamp($nextrun), 'id', $job->id);
    }
}
function cron_next_run_time($lastrun, $job)
{
    $run_date = getdate($lastrun);
    // we don't care about seconds for cron
    $run_date['seconds'] = 0;
    // assert valid month
    if (!cron_valid_month($job, $run_date)) {
        cron_next_month($job, $run_date);
        cron_first_day($job, $run_date);
        cron_first_hour($job, $run_date);
        cron_first_minute($job, $run_date);