MAINSAIL STREAMS User's Guide, Chapter 9

previous   next   top   contents   index   framed top   this page unframed


9. Handling Keyboard Interrupts

The STREAMS package provides the ability to detect interrupts generated on the physical $tty in a portable way, provided that this capability is supported by the underlying operating system. The interrupt is typically generated by typing some control character such as CTRL-C on the keyboard of the program's controlling terminal. Using an exception handler, a portable MAINSAIL program can handle such interrupts in what appears to be an asynchronous way.

9.1. Enabling and Handling Interrupts on $tty

Figure 9–1. Interrupt-Catching PROCEDUREs
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;

9.2. Stacking of $enableInterrupts

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.

9.3. Conflict between $enableInterrupt and the $noInterrupt Bit

The $noInterrupt bit in a read, if implemented, takes precedence over the state of enabled interrupts. Thus, if a program is set up to catch interrupts on $tty and a PROCEDURE running in any coroutine calls $readStream($tty,...,$noInterrupt...) on the same $tty, the interrupt characters are read as normal characters by $readStream. Interrupts remain turned off until an input from $tty occurs in which $noInterrupt is not given.

9.4. Sample Program That Catches Interrupts

Example 9–3 shows an interactive program (command interpreter) that catches interrupts.

Example 9–3. Catching Interrupts Asynchronously
PROCEDURE commandInterpreter;
BEGIN
STRING cmd;
DOB write(logFile,"Command: "); read(cmdFile,cmd);
    
IF equ(cmd,"exit",upperCaseTHEN RETURN
    
EF isValidCmd(cmdTHENB ... END
    
EL errMsg("Invalid command","",noResponseEND;
END;



PROCEDURE topLevel;
BEGIN
$enableInterrupts;
DOB $HANDLEB commandInterpreterDONE 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.


previous   next   top   contents   index   framed top   this page unframed

MAINSAIL STREAMS User's Guide, Chapter 9