MDL-24211 events subsystem cleanup and improvements

* handlers can be defined as internal or external
* external handlers are not called instantly when inside DB transaction
* code cleanup
* more robust detection of broken handlers
* refixed unit tests
* removing all pending events from 1.9 - these can not be processed due to encoding changes
* finally using real 'component' in handlers table
This commit is contained in:
Petr Skoda 2010-09-15 15:55:18 +00:00
parent d90e49f238
commit 650462dfac
13 changed files with 366 additions and 176 deletions

View File

@ -31,13 +31,15 @@ $handlers = array (
'role_assigned' => array (
'handlerfile' => '/enrol/category/locallib.php',
'handlerfunction' => array('enrol_category_handler', 'role_assigned'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'role_unassigned' => array (
'handlerfile' => '/enrol/category/locallib.php',
'handlerfunction' => array('enrol_category_handler', 'role_unassigned'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
);

View File

@ -31,18 +31,21 @@ $handlers = array (
'cohort_member_added' => array (
'handlerfile' => '/enrol/cohort/locallib.php',
'handlerfunction' => array('enrol_cohort_handler', 'member_added'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'cohort_member_removed' => array (
'handlerfile' => '/enrol/cohort/locallib.php',
'handlerfunction' => array('enrol_cohort_handler', 'member_removed'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'cohort_deleted' => array (
'handlerfile' => '/enrol/cohort/locallib.php',
'handlerfunction' => array('enrol_cohort_handler', 'deleted'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
);

View File

@ -31,30 +31,35 @@ $handlers = array (
'role_assigned' => array (
'handlerfile' => '/enrol/meta/locallib.php',
'handlerfunction' => array('enrol_meta_handler', 'role_assigned'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'role_unassigned' => array (
'handlerfile' => '/enrol/meta/locallib.php',
'handlerfunction' => array('enrol_meta_handler', 'role_unassigned'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'user_enrolled' => array (
'handlerfile' => '/enrol/meta/locallib.php',
'handlerfunction' => array('enrol_meta_handler', 'user_enrolled'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'user_unenrolled' => array (
'handlerfile' => '/enrol/meta/locallib.php',
'handlerfunction' => array('enrol_meta_handler', 'user_unenrolled'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'course_deleted' => array (
'handlerfile' => '/enrol/meta/locallib.php',
'handlerfunction' => array('enrol_meta_handler', 'course_deleted'),
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
);

View File

@ -41,7 +41,8 @@ $handlers = array(
'portfolio_send' => array (
'handlerfile' => '/lib/portfolio.php',
'handlerfunction' => 'portfolio_handle_event', // argument to call_user_func(), could be an array
'schedule' => 'cron'
'schedule' => 'cron',
'internal' => 0,
),

View File

@ -1550,18 +1550,19 @@
<TABLE NAME="events_handlers" COMMENT="This table is for storing which components requests what type of event, and the location of the responsible handlers. For example, the assignment registers 'grade_updated' event with a function assignment_grade_handler() that should be called event time an 'grade_updated' event is triggered by grade_update() function." PREVIOUS="events_queue" NEXT="events_queue_handlers">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="eventname"/>
<FIELD NAME="eventname" TYPE="char" LENGTH="166" NOTNULL="true" SEQUENCE="false" COMMENT="name of the event, e.g. 'grade_updated'" PREVIOUS="id" NEXT="handlermodule"/>
<FIELD NAME="handlermodule" TYPE="char" LENGTH="166" NOTNULL="true" SEQUENCE="false" COMMENT="e.g. moodle, mod/forum, block/rss_client" PREVIOUS="eventname" NEXT="handlerfile"/>
<FIELD NAME="handlerfile" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="path to the file of the function, eg /grade/export/lib.php" PREVIOUS="handlermodule" NEXT="handlerfunction"/>
<FIELD NAME="eventname" TYPE="char" LENGTH="166" NOTNULL="true" SEQUENCE="false" COMMENT="name of the event, e.g. 'grade_updated'" PREVIOUS="id" NEXT="component"/>
<FIELD NAME="component" TYPE="char" LENGTH="166" NOTNULL="true" SEQUENCE="false" COMMENT="e.g. moodle, mod_forum, block_rss_client" PREVIOUS="eventname" NEXT="handlerfile"/>
<FIELD NAME="handlerfile" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="path to the file of the function, eg /grade/export/lib.php" PREVIOUS="component" NEXT="handlerfunction"/>
<FIELD NAME="handlerfunction" TYPE="text" LENGTH="medium" NOTNULL="false" SEQUENCE="false" COMMENT="serialized string or array describing function, suitable to be passed to call_user_func()" PREVIOUS="handlerfile" NEXT="schedule"/>
<FIELD NAME="schedule" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="'cron' or 'instant'." PREVIOUS="handlerfunction" NEXT="status"/>
<FIELD NAME="status" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="number of failed attempts to process this handler" PREVIOUS="schedule"/>
<FIELD NAME="status" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="number of failed attempts to process this handler" PREVIOUS="schedule" NEXT="internal"/>
<FIELD NAME="internal" TYPE="int" LENGTH="2" NOTNULL="true" UNSIGNED="true" DEFAULT="1" SEQUENCE="false" COMMENT="1 means standard plugin handler, 0 indicates if event handler sends data to external systems, this is used for example to prevent immediate sending of events from pending db transactions" PREVIOUS="status"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="eventname-handlermodule" UNIQUE="true" FIELDS="eventname, handlermodule"/>
<INDEX NAME="eventname-component" UNIQUE="true" FIELDS="eventname, component"/>
</INDEXES>
</TABLE>
<TABLE NAME="events_queue_handlers" COMMENT="This is the list of queued handlers for processing. The event object is retrieved from the events_queue table. When no further reference is made to the event_queues table, the corresponding entry in the events_queue table should be deleted. Entry should get deleted after a successful event processing by the specified handler." PREVIOUS="events_handlers" NEXT="grade_outcomes">
@ -2608,7 +2609,7 @@
<TABLE NAME="registration_hubs" COMMENT="hub where the site is registered on with their associated token" PREVIOUS="license" NEXT="backup_controllers">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="token"/>
<FIELD NAME="token" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="hubname"/>
<FIELD NAME="token" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="hubname"/>
<FIELD NAME="hubname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="token" NEXT="huburl"/>
<FIELD NAME="huburl" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="hubname" NEXT="confirmed"/>
<FIELD NAME="confirmed" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="huburl"/>

View File

@ -1976,18 +1976,6 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
upgrade_main_savepoint(true, 2009061704);
}
if ($oldversion < 2009061705) {
// change component string in events_handlers records to new "_" format
if ($handlers = $DB->get_records('events_handlers')) {
foreach ($handlers as $handler) {
$handler->handlermodule = str_replace('/', '_', $handler->handlermodule);
$DB->update_record('events_handlers', $handler);
}
}
unset($handlers);
upgrade_main_savepoint(true, 2009061705);
}
if ($oldversion < 2009063000) {
// upgrade format of _with_advanced settings - quiz only
// note: this can be removed later, not needed for upgrades from 1.9.x
@ -5174,6 +5162,89 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
upgrade_main_savepoint(true, 2010091504);
}
if ($oldversion < 2010091505) {
// drop all events queued from 1.9, unfortunately we can not process them because the serialisation of data changed
// also the events format was changed....
$DB->delete_records('events_queue_handlers', array());
$DB->delete_records('events_queue', array());
//reset all status fields too
$DB->set_field('events_handlers', 'status', 0, array());
upgrade_main_savepoint(true, 2010091505);
}
if ($oldversion < 2010091506) {
// change component string in events_handlers records to new "_" format
if ($handlers = $DB->get_records('events_handlers')) {
foreach ($handlers as $handler) {
$handler->handlermodule = str_replace('/', '_', $handler->handlermodule);
$DB->update_record('events_handlers', $handler);
}
}
unset($handlers);
upgrade_main_savepoint(true, 2010091506);
}
if ($oldversion < 2010091507) {
// Define index eventname-handlermodule (unique) to be dropped form events_handlers
$table = new xmldb_table('events_handlers');
$index = new xmldb_index('eventname-handlermodule', XMLDB_INDEX_UNIQUE, array('eventname', 'handlermodule'));
// Conditionally launch drop index eventname-handlermodule
if ($dbman->index_exists($table, $index)) {
$dbman->drop_index($table, $index);
}
// Main savepoint reached
upgrade_main_savepoint(true, 2010091507);
}
if ($oldversion < 2010091508) {
// Rename field handlermodule on table events_handlers to component
$table = new xmldb_table('events_handlers');
$field = new xmldb_field('handlermodule', XMLDB_TYPE_CHAR, '166', null, XMLDB_NOTNULL, null, null, 'eventname');
// Launch rename field handlermodule
$dbman->rename_field($table, $field, 'component');
// Main savepoint reached
upgrade_main_savepoint(true, 2010091508);
}
if ($oldversion < 2010091509) {
// Define index eventname-component (unique) to be added to events_handlers
$table = new xmldb_table('events_handlers');
$index = new xmldb_index('eventname-component', XMLDB_INDEX_UNIQUE, array('eventname', 'component'));
// Conditionally launch add index eventname-component
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}
// Main savepoint reached
upgrade_main_savepoint(true, 2010091509);
}
if ($oldversion < 2010091510) {
// Define field internal to be added to events_handlers
$table = new xmldb_table('events_handlers');
$field = new xmldb_field('internal', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '1', 'status');
// Conditionally launch add field internal
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Main savepoint reached
upgrade_main_savepoint(true, 2010091510);
}
return true;
}

View File

@ -34,11 +34,16 @@ defined('MOODLE_INTERNAL') || die();
*
* INTERNAL - to be used from eventslib only
*
* @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
* @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
* @return array of capabilities or empty array if not exists
*/
function events_load_def($component) {
$defpath = get_component_directory($component).'/db/events.php';
global $CFG;
if ($component === 'unittest') {
$defpath = $CFG->dirroot.'/lib/simpletest/fixtures/events.php';
} else {
$defpath = get_component_directory($component).'/db/events.php';
}
$handlers = array();
@ -46,6 +51,42 @@ function events_load_def($component) {
require($defpath);
}
// make sure the definitions are valid and complete; tell devs what is wrong
foreach ($handlers as $eventname => $handler) {
if ($eventname === 'reset') {
debugging("'reset' can not be used as event name.");
unset($handlers['reset']);
continue;
}
if (!is_array($handler)) {
debugging("Handler of '$eventname' must be specified as array'");
unset($handlers[$eventname]);
continue;
}
if (!isset($handler['handlerfile'])) {
debugging("Handler of '$eventname' must include 'handlerfile' key'");
unset($handlers[$eventname]);
continue;
}
if (!isset($handler['handlerfunction'])) {
debugging("Handler of '$eventname' must include 'handlerfunction' key'");
unset($handlers[$eventname]);
continue;
}
if (!isset($handler['schedule'])) {
$handler['schedule'] = 'instant';
}
if ($handler['schedule'] !== 'instant' and $handler['schedule'] !== 'cron') {
debugging("Handler of '$eventname' must include valid 'schedule' type (instant or cron)'");
unset($handlers[$eventname]);
continue;
}
if (!isset($handler['internal'])) {
$handler['internal'] = 1;
}
$handlers[$eventname] = $handler;
}
return $handlers;
}
@ -55,7 +96,7 @@ function events_load_def($component) {
*
* INTERNAL - to be used from eventslib only
*
* @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
* @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
* @return array of events
*/
function events_get_cached($component) {
@ -63,13 +104,14 @@ function events_get_cached($component) {
$cachedhandlers = array();
if ($storedhandlers = $DB->get_records('events_handlers', array('handlermodule'=>$component))) {
if ($storedhandlers = $DB->get_records('events_handlers', array('component'=>$component))) {
foreach ($storedhandlers as $handler) {
$cachedhandlers[$handler->eventname] = array (
'id' => $handler->id,
'handlerfile' => $handler->handlerfile,
'handlerfunction' => $handler->handlerfunction,
'schedule' => $handler->schedule);
'schedule' => $handler->schedule,
'internal' => $handler->internal);
}
}
@ -85,7 +127,7 @@ function events_get_cached($component) {
* the database.
*
* @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
* @return boolean
* @return boolean always returns true
*/
function events_update_definition($component='moodle') {
global $DB;
@ -100,9 +142,10 @@ function events_update_definition($component='moodle') {
foreach ($filehandlers as $eventname => $filehandler) {
if (!empty($cachedhandlers[$eventname])) {
if ($cachedhandlers[$eventname]['handlerfile'] == $filehandler['handlerfile'] &&
$cachedhandlers[$eventname]['handlerfunction'] == serialize($filehandler['handlerfunction']) &&
$cachedhandlers[$eventname]['schedule'] == $filehandler['schedule']) {
if ($cachedhandlers[$eventname]['handlerfile'] === $filehandler['handlerfile'] &&
$cachedhandlers[$eventname]['handlerfunction'] === serialize($filehandler['handlerfunction']) &&
$cachedhandlers[$eventname]['schedule'] === $filehandler['schedule'] &&
$cachedhandlers[$eventname]['internal'] == $filehandler['internal']) {
// exact same event handler already present in db, ignore this entry
unset($cachedhandlers[$eventname]);
@ -115,6 +158,7 @@ function events_update_definition($component='moodle') {
$handler->handlerfile = $filehandler['handlerfile'];
$handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
$handler->schedule = $filehandler['schedule'];
$handler->internal = $filehandler['internal'];
$DB->update_record('events_handlers', $handler);
@ -127,10 +171,12 @@ function events_update_definition($component='moodle') {
// add it
$handler = new object();
$handler->eventname = $eventname;
$handler->handlermodule = $component;
$handler->component = $component;
$handler->handlerfile = $filehandler['handlerfile'];
$handler->handlerfunction = serialize($filehandler['handlerfunction']); // static class methods stored as array
$handler->schedule = $filehandler['schedule'];
$handler->status = 0;
$handler->internal = $filehandler['internal'];
$DB->insert_record('events_handlers', $handler);
}
@ -140,17 +186,21 @@ function events_update_definition($component='moodle') {
// and should be removed, delete from db
events_cleanup($component, $cachedhandlers);
events_get_handlers('reset');
return true;
}
/**
* Remove all event handlers and queued events
*
* @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
* @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
*/
function events_uninstall($component) {
$cachedhandlers = events_get_cached($component);
events_cleanup($component, $cachedhandlers);
events_get_handlers('reset');
}
/**
@ -158,9 +208,9 @@ function events_uninstall($component) {
*
* INTERNAL - to be used from eventslib only
*
* @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
* @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
* @param array $cachedhandlers array of the cached events definitions that will be
* @return int number of deprecated capabilities that have been removed
* @return int number of unused handlers that have been removed
*/
function events_cleanup($component, $cachedhandlers) {
global $DB;
@ -168,15 +218,15 @@ function events_cleanup($component, $cachedhandlers) {
$deletecount = 0;
foreach ($cachedhandlers as $eventname => $cachedhandler) {
if ($qhandlers = $DB->get_records('events_queue_handlers', array('handlerid'=>$cachedhandler['id']))) {
debugging("Removing pending events from queue before deleting of event handler: $component - $eventname");
//debugging("Removing pending events from queue before deleting of event handler: $component - $eventname");
foreach ($qhandlers as $qhandler) {
events_dequeue($qhandler);
}
}
if ($DB->delete_records('events_handlers', array('eventname'=>$eventname, 'handlermodule'=>$component))) {
$deletecount++;
}
$DB->delete_records('events_handlers', array('eventname'=>$eventname, 'component'=>$component));
$deletecount++;
}
return $deletecount;
}
@ -206,7 +256,7 @@ function events_queue_handler($handler, $event, $errormessage) {
$qhandler->handlerid = $handler->id;
$qhandler->errormessage = $errormessage;
$qhandler->timemodified = time();
if ($handler->schedule == 'instant' and $handler->status == 1) {
if ($handler->schedule === 'instant' and $handler->status == 1) {
$qhandler->status = 1; //already one failed attempt to dispatch this event
} else {
$qhandler->status = 0;
@ -223,7 +273,7 @@ function events_queue_handler($handler, $event, $errormessage) {
* @param handler $hander object from db
* @param eventdata $eventdata dataobject
* @param string $errormessage error message indicating problem
* @return bool success or fail
* @return bool true means event processed, false means retry event later; may throw exception, NULL means internal error
*/
function events_dispatch($handler, $eventdata, &$errormessage) {
global $CFG;
@ -237,17 +287,22 @@ function events_dispatch($handler, $eventdata, &$errormessage) {
include_once($CFG->dirroot.$handler->handlerfile);
} else {
$errormessage = "Handler file of component $handler->handlermodule: $handler->handlerfile can not be found!";
return false;
$errormessage = "Handler file of component $handler->component: $handler->handlerfile can not be found!";
return null;
}
// checks for handler validity
if (is_callable($function)) {
return call_user_func($function, $eventdata);
$result = call_user_func($function, $eventdata);
if ($result === false) {
$errormessage = "Handler function of component $handler->component: $handler->handlerfunction requested resending of event!";
return false;
}
return true;
} else {
$errormessage = "Handler function of component $handler->handlermodule: $handler->handlerfunction not callable function or class method!";
return false;
$errormessage = "Handler function of component $handler->component: $handler->handlerfunction not callable function or class method!";
return null;
}
}
@ -257,10 +312,10 @@ function events_dispatch($handler, $eventdata, &$errormessage) {
* INTERNAL - to be used from eventslib only
*
* @param object $qhandler events_queued_handler object from db
* @return boolean meaning success, or NULL on fatal failure
* @return boolean true means event processed, false means retry later, NULL means fatal failure
*/
function events_process_queued_handler($qhandler) {
global $CFG, $DB;
global $DB;
// get handler
if (!$handler = $DB->get_record('events_handlers', array('id'=>$qhandler->handlerid))) {
@ -280,22 +335,28 @@ function events_process_queued_handler($qhandler) {
}
// call the function specified by the handler
$errormessage = 'Unknown error';
if (events_dispatch($handler, unserialize(base64_decode($event->eventdata)), $errormessage)) {
//everything ok
events_dequeue($qhandler);
return true;
} else {
//dispatching failed
$qh = new object();
$qh->id = $qhandler->id;
$qh->errormessage = $errormessage;
$qh->timemodified = time();
$qh->status = $qhandler->status + 1;
$DB->update_record('events_queue_handlers', $qh);
return false;
try {
$errormessage = 'Unknown error';
if (events_dispatch($handler, unserialize(base64_decode($event->eventdata)), $errormessage)) {
//everything ok
events_dequeue($qhandler);
return true;
}
} catch (Exception $e) {
// the problem here is that we do not want one broken handler to stop all others,
// cron handlers are very tricky because the needed data might have been deleted before the cron execution
$errormessage = "Handler function of component $handler->component: $handler->handlerfunction threw exception :".$e->getMessage();
}
//dispatching failed
$qh = new object();
$qh->id = $qhandler->id;
$qh->errormessage = $errormessage;
$qh->timemodified = time();
$qh->status = $qhandler->status + 1;
$DB->update_record('events_queue_handlers', $qh);
return false;
}
/**
@ -332,7 +393,7 @@ function events_get_handlers($eventname) {
global $DB;
static $handlers = array();
if ($eventname == 'reset') {
if ($eventname === 'reset') {
$handlers = array();
return false;
}
@ -353,7 +414,7 @@ function events_get_handlers($eventname) {
* PUBLIC
*
* @param string $eventname empty means all
* @return number of dispatched+removed broken events
* @return number of dispatched events
*/
function events_cron($eventname='') {
global $DB;
@ -376,19 +437,36 @@ function events_cron($eventname='') {
if ($rs = $DB->get_recordset_sql($sql, $params)) {
foreach ($rs as $qhandler) {
if (in_array($qhandler->handlerid, $failed)) {
// do not try to dispatch any later events when one already failed
if (isset($failed[$qhandler->handlerid])) {
// do not try to dispatch any later events when one already asked for retry or ended with exception
continue;
}
$status = events_process_queued_handler($qhandler);
if ($status === false) {
$failed[] = $qhandler->handlerid;
// handler is asking for retry, do not send other events to this handler now
$failed[$qhandler->handlerid] = $qhandler->handlerid;
} else if ($status === NULL) {
// means completely broken handler, event data was purged
$failed[$qhandler->handlerid] = $qhandler->handlerid;
} else {
$processed++;
}
}
$rs->close();
}
// remove events that do not have any handlers waiting
$sql = "SELECT eq.id
FROM {events_queue} eq
LEFT JOIN {events_queue_handlers} qh ON qh.queuedeventid = eq.id
WHERE qh.id IS NULL";
$rs = $DB->get_recordset_sql($sql);
foreach ($rs as $event) {
//debugging('Purging stale event '.$event->id);
$DB->delete_records('events_queue', array('id'=>$event->id));
}
$rs->close();
return $processed;
}
@ -406,24 +484,13 @@ function events_trigger($eventname, $eventdata) {
global $CFG, $USER, $DB;
$failedcount = 0; // number of failed events.
$event = false;
// pull out all registered event handlers
if ($handlers = events_get_handlers($eventname)) {
foreach ($handlers as $handler) {
$errormessage = '';
$errormessage = '';
if ($handler->schedule == 'instant' and $DB->is_transaction_started()) {
// Instant events can not be rolled back in DB transactions,
// we need to send them to queue instead - this is slow but necessary.
// It could be improved in future by adding internal/external flag to each handler.
$DB->set_field('events_handlers', 'status', ($handler->status+1), array('id'=>$handler->id));
// reset static handler cache
events_get_handlers('reset');
} else if ($handler->schedule == 'instant') {
if ($handler->schedule === 'instant') {
if ($handler->status) {
//check if previous pending events processed
if (!$DB->record_exists('events_queue_handlers', array('handlerid'=>$handler->id))) {
@ -436,71 +503,78 @@ function events_trigger($eventname, $eventdata) {
}
// dispatch the event only if instant schedule and status ok
if (!$handler->status) {
$errormessage = 'Unknown error';;
if (events_dispatch($handler, $eventdata, $errormessage)) {
continue;
}
// set error count to 1 == send next instant into cron queue
$DB->set_field('events_handlers', 'status', 1, array('id'=>$handler->id));
// reset static handler cache
events_get_handlers('reset');
} else {
if ($handler->status or (!$handler->internal and $DB->is_transaction_started())) {
// increment the error status counter
$handler->status++;
$DB->set_field('events_handlers', 'status', $handler->status, array('id'=>$handler->id));
// reset static handler cache
events_get_handlers('reset');
} else {
$errormessage = 'Unknown error';;
$result = events_dispatch($handler, $eventdata, $errormessage);
if ($result === true) {
// everything is fine - event dispatched
continue;
} else if ($result === false) {
// retry later - set error count to 1 == send next instant into cron queue
$DB->set_field('events_handlers', 'status', 1, array('id'=>$handler->id));
// reset static handler cache
events_get_handlers('reset');
} else {
// internal problem - ignore the event completely
$failedcount ++;
continue;
}
}
// update the failed counter
$failedcount ++;
} else if ($handler->schedule == 'cron') {
} else if ($handler->schedule === 'cron') {
//ok - use queueing of events only
} else {
// unknown schedule - fallback to cron type
// unknown schedule - ignore event completely
debugging("Unknown handler schedule type: $handler->schedule");
$failedcount ++;
continue;
}
// if even type is not instant, or dispatch failed, queue it
if ($event === false) {
$event = new object();
$event->userid = $USER->id;
$event->eventdata = base64_encode(serialize($eventdata));
$event->timecreated = time();
if (debugging()) {
$dump = '';
$callers = debug_backtrace();
foreach ($callers as $caller) {
if (!isset($caller['line'])) {
$caller['line'] = '?';
}
if (!isset($caller['file'])) {
$caller['file'] = '?';
}
$dump .= 'line ' . $caller['line'] . ' of ' . substr($caller['file'], strlen($CFG->dirroot) + 1);
if (isset($caller['function'])) {
$dump .= ': call to ';
if (isset($caller['class'])) {
$dump .= $caller['class'] . $caller['type'];
}
$dump .= $caller['function'] . '()';
}
$dump .= "\n";
// if even type is not instant, or dispatch asked for retry, queue it
$event = new object();
$event->userid = $USER->id;
$event->eventdata = base64_encode(serialize($eventdata));
$event->timecreated = time();
if (debugging()) {
$dump = '';
$callers = debug_backtrace();
foreach ($callers as $caller) {
if (!isset($caller['line'])) {
$caller['line'] = '?';
}
$event->stackdump = $dump;
} else {
$event->stackdump = '';
if (!isset($caller['file'])) {
$caller['file'] = '?';
}
$dump .= 'line ' . $caller['line'] . ' of ' . substr($caller['file'], strlen($CFG->dirroot) + 1);
if (isset($caller['function'])) {
$dump .= ': call to ';
if (isset($caller['class'])) {
$dump .= $caller['class'] . $caller['type'];
}
$dump .= $caller['function'] . '()';
}
$dump .= "\n";
}
$event->id = $DB->insert_record('events_queue', $event);
$event->stackdump = $dump;
} else {
$event->stackdump = '';
}
$event->id = $DB->insert_record('events_queue', $event);
events_queue_handler($handler, $event, $errormessage);
}
} else {
//debugging("No handler found for event: $eventname");
// No handler found for this event name - this is ok!
}
return $failedcount;
@ -515,7 +589,7 @@ function events_trigger($eventname, $eventdata) {
*/
function events_is_registered($eventname, $component) {
global $DB;
return $DB->record_exists('events_handlers', array('handlermodule'=>$component, 'eventname'=>$eventname));
return $DB->record_exists('events_handlers', array('component'=>$component, 'eventname'=>$eventname));
}
/**
@ -527,10 +601,12 @@ function events_is_registered($eventname, $component) {
* @return int number of queued events
*/
function events_pending_count($eventname) {
global $CFG, $DB;
global $DB;
$sql = "SELECT COUNT('x')
FROM {events_queue_handlers} qh, {events_handlers} h
WHERE qh.handlerid = h.id AND h.eventname=?";
FROM {events_queue_handlers} qh
JOIN {events_handlers} h ON h.id = qh.handlerid
WHERE h.eventname = ?";
return $DB->count_records_sql($sql, array($eventname));
}

View File

@ -1,40 +1,43 @@
<?php
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.org //
// //
// Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
// //
// This program 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 2 of the License, or //
// (at your option) any later version. //
// //
// This program 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: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
// 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/>.
/**
* Test event handler definition used only from unit tests.
*
* @package core
* @subpackage event
* @copyright 2007 onwards Martin Dougiamas (http://dougiamas.com)
* @author Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$handlers = array (
'test_instant' => array (
'handlerfile' => '/lib/simpletest/testeventslib.php',
'handlerfunction' => 'sample_function_handler',
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'test_cron' => array (
'handlerfile' => '/lib/simpletest/testeventslib.php',
'handlerfunction' => array('sample_handler_class', 'static_method'),
'schedule' => 'cron'
'schedule' => 'cron',
'internal' => 1,
)
);

View File

@ -1,8 +1,32 @@
<?php
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
// 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/>.
/**
* Tests events subsystems
*
* @package core
* @subpackage event
* @copyright 2007 onwards Martin Dougiamas (http://dougiamas.com)
* @author Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// test handler function
function sample_function_handler($eventdata) {
@ -73,6 +97,9 @@ class sample_handler_class {
}
class eventslib_test extends UnitTestCase {
public static $includecoverage = array('lib/eventslib.php');
/**
* Create temporary entries in the database for these tests.
* These tests have to work no matter the data currently in the database
@ -81,6 +108,7 @@ class eventslib_test extends UnitTestCase {
*/
function setUp() {
// Set global category settings to -1 (not force)
events_uninstall('unittest');
sample_function_handler('reset');
sample_handler_class::static_method('reset');
@ -100,7 +128,7 @@ class eventslib_test extends UnitTestCase {
function test__events_update_definition__install() {
global $CFG, $DB;
$dbcount = $DB->count_records('events_handlers', array('handlermodule'=>'unittest'));
$dbcount = $DB->count_records('events_handlers', array('component'=>'unittest'));
$handlers = array();
require($CFG->libdir.'/simpletest/fixtures/events.php');
$filecount = count($handlers);
@ -114,7 +142,7 @@ class eventslib_test extends UnitTestCase {
global $DB;
events_uninstall('unittest');
$this->assertEqual(0, $DB->count_records('events_handlers', array('handlermodule'=>'unittest')), 'All handlers should be uninstalled: %s');
$this->assertEqual(0, $DB->count_records('events_handlers', array('component'=>'unittest')), 'All handlers should be uninstalled: %s');
}
/**
@ -123,7 +151,7 @@ class eventslib_test extends UnitTestCase {
function test__events_update_definition__update() {
global $DB;
// first modify directly existing handler
$handler = $DB->get_record('events_handlers', array('handlermodule'=>'unittest', 'eventname'=>'test_instant'));
$handler = $DB->get_record('events_handlers', array('component'=>'unittest', 'eventname'=>'test_instant'));
$original = $handler->handlerfunction;
@ -132,7 +160,7 @@ class eventslib_test extends UnitTestCase {
// update the definition, it should revert the handler back
events_update_definition('unittest');
$handler = $DB->get_record('events_handlers', array('handlermodule'=>'unittest', 'eventname'=>'test_instant'));
$handler = $DB->get_record('events_handlers', array('component'=>'unittest', 'eventname'=>'test_instant'));
$this->assertEqual($handler->handlerfunction, $original, 'update should sync db with file definition: %s');
}
@ -158,7 +186,7 @@ class eventslib_test extends UnitTestCase {
function test__events_trigger__cron() {
$this->assertEqual(0, events_trigger('test_cron', 'ok'));
$this->assertEqual(0, sample_handler_class::static_method('status'));
events_cron();
events_cron('test_cron');
$this->assertEqual(1, sample_handler_class::static_method('status'));
}
@ -168,7 +196,6 @@ class eventslib_test extends UnitTestCase {
function test__events_pending_count() {
events_trigger('test_cron', 'ok');
events_trigger('test_cron', 'ok');
$this->assertEqual(2, events_pending_count('test_cron'), 'two events should in queue: %s');
events_cron('test_cron');
$this->assertEqual(0, events_pending_count('test_cron'), 'all messages should be already dequeued: %s');
}
@ -193,9 +220,6 @@ class eventslib_test extends UnitTestCase {
$this->assertEqual(4, sample_function_handler('status'), 'verify event was dispatched: %s');
$this->assertEqual(0, events_pending_count('test_instant'), 'no events should in queue: %s');
}
}

View File

@ -28,12 +28,14 @@ $handlers = array (
'user_enrolled' => array (
'handlerfile' => '/mod/forum/lib.php',
'handlerfunction' => 'forum_user_enrolled',
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
'user_unenrolled' => array (
'handlerfile' => '/mod/forum/lib.php',
'handlerfunction' => 'forum_user_unenrolled',
'schedule' => 'instant'
'schedule' => 'instant',
'internal' => 1,
),
);

View File

@ -4,7 +4,8 @@ $handlers = array (
'user_deleted' => array (
'handlerfile' => '/portfolio/googledocs/lib.php',
'handlerfunction' => 'portfolio_googledocs_user_deleted',
'schedule' => 'cron'
'schedule' => 'cron',
'internal' => 0,
),
);

View File

@ -4,7 +4,8 @@ $handlers = array (
'user_deleted' => array (
'handlerfile' => '/portfolio/picasa/lib.php',
'handlerfunction' => 'portfolio_picasa_user_deleted',
'schedule' => 'cron'
'schedule' => 'cron',
'internal' => 0,
),
);

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2010091504; // YYYYMMDD = date of the last version bump
$version = 2010091511; // YYYYMMDD = date of the last version bump
// XX = daily increments
$release = '2.0 Preview 4+ (Build: 20100913)'; // Human-friendly version name