Difference between revisions of "Event Handling in FreeM"
Line 156: | Line 156: | ||
FreeM employs an event queue for asynchronous events, shared across all event classes. External signals from the operating system will interrupt the flow of the FreeM C code, calling callbacks internal to FreeM that will enqueue an event, along with its event class, event identifier, and metadata, in the event queue, and interpreter execution will resume. If the event is not important enough to immediately interrupt the interpreter, the event queue will be checked and handlers run after the current M command completes. If the event is extremely important, FreeM will raise error condition <code>ZASYNC</code>. Once <code>ZASYNC</code> is raised, at the next checkpoint where FreeM checks for an error condition, the internal error handler will be invoked. When <code>$ECODE</code> is <code>ZASYNC</code>, FreeM will immediately de-queue the event queue and execute all pending event handlers prior to resuming normal program execution. | FreeM employs an event queue for asynchronous events, shared across all event classes. External signals from the operating system will interrupt the flow of the FreeM C code, calling callbacks internal to FreeM that will enqueue an event, along with its event class, event identifier, and metadata, in the event queue, and interpreter execution will resume. If the event is not important enough to immediately interrupt the interpreter, the event queue will be checked and handlers run after the current M command completes. If the event is extremely important, FreeM will raise error condition <code>ZASYNC</code>. Once <code>ZASYNC</code> is raised, at the next checkpoint where FreeM checks for an error condition, the internal error handler will be invoked. When <code>$ECODE</code> is <code>ZASYNC</code>, FreeM will immediately de-queue the event queue and execute all pending event handlers prior to resuming normal program execution. | ||
+ | |||
+ | =References= | ||
+ | <references/> |
Latest revision as of 14:47, 20 December 2024
FreeM implements synchronous event handling as defined in ANSI X11.6-1995 (MWAPI) and asynchronous event handling as proposed in MDC Type A extension proposal X11/1998-28[1], with several significant vendor-specific extensions. Though the M Development Committee’s use of the terms “synchronous” and “asynchronous” are technically correct, the MWAPI and X11/1998-28 event handling models’ use of the terms may seem somewhat unusual or foreign to those accustomed to event handling in world-wide web technologies, such as JavaScript. The remainder of this article will explore in some depth the X11/1998-28 and MWAPI event handling models as well as the architecture with which FreeM implements and extends them.
Synchronous Events
In M parlance, a synchronous event is one originating from a graphical user interface defined in the M Windowing API (MWAPI). To begin accepting and processing synchronous events, normal, procedural M code must execute the ESTART
command, which implicitly enters an event processing loop. ESTART
will block the flow of M code execution on the code path in which ESTART
was invoked: M code immediately following ESTART
will not execute until a synchronous event handler subroutine outside the primary code path of the application calls ESTOP
to stop the implicit event processing loop.
Synchronous event handlers are typically registered in the ^$WINDOW
structured system variable. The following code will create the window with ID myWindow
, register CLOSE^WINDOW
as the event handler for the CLOSE
event class on window ID myWindow
–called when the close gadget is pressed or the window is closed by other means. It will then begin the implicit synchronous event processing loop:
WINDOW ; SET W("EVENT","CLOSE")="CLOSE^WINDOW" ; Create a window definition MERGE ^$WINDOW("myWindow")=W ; After this MERGE, the window will appear ESTART ; This enters the implicit event processing loop QUIT ; Will not execute until CLOSE^WINDOW calls ESTOP ; ; CLOSE ; ESTOP ; Stop synchronous event processing QUIT
Other metadata about the CLOSE
event, including the window ID and window type (among others) would be supplied to CLOSE^WINDOW
by populating nodes of the ^$EVENT
structured system system variable, which is implicitly NEW
ed prior to the relevant nodes being populated and CLOSE^WINDOW
being invoked.
In FreeM, the ESTART
event processing loop for the above code sample takes the following steps:
- Check if
ESTOP
has been called. If so, exit the event processing loop and proceed to the next command followingESTART
. - Wait for OSF/Motif to have a user interface event in its own internal queue. During this step, the FreeM thread in which the event loop runs goes to sleep. At this point, the user closes window myWindow.
- FreeM checks the event information received from OSF/Motif against FreeM’s table of windows, and see if a
CLOSE
event handler has been registered for this window ID (myWindow). If so, FreeM will implicitly executeNEW ^$EVENT
, populate the SSVN with metadata about the event and window from which the event originated, and then execute the M subroutine specified at^$WINDOW(“myWindow”,”EVENT”,”CLOSE”)
. When that subroutine (in this case,CLOSE^WINDOW
) exits, FreeM will return to the top of the event processing loop (step 1). If not, ignore the event and return to step 1. In the above case, this does not apply, as CLOSE^WINDOW was defined as an event handler for event class CLOSE on window ID myWindow.
The above example illustrates how, from the perspective of ESTART
, this type of event processing is indeed synchronous. However, while ESTART
(and thus the MWAPI event loop) is in control, user interface events are still processed asynchronously by the underlying windowing system. This can be confusing, as MWAPI events ride the wire between low-level and high-level concepts, requiring the developer to be at least somewhat familiar with both.
MWAPI–and therefore synchronous events in M–preceded the development of the asynchronous events specification, and unlike asynchronous events, are codified in published and existing MDC standards: specifically, ANSI X11.6.
Asynchronous Events
From the perspective of the M Development Committee, asynchronous event processing exists only as a Type A Extension–specifically, extension X11/1998-28. This extension was proposed by Arthur B. Smith in September 1996 and elevated to Type A extension status–as document X11/SC15/1998-6–in June 1998 at an MDC meeting in Boston, MA. As of this writing, FreeM is the only implementation known to have implemented any part of this proposal.
Event Classes
Each asynchronous event is broadly categorized into one of several event classes, referred to as evclasses in relevant standards. FreeM event classes are as follows:
Event Class | Description |
---|---|
COMM | Allows application code to respond to communications events |
HALT | Allows applications to handle HALT events |
IPC | Supports inter-process communication |
INTERRUPT | Allows applications to respond to operating system interrupt signals |
POWER | Intended to allow applications to respond to imminent power failure messages from uninterruptible power supplies |
TIMER | Supports the asynchronous execution of an M subroutine after a specified time has elapsed |
TRIGGER (non-standard) | Allows an M subroutine to run when data in an M global is accessed, changed, or deleted |
USER | Designed to support user-defined events |
WAPI | Reserved for MWAPI events–MWAPI only supports synchronous event processing at the time of writing |
Event Identifiers
Beyond the event class, events are further categorized into specific event identifiers, referred to in relevant standards as evids. Event identifiers are often used as a sort of sub-type within a particular event class. Therefore, a particular, specific event is identified by the pairing of its event class and its event identifier.
In short, event classes indicate broad categories of events, while event identifiers indicate specific types of events within an event class.
Registering Asynchronous Event Handlers
Registering an event handler is the mechanism by which the M programmer associates an event class and event identifier with an M subroutine that the M implementation will execute when that event occurs. For example, if we wanted to run the RESIZE^ASNCDEMO
M routine any time the user’s terminal window is resized, we would want to handle an event with event class INTERRUPT
, and event identifier SIGWINCH
. The following code will associate the above event class and identifier with the RESIZE^ASNCDEMO
subroutine:
ASNCDEMO ; SET ^$JOB($JOB,"EVENT","INTERRUPT","SIGWINCH")="RESIZE^ASNCDEMO" QUIT ; RESIZE ; WRITE "The terminal was resized!",! QUIT
Much like synchronous events, metadata about asynchronous events–if any such metadata exists–is populated in the ^$EVENT
structured system variable. As an explanation of all possible subscripts and values of ^$EVENT
is far beyond the scope of this article, you are encouraged to consult the FreeM Manual for further information.
Starting and Stopping Asynchronous Event Processing
Though the action of the code above will associate an M subroutine with an event class and identifier, this alone will not cause the M implementation to begin processing asynchronous events. Much like ESTART
begins processing synchronous events, ASTART
must be run before asynchronous event processing can occur. The ASTART
command looks like this:
ASTART:postcondition [[evclass,...] | [(evclass,...)]
As is typical with M commands, ASTART
supports argumentless, inclusive, and exclusive forms. In its argumentless form, ASTART
will begin asynchronous event processing for all event classes. In its inclusive form, ASTART
will begin asynchronous event processing for only the specified event classes. Finally, the exclusive form of ASTART
begins asynchronous event processing for all event classes except those specified.
Let’s further flesh out our ASNCDEMO
routine to enable asynchronous event processing for the INTERRUPT event class:
ASNCDEMO ; SET ^$JOB($JOB,"EVENT","INTERRUPT","SIGWINCH")="RESIZE^ASNCDEMO" ASTART "INTERRUPT" QUIT ; RESIZE ; WRITE "The terminal was resized!",! QUIT
While the above code will definitely enable asynchronous event processing for INTERRUPT
events, the user would never see any output from the event handler, as the program would quit prior to any event occurring: unlike ESTART
for synchronous events, ASTART
is always non-blocking. Therefore, in the above example, ASTART “INTERRUPT”
will enable asynchronous event processing for INTERRUPT
events and return immediately. As the next command in the routine is QUIT
, the routine will immediately exit. The non-blocking nature of ASTART
is a primary reason why asynchronous events in M are so named: they do not block the primary code path or enter an implicit event loop.
Due to the non-blocking nature of ASTART
, asynchronous event processing in M probably makes the most sense for applications that provide their own loop: for instance, an application that displays a menu, accepts a selection, performs processing, and then re-displays its menu, or, an application that runs in an I/O loop gathering data, processing it, and storing results.
Blocking and Unblocking Asynchronous Events
Each asynchronous event class is paired with an event block counter specific to that event class. This counter is a simple integer, and when nonzero, rather than executing the M subroutine associated with that event class, when an event of that class occurs, it will instead be queued for later processing. This mechanism is implicitly employed on invocation of an event handler subroutine: prior to entering the event handler, the event block counters for all event classes are incremented by one, ensuring that the execution of one event handler can never be interrupted by the execution of another. Similar to M’s incremental LOCK, event blocking is also incremental, as the block counter for an event’s event class must be zero in order for its event handlers to execute.
Event blocking and unblocking can also be achieved manually via the ABLOCK
and AUNBLOCK
commands, whose syntax are thus:
ABLOCK:postcondition [[evclass,...] | [(evclass,...)] AUNBLOCK:postcondition [[evclass,...] | [(evclass,...)]
In their argumentless forms, ABLOCK
and AUNBLOCK
will increment or decrement the event block counters for all event classes. In their inclusive forms, they will increment or decrement the event block counters for only the specified event classes. In their exclusive forms, they will increment or decrement the event block counters for all event classes except those listed.
Remember earlier, when we mentioned that an argumentless ABLOCK
is implicitly executed prior to entering an event handler subroutine, in order to prevent asynchronous event handlers from interrupting each other. Although not a feature for either the faint of heart or those without exceptionally sharp minds for writing reentrant code, it is possible (though not generally recommended) to AUNBLOCK
one or more event event classes inside of an event handler to enable such reentrant behavior. The pitfalls and risks to logical integrity of M globals are so great that you should do so only with a preponderance of caution and prodigious and careful use of LOCK
around global variable accesses in such event handlers: here there be dragons!
FreeM Extension: System-Wide Asynchronous Events
In FreeM, the X11/1998-28 extension has been extended to support events that will be recognized in all FreeM processes within the current running environment, rather than being limited to the current process only. The only difference is in the registering of event handlers: rather than registering handlers in ^$JOB($JOB,”EVENT”,…)
, system-wide event handlers are registered in ^$SYSTEM(“EVENT”,…)
.
FreeM Asynchronous Event Handling Architecture
FreeM employs an event queue for asynchronous events, shared across all event classes. External signals from the operating system will interrupt the flow of the FreeM C code, calling callbacks internal to FreeM that will enqueue an event, along with its event class, event identifier, and metadata, in the event queue, and interpreter execution will resume. If the event is not important enough to immediately interrupt the interpreter, the event queue will be checked and handlers run after the current M command completes. If the event is extremely important, FreeM will raise error condition ZASYNC
. Once ZASYNC
is raised, at the next checkpoint where FreeM checks for an error condition, the internal error handler will be invoked. When $ECODE
is ZASYNC
, FreeM will immediately de-queue the event queue and execute all pending event handlers prior to resuming normal program execution.