previous next top contents index framed top this page unframed
Figure 9–1. Interrupt-Catching PROCEDUREs
9.1. Enabling and Handling Interrupts on $tty
BOOLEAN
PROCEDURE $enableInterrupts
(OPTIONAL INTEGER
exitAfterThisMany);
PROCEDURE $disableInterrupts;
INTEGER
PROCEDURE $interruptsEnabled;
# Predefined identifier for the interrupt governed by the
# above PROCEDUREs:
# system variable
STRING $keyboardInterruptExcpt;
Currently, these PROCEDUREs work only on the physical TTY stream $tty (see Section 11.1) associated with a process. If multiwindow support is implemented, these PROCEDUREs may be extended to work on simulated TTY streams that refer to a window.
By default, keyboard interrupt catching through STREAMS is disabled. This means that the interrupt character typically either aborts the program or confirms the interrupt and then aborts the program.
When the PROCEDURE $enableInterrupts is called and the system supports catching interrupts on $tty, the PROCEDURE returns TRUE; interrupt catching through STREAMS is then enabled. If interrupt catching on $tty is not supported by the system, the PROCEDURE returns FALSE.
Once interrupt catching is enabled, the coroutine that most recently called $enableInterrupts (or possibly one of that coroutine's ancestors) may catch the exception $keyboardInterruptExcpt using a MAINSAIL exception handler. The exception is raised by the Scheduler.
exitAfterThisMany may be supplied to provide an “escape hatch” in case the Scheduler does not receive control for some reason (the program might be in a tight loop that does not involve scheduled I/O). The exitAfterThisMany value applies only to the physical terminal, not to (possible future) simulated $ttys, since the Scheduler will always be involved when generating a simulated interrupt.
If exitAfterThisMany physical keyboard interrupts occur without a rescheduling, the interrupt is processed in the default way as if the program had not issued the $enableInterrupts call (typically the program is aborted). If exitAfterThisMany is not given or is 0, 10 is used by default. If exitAfterThisMany is -1, the escape hatch feature is disabled. Programs that do $enableInterrupts(-1) must be very carefully written so that they never go into an infinite loop.
$disableInterrupts restores the previous behavior of keyboard interrupt handling. Calls to $enableInterrupts and $disableInterrupts may be nested, in which case $disableInterrupts restores the previous caller's parameters.
It is the programmer's responsibility to ensure that the exception $keyboardInterruptExcpt is actually handled. It is also the programmer's responsibility to ensure that a matching $disableInterrupts occurs for each $enableInterrupts. In particular, be sure to call $disableInterrupts if the $abortProcedureExcpt occurs in the handling coroutine.
$interruptsEnabled returns the nonzero value of exitAfterThisMany, or zero if interrupts are not currently enabled by the calling coroutine.
Example 9–2 shows the recommended form of a keyboard interrupt handler. Interrupt handling is enabled and disabled within the $HANDLE statement, to guarantee that there is always a handler for the keyboard interrupts. Interrupt handling is also disabled when $abortProcedureExcpt is raised, to clean up correctly in case the current PROCEDURE is aborted (e.g., due to an unanticipated error).
Example 9–2. Handling Keyboard Interrupts
| $HANDLEB $enableInterrupts; ... $disableInterrupts END $WITHB IF $exceptionName = $keyboardInterruptExcpt THENB IF ... THEN $raiseReturn EL disableInterrupts; # and fall out of handler END EF $exceptionName = $abortProcedureExcpt THENB $disableInterrupts; $raise END EL $raise END; |
Enabling may be dynamically stacked, that is, a PROCEDURE that was called by the first enabling PROCEDURE may also enable interrupts. Enabling may also be stacked with respect to coroutines; i.e., a coroutine that is either a descendant or an ancestor of all enabled coroutines may also enable. No other coroutines may enable; it must be the case that there is a direct line of coroutine ancestry through all enablers. An error message is issued if this rule is violated.
The coroutine of the most recent enabler receives the exception. If it does not handle $keyboardInterruptExcpt by falling out of the handler or by doing a $raiseReturn, the exception is propagated to the ancestor coroutines in the normal way. It is an error if the exception is not handled at all by any coroutines. In this case, the Scheduler disables the most recent enabler, and possibly its ancestors as well.
If all enablers disable, handling of interrupts is disabled, that is, the default action for keyboard interrupts (typically aborting the program) is restored.
Example 9–3. Catching Interrupts Asynchronously
| PROCEDURE commandInterpreter; BEGIN STRING cmd; DOB write(logFile,"Command: "); read(cmdFile,cmd); IF equ(cmd,"exit",upperCase) THEN RETURN EF isValidCmd(cmd) THENB ... END EL errMsg("Invalid command","",noResponse) END; END; PROCEDURE topLevel; BEGIN $enableInterrupts; DOB $HANDLEB commandInterpreter; DONE END $WITHB IF $exceptionName = $keyboardInterruptExcpt THEN # fall out (and restart loop) EF $exceptionName = $abortProcedureExcpt THENB $disableInterrupts; $raise END EL $raise END END; $disableInterrupts; END; |
This approach is similar to that taken by a system shell. The top-level PROCEDURE, topLevel, calls a command interpreter that read commands in a loop. If an interrupt occurs, the handler in the top level gains control and falls out of the handler, thus aborting the command interpreter and any nested PROCEDUREs it has called. At this point, topLevel continues its loop and starts a fresh command interpreter.
The command interpreter itself has no knowledge that it is running under an interrupt handler.
If the command interpreter (or a PROCEDURE that it calls) enables interrupts, it has the first chance to handle them. If it chooses not to handle them, the PROCEDURE topLevel is notified of an interrupt.
Although the use of this handler appears to allow true asynchronous
interrupt catching, the Scheduler gains control only during scheduled
I/O (e.g., to the $tty if it is scheduled).
Thus, this approach does not
allow the program to abort another coroutine that is in a tight loop
that does not do stream I/O
or otherwise give up control to the STREAMS
Scheduler.
In such cases, calls to the $reschedule PROCEDURE could be
inserted within the offending PROCEDURE
to allow the Scheduler to check
for interrupts.
9.5. Causing Keyboard Interrupts to Occur in a Child Process
When a stream is connected to a process through a PTY, the process can
be signaled to interrupt
using the PROCEDURE $writeStreamInterrupt.
In addition, PTY streams may have an associated character pty.$interrupt. If this character is not -1, it causes an interrupt if it is written to the PTY using $cWriteStream or as part of a $writeStream. It it has the value -1, it is not possible to interrupt the PTY process.
On some systems, a program running as a child process through a PTY may not be able to enable interrupt catching even though the program can enable them when running on an interactive terminal. On such systems, if the parent calls $writeStreamInterrupt, the child process is killed.
MAINSAIL STREAMS User's Guide, Chapter 9