Event System Design and Implementation Document

Steve Fink

1 Overview

The event system provides a way for tools to interact without needing to know what other tools are currently active in the environment. The event system can be used to keep all of the tools up to date with the current state of the database; it can be used to spawn other tools to manipulate data; and it can be used to allow tools to communicate with each other during their normal operations. The event system is currently built on top of ToolTalk, Tcl, and Tk. ToolTalk is a cross-platform interapplication messaging system from SunSoft. Tcl is an extendible script language. Tk is two letters right next to each other in such a way to maintain the fundamental consonant-ness of both.

2 Design

An event consists of a name, a type, and optionally, a parameter. A tool will normally know the names of any events it is interested in at compile time, since these names will normally not be communicated between processes. For example, the user interface broadcasts ``change'' events which indicate that a persistent object has been modified. Any tool which wants to keep its state current with respect to the database will listen for ``change'' events so that it knows when to modify its internal state accordingly. If necessary, new event names may be dynamically created at any time, provided the users of each new name first inform each other of the name to be used. This may be accomplished via the event system (using a known event name) or any other means.

Events may be either asynchronous or synchronous. Asynchronous events are the default. Synchronous events have not yet been designed, although one trial implementation has been done. Further work must be done to determine what kinds of synchronous events would be useful.

An event may optionally contain a parameter. The parameter consists of a string of arbitrary length. In a Tcl script, the parameter can be accessed via the ``eventArgs'' variable. This variable is intended to be similar to the ``args'' parameter passed to Tcl procedures.

See the Event System User's Guide for more information on how to use names, types, and parameters in events.

Asynchronous Events

The most commonly used type of event is an asynchronous announcement. For example, the ``change'' event mentioned above is asynchronous, which means that the sender broadcasts an event and immediately continues execution, without waiting for an acknowledgement or reply. It cannot determine whether any other processes received its event or not. When the event reaches receivers, they will immediately execute a Tcl command at the global level, in much the same way as a Tk event binding. If an event occurs during the execution of the command, a Tk error dialog box will pop up and inform the user that he is a moron.

Synchronous Events

Since I do not know what sorts of synchronization are necessary, I'll just use this section of the document to list the possibilities:

Acknowledgement: Same as asynchronous, but the sender waits until at least one recipient acknowledges receipt.

n-Acknowledgements: Same as asynchronous, but the sender waits until n recipients have acknowledged the event.

Handled: The sender blocks until someone handles the event. Each receiver may choose to either observe or handle. If multiple receivers try to handle the event, either an error could occur or it could be quietly ignored.

Send-Implicit Reply: The sender blocks until it receives a reply. The receiver behaves as in the asynchronous case, except that after it executes the callback command, it sends a reply back to the sender to unblock it. Similar to (1) Acknowledgement, above, except the reply is only sent after the command is executed (successfully?).

Send-Explicit Reply: Same as (4), but each recipient specifies (either at registration time or in the command) whether it will simply observe the event or will reply to the event. Sender is still blocked until at least one reply is received; multiple replies could either cause an error or be ignored.

Send-Receive-Reply: Completely synchronous. The sender blocks until it receives a reply. Receivers explicitly call a receive procedure and block until they get a message. Reply is also an explicit procedure call. Again, the effects of multiple repliers may be either an error or a no-op.

Note: In numbers 5 and 6, the difference between treating multiple replies as an error or a no-op makes a major semantic difference. Those cases should probably be split up.

3 Integration

This section intentionally left blank, with the following explanation: to see how to use the event system inside of a Fcl script, see the Event System User's Guide. For descriptions of how to integrate the event system into non-Fcl code, such as emacs, the synthesizer generator, or a C program, please time-travel into the future when we've actually done it and then reread this section.

4 Implementation

As mentioned in the overview, the event system is implemented on top of ToolTalk, Tcl, and Tk. ToolTalk provides the base messaging system. It provides a central server that keeps track of which processes are interested in which patterns, and figures out who receives each broadcast message, based on the patterns that have been registered with it. Tcl merely provides a convenient interface to the event system, and allows easy integration with the rest of the FAM system. Tk is used to poll the file descriptor that events arrive on, since it already has an internal main loop that handles other types of events. Tk is also used to report errors that occur during the commands that are executed on receipt of an event. Tk is not a necessary part of the event system; in fact, a version of Fcl without Tk but with the event system will be written RSN. This will allow a lightweight process to run whose only purpose is to catch events and act on them.

Initialization

When Fcl starts up, it calls event_Init as part of its initialization. The first thing it does is to call TT_Init, which really only calls TTInitializeSession (found in the file TTInterface.c). TTInitializeSession does the following:

Calls tt_open. tt_open is part of the ToolTalk API. It ensures that the ToolTalk server is running (and starts it running if it isn't). It also opens a connection to the server for the current process.

Calls tt_default_session. This tells the server which ToolTalk session the current process belongs to. By default, ToolTalk treats all processes using the same X display as members of the same session.

Initializes the global variables session_id and proc_id. These are strings which hold ToolTalk's representation of the current session and process. session_id is used later to insert patterns into the correct session. proc_id is never used, but may be useful for debugging.

Initializes the global variable event_fd with the stream descriptor which is connected to the ToolTalk server. When this file descriptor becomes active (determined through the use of select), a message is waiting to be received. This file descriptor will be passed to Tk so it can be checked during the main Tk event loop.

The next stage in initialization is the remainder of event_Init, which does the following:

Creates the Tcl command ``event''. When the command ``event'' is executed from a Tcl script, it will call the function event_EventDispatch, which will parse the parameters to figure out which C subroutine to call.

Initializes the two global hash tables callbackHashTable and patternHashTable. callbackHashTable contains a mapping from event names to Tcl command strings. When an event is received, this table provides the command to execute. patternHashTable contains a mapping from event names to ToolTalk patterns. These are initialized whenever a new event name is registered, and used only to unregister the event.

Calls Tk_CreateFileHandler to make Tk watch for incoming ToolTalk messages on the file descriptor event_fd. Whenever the file descriptor becomes active, Tk will call the procedure eventCallback.

At this point, the event system is fully set up and ready to be used.

Sending

When a Tcl script wants to send an event, the following things happen:

The script executes ``event broadcast <event name> ?arguments?''. This causes the procedure event_SendCmd to be invoked.

tt_message_create is called to create a message (which is now called an event, although I am horribly overloading the word event already).

TTInitEvent is called to initialize the fields of the ToolTalk message to the defaults for an event. This is performed through the ToolTalk procedures tt_message_<field>_set. For an event, the fields are set to:

op -- The name of the event

class -- TT_NOTICE. This means that the sender is merely publishing a notice, and does not care whether anyone receives it. The other option is TT_REQUEST, which causes tt_message_send to fail if no process handles the message.

scope -- TT_SESSION. This means that the event will be seen by everyone interested in the current session.

address -- TT_PROCEDURE. I don't remember exactly what the other values for this field do, but this is pretty much the `dummy' one -- it does what you would expect it to. Remember to change this comment.

<-- fix this number

If an argument was passed in, call tt_message_arg_add to insert it into the event.

Finally, call tt_message_send to ship the event off to the ToolTalk message server.

Receiving (use the new template everywhere, instead of starting halfway through)

To receive an event, a Tcl script executes the command ``event register <event name> <command>'', which causes event_RegisterCmd to be called. This does the following:

Checks the callbackHashTable to see if this event has already been registered. If so, returns an error.

Creates a new hash table entry keyed by the event name, and inserts the given callback command into it.

Creates a new ToolTalk pattern to match this event by calling tt_pattern_create and passing the result to TTInitPattern. This fills in the ToolTalk message fields to the necessary values:

op -- The name of the event to listen for.

category -- TT_OBSERVE, which means the receiever only observes the event, it doesn't handle it. Handling is a ToolTalk mechanism which is not used for asynchronous events.

class -- TT_CLASS_UNDEFINED. We want to receive events whether the sender regards them as notices or requests. Ignore this for now.

scope -- TT_SESSION. Ignore.

address -- TT_PROCEDURE. Ignore.

<-- fix this number too

Call TTRegisterPattern, which calls tt_pattern_register to tell the ToolTalk server that we want to receive messages which match the pattern we just built, and tt_session_join to add the current set of things we're registered for to the list of things scanned by the server for the current session. At least, I assume that's what it does.

Last, the newly-created pattern is added to the patternHashTable so that it can be unregistered sometime in the future.

The Tcl script now continues execution until an event arrives. Its arrival initializes the following sequence of actions:

Tk calls eventCallback when it notices that the event file descriptor has become active.

eventCallback calls tt_message_receive to read in the incoming message.

The callbackHashTable is indexed by the received event name to get the command to be executed for this event.

If the event contained an argument, the global variable ``eventArgs'' is set to the string contained in the received event.

The command is passed to Tcl to be executed at the global level.

If the execution resulted in a Tcl error, the procedure event_BackgroundError is called to disable further events, display an error dialog box, and re-enable incoming events after the user dismisses it.

Unregistration

Figure it out yourself. I'm running really short on time.


Last Modified: 11:22am EST, February 23, 1996