Event statements declare the events that the application will be notified of, such as: adding a new object, deleting an object, updating an object or a specified field of an object (with the exception of array,
vector
andblob
fields and elements of inner structures), and checkpoint events. (Checkpoint events are triggered only by explicitly calling a checkpoint API.) Events are specific to classes, so only those classes that have events defined will cause notifications to be fired.Database events can be handled synchronously or asynchronously by the application.
Asynchronous Event Handling
In asynchronous event handling, the application spawns a separate thread to handle each type of event. In C and C++ applications this event thread calls function
mco_async_event_wait()
to wait for the specified event. Java, Python and C# applications call the Connection methodWaitEvent()
. When the event occurs, the eXtremeDB runtime releases the thread. Upon releasing the thread, the runtime continues normal processing, so the handler thread runs in parallel with other threads, until it completes its processing and again callsmco_async_event_wait()
orWaitEvent()
.There is a small window of possibility for another instance of the event to occur before the event handler has completed its task and calls
WaitEvent()
ormco_async_event_wait()
again to wait on the event (events are not queued). This window can be minimized if the handler delegates the processing of the event to yet another thread, allowing the handler thread to immediately wait on the event again. If this risk of an unhandled event cannot be tolerated, either a synchronous event handler can be used, the application can maintain a separate table of unhandled events, or themco_trans_play()
function of the eXtremeDB Transaction Logging feature can be employed. Asynchronous events are activated after the transaction commits. If, within the scope of a single transaction, several objects are added, or deleted, or several fields are updated which have event handlers waiting, all the handlers will be activated simultaneously.A C or C++ application will define event handlers like the following:
/* Thread function that handles the <new> event. */ void NewEventHandler( sample_task_t * descriptor ) { mco_db_h db = descriptor->db_connection; while ( MCO_S_OK == mco_async_event_wait(db, MCO_EVENT_newEvent) ) { /* Process the event. */ ... } } /* Thread function that handles the <update> event. */ void UpdateEventHandler( sample_task_t * descriptor ) { mco_db_h db = descriptor->db_connection; while ( MCO_S_OK == mco_async_event_wait(db, MCO_EVENT_updateEvent) ) { /* Process the event. */ ... } } /* Thread function that handles the <delete> event. */ void DeleteEventHandler( sample_task_t * descriptor ) { mco_db_h db = descriptor->db_connection; while ( MCO_S_OK == mco_async_event_wait(db, MCO_EVENT_deleteEvent) ) { /* Process the event. */ ... } } /* Thread function that handles the <update> event. */ void CheckpointEventHandler( sample_task_t * descriptor ) { mco_db_h db = descriptor->db_connection; while ( MCO_S_OK == mco_async_event_wait(db, MCO_EVENT_checkpointEvent) ) { /* Process the event. */ ... } }Then the C/C++ application will start the event handler threads and cause the main application thread to sleep for some milliseconds (100 is usually adequate) in order for the event handler threads to start listening (waiting). Then the main application thread will proceed with normal database processing. When terminating, the C/C++ application will call
mco_async_event_release_all()
to release all events and then stop the event handler threads. (See the SDK sample samples/native/core/10-events/asynch for implementation details.)Java and C# applications will similarly start individual threads to handle each event with a ThreadProc like the following:
private class ThreadParams { public Connection con; public string event_name; public ThreadParams(Connection con, string EventName) { this.con = con; this.event_name = EventName; } } private static void ThreadProc(object param) { ThreadParams tp = (ThreadParams)param; try { while (!exit) { tp.con.WaitEvent(tp.event_name); } } catch (DatabaseError x) { if (x.errorCode >= 50) { // Errors } else { // Normal return codes including MCO_S_EVENT_RELEASED } } }C# and Java applications will then cause the main application thread to sleep for some milliseconds as explained above, then proceed with normal database processing. When terminating, the application will call the Connection method
ReleaseAllEvents()
and then stop the event handler threads. (See the SDK sample samples/java/events/asyncbasic or samples/csharp/AsyncEvent for further implementation details.) C# applications can also handle asynchronous events through the Delegates mechanism by adding handlers to the Connection propertiesAsynchEvent
andAsyncEventError
and then calling methodStartEventListeners()
. (See the SDK sample samples/csharp/events/AsyncDelegate for further implementation details.)Synchronous Event Handling
C and C++ applications can also respond to eXtremeDB events synchronously, however this capability is not available for Java and C# applications. Synchronous event handlers are called within the context of the same thread that caused the event. Care should be taken in event handlers not to cause extraordinary delays because the handler has control of a transaction that, by definition, is a
READ_WRITE
transaction and thus could block access to the database. Specifically, the handler should not wait on an indeterminate external event such as user input.A synchronous handler returns
MCO_S_OK
to indicate successful completion; any other value (one ofMCO_S_*
orMCO_E_*
constants defined inmco.h
, or a user-defined value) indicates success or failure with additional information. eXtremeDB returns this value to the application, which can act on it accordingly (rolling back the transaction if necessary).Synchronous handlers are registered by calling the generated
mco_register_<eventname>_handler()
function for each event handler. Any number of handlers can be registered for a single event type, but the order in which they are called cannot be predicted. At registration, the application can pass a user-defined parameter that will be, in turn, passed to the event handler when it is invoked. This parameter is a void pointer that can reference a simple scalar value or a complex data structure depending on the application requirements.For new events, synchronous handlers are called by the
classname_new()
orclassname_oid_new()
method immediately after the object is instantiated (so the object handle is guaranteed to be valid). For checkpoint events, synchronous handlers are called by theclassname_checkpoint()
method immediately before or after the object is inserted into indexes – the application specifies whether the handler will be invoked before or after inserting into indexes through the handler registration interface. Checkpoint events are not fired bymco_trans_commit()
, though this function also updates the index(es). For delete events, synchronous handlers are called by theclassname_delete()
method before the object is deleted (while the object handle is still valid).
Note that delete events are not invoked by the
classname_delete_all()
method. Onlyclassname_delete()
will invoke these events.Update events can be defined for a class (i.e. all fields of that class) or for a specific field of a class by specifying the field name in the event declaration. As with checkpoint events, the application must specify through the handler registration interface whether the handler will be invoked before or after a field is updated. Update handlers are activated by the interface methods that cause a field’s contents to change, for example
classname_fieldname_put()
. If the event handler is called before the update and the handler invokeson the field, it will retrieve the current value in the database. Conversely, if the event is called after the update, the handler will retrieve the value the application just put in the database. The user-defined parameter can be used to provide additional information to the handler such as the incoming value for a before-event handler or the old value for an after-event handler.
classname_fieldname_get()
Note that both synchronous and asynchronous events can be applied to any given event. When using eXtremeDB in Shared Memory, Synchronous event handlers must belong to the same process that caused the event. So do not register a synchronous event handler for class Alpha in process A if it is possible that process B will insert/update/delete Alpha objects. Use an asynchronous event handler, instead.
Note that for update events, a class-wide event cannot be combined with field update events for the same class.
Example
The following code fragments illustrate event handling. Consider the following schema DDL definition for a class with event notifications:
class dropped_call { uint4 trunk_id; ... autoid[10000]; event < trunk_id update > upd_trunk; // any name will do event < new > add_trunk; event < checkpoint > checkpoint_trunk; event < delete > del_trunk; };The schema compiler will generate the following definitions in the interface header file:
#define MCO_EVENT_upd_trunk 15 // 15 is only illustrative; the actual value is not important typedef MCO_RET (*mco_upd_trunk_handler)( /*IN*/ mco_trans_h *t, /*IN*/ dropped_call *handle, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param ); MCO_RET mco_register_upd_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_upd_trunk_handler handler, /*IN*/ void *param, /*IN*/ MCO_HANDLING_ORDER when); MCO_RET mco_unregister_upd_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_upd_trunk_handler handler); #define MCO_EVENT_add_trunk 16 typedef MCO_RET (*mco_add_trunk_handler)( /*IN*/ mco_trans_h *t, /*IN*/ dropped_call *handle, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param ); MCO_RET mco_register_add_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_add_trunk_handler handler, /*IN*/ void *param); MCO_RET mco_unregister_add_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_add_trunk_handler handler); #define MCO_EVENT_checkpoint_trunk 17 typedef MCO_RET (*mco_checkpoint_trunk_handler)( /*IN*/ mco_trans_h *t, /*IN*/ dropped_call *handle, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param ); MCO_RET mco_register_checkpoint_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_checkpoint_trunk_handler handler, /*IN*/ void *param, /*IN*/ MCO_HANDLING_ORDER when); MCO_RET mco_unregister_checkpoint_trunk_handler(/*IN*/ mco_trans_h *t, /*IN*/ mco_checkpoint_trunk_handler handler); #define MCO_EVENT_del_trunk 18 typedef MCO_RET (*mco_del_trunk_handler)( /*IN*/ mco_trans_h *t, /*IN*/ dropped_call *handle, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param ); MCO_RET mco_register_del_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_del_trunk_handler handler, /*IN*/ void *param); MCO_RET mco_unregister_del_trunk_handler( /*IN*/ mco_trans_h *t, /*IN*/ mco_del_trunk_handler handler);To employ an asynchronous handler for one of the events above, the C/C++ application would start a thread and, within the thread function, call:
mco_async_event_wait( dbh, MCO_EVENT_upd_trunk );Where
dbh
is the database handle from themco_db_connect()
call andupd_trunk
is the value defined in the generated interface file to reference the event of interest. Java and C# applications will start a thread and call the Connection methodWaitEvent()
.As previously mentioned, this thread will block (wait) until released by the eXtremeDB runtime. It can be released either by an occurrence of the event, or the C/C++ application can release it forcibly by calling one of the following:
mco_async_event_release( dbh, upd_trunk ); mco_async_event_release_all_( dbh );A C/C++ event handler will know if it was released by an occurrence of the event or by a
release()
function by the return value ofmco_async_event_wait()
.MCO_S_OK
means the event happened;MCO_S_EVENT_RELEASED
means the event was released. Java and C# event handlers will catch an exception with error codeMCO_S_EVENT_RELEASED
when the event is released.For the preceding class definition and its generated interfaces, the following code fragments illustrate synchronous event handling. First, the C/C++ application must register its synchronous event handler functions with code like the following:
int register_events(mco_db_h db) { MCO_RET rc; mco_trans_h t; mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t); mco_register_add_trunk_handler(t, &new_handler, (void*) 0); mco_register_checkpoint_trunk_handler(t, &checkpoint_handler, (void*) 0, MCO_BEFORE_UPDATE ); mco_register_del_trunk_handler(t, &delete_handler, (void *) 0); mco_register_upd_trunk_handler( t, &update_handler1, (void *) 0, MCO_BEFORE_UPDATE ); rc = mco_trans_commit(t); return rc; }The bodies of the C/C++ handler functions would look like the following:
/* Handler for the "<new>" event. Reads the autoid and prints it out */ MCO_RET new_handler( /*IN*/ mco_trans_h t, /*IN*/ dropped_call * obj, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param) { int8 u8; param = (int *)1; dropped_call_autoid_get( obj, &u8 ); printf( "Event \"Object New\" : object (%llu) is created\n", u8 ); return MCO_S_OK; } /* Handler for the "<checkpoint>" event. Reads the autoid and prints it out */ MCO_RET checkpoint_handler( /*IN*/ mco_trans_h t, /*IN*/ dropped_call * obj, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param) { int8 u8; param = (int *)1; dropped_call_autoid_get( obj, &u8 ); printf( "Event \"Object Checkpoint\" : object (%ld,%ld) is about to be created\n", u8.lo, u8.hi ); return MCO_S_OK; } /* Handler for the "<delete>" event. Note that the handler * is called before the current transaction is committed. * Therefore, the object is still valid; the object handle * is passed to the handler and is used to obtain the * autoid of the object. The event's handler return value * is passed into the "delete" function and is later * examined by the mco_trans_commit(). If the value is * anything but MCO_S_OK, the transaction is rolled back. * In this sample every other delete transaction is * committed. */ MCO_RET delete_handler( /*IN*/ mco_trans_h t, /*IN*/ dropped_call * obj, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *user_param) { int8 u8; dropped_call_autoid_get( obj, &u8); printf( "Event \"Object Delete\": object (%ld,%ld) is being deleted...", u8.lo, u8.hi ); return (((u8.lo + u8.hi) %2) ? 1: MCO_S_OK); } /* Handler for the "update" event. This handler is called * before the update transaction is committed - hence the * value of the field being changed is reported unchanged * yet. */ MCO_RET update_handler1( /*IN*/ mco_trans_h t, /*IN*/ dropped_call * obj, /*IN*/ MCO_EVENT_TYPE et, /*INOUT*/ void *param) { uint4 u4; int8 u8; dropped_call_autoid_get( obj, &u8); dropped_call_trunk_id_get(obj, &u4); printf( "Event \"Object Update\" (before commit): object (%ld,%ld) value %d\n", u8.lo, u8.hi, u4 ); return MCO_S_OK; }When the C/C++ application is finished handling events, the events are unregistered by code like the following:
int unregister_events(mco_db_h db) { MCO_RET rc; mco_trans_h t; mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t); mco_unregister_add_trunk_handler( t, & new_handler); mco_unregister_del_trunk_handler( t, & delete_handler); mco_unregister_update_handler( t, & update_handler1); rc = mco_trans_commit(t); return rc; }(See the SDK sample samples/native/core/10-events/synch for further implementation details.)