Chapter 21. Interrupts
The MDL interrupt handling facilities provide the ability to say the
following: whenever "this event" occurs, stop whatever is being done
at the time and perform "this action"; when "this action" is finished,
continue with whatever was originally being done. "This event" can be
things like the typing of a character at a terminal, a time interval
ending, a PROCESS becoming blocked, or a program-defined and
-generated "event". "This action" is the application of a specified
APPLICABLE object to arguments provided by the MDL interrupt system.
The sets of events and actions can be changed in extremely flexible
ways, which accounts for both the variety of SUBRs and arguments,
and the rich interweaving of the topics in this chapter. Interrupt
handling is a kind of parallel processing: a program can be divided
into a "main-level" part and one or more interrupt handlers that
execute only when conditions are ripe.
21.1. Definitions of Terms
An interrupt is not an object in MDL, but rather a class of events, for example, "ticks" of a clock, garbage collections, the typing of a character at a terminal, etc.
An interrupt is said to occur when one of the events in its class takes place.
An external interrupt is one whose occurrences are signaled to MDL by the operating system, for example, "ticks" of a clock. An internal interrupt is one whose occurrences are detected by MDL itself, for example, garbage collections. MDL can arrange for the operating system to not signal occurrences of an external interrupt to it; then, as far as MDL is concerned, that interrupt does not occur.
Each interrupt has a name which is either a STRING (for example,
"GC", "CHAR", "WRITE") or an ATOM with that PNAME in a
special OBLIST, named INTERRUPTS!-. (This OBLIST is returned by
<INTERRUPTS>.) Certain names must always be further specified by a
CHANNEL or a LOCATIVE to tell which interrupt by that name is
meant.
When an interrupt occurs, the interpreter looks for an association on
the interrupt's name. If there is an association, its AVALUE should
be an IHEADER, which heads a list of actions to be performed. In
each IHEADER is the name of the interrupt with which the IHEADER
is or was associated.
In each IHEADER is an element telling whether it is disabled. If an
IHEADER is disabled, then none of its actions is performed.
The opposite of disabled is enabled. It is sometimes useful to
disable an IHEADER temporarily, but removing its association with
the interrupt's name is better than long-term disabling. There are
SUBRs for creating an IHEADER, associating it with an interrupt,
and later removing the association.
In each IHEADER is a priority, a FIX greater than 0 which
specifies the interrupt's "importance". The processing of a
higher-priority (larger-numbered) interrupt will supersede the
processing of a lower-priority (smaller-numbered) interrupt until the
high-priority interrupt has been handled.
In each IHEADER is a (possibly empty) list of HANDLERs. (This list
is not a MDL LIST.) Each HANDLER corresponds to an action to
perform. There are SUBRs for creating a HANDLER, adding it to an
IHEADER's list, and later removing it.
In each HANDLER is a function that we will call a handler (in
lower case), despite possible confusion, because that is really the
best name for it. An action consists of applying a handler to
arguments supplied by the interrupt system. The number and meaning of
the arguments depend on the name of the interrupt. In each HANDLER
is an element telling in which PROCESS the action should be
performed.
21.2. EVENT
<EVENT name priority which>
creates and returns an enabled IHEADER with no HANDLERs. The
name may be an ATOM in the INTERRUPTS OBLIST or a STRING; if
it is a STRING, EVENT does a LOOKUP or INSERT in
<INTERRUPTS>. If there already is an IHEADER associated with
name, EVENT just returns it, ignoring the given priority.
which must be given only for certain names:
- It must be a
CHANNELif and only if name is"CHAR"(orCHAR!-INTERRUPTS). In this case it is the inputCHANNELfrom the (pseudo-)terminal or Network socket whose received characters will cause the interrupt to occur, or the outputCHANNELto the pseudo-terminal or Network socket whose desired characters will cause the interrupt to occur. (See below. Pseudo-terminals are not available in the Tenex and Tops-20 versions.) - The argument must be a
LOCATIVEif and only if name is"READ"(orREAD!-INTERRUPTS) or"WRITE"(orWRITE!-INTERRUPTS). In this case it specifies an object to be "monitored" for usage by (interpreted) MDL programs (section 21.8.9).
If the interrupt is external, MDL arranges for the operating system to signal its occurrences.
21.3. HANDLER (the SUBR)
<HANDLER iheader applicable process>
creates a HANDLER, adds it to the front of iheader's HANDLER
list (first action to be performed), and returns it as a value.
applicable may be any APPLICABLE object that takes the proper
number of arguments. (None of the arguments can be QUOTEd; they must
all be evaluated at call time.) process is the PROCESS in which
the handler will be applied, by default whatever PROCESS was running
when the interrupt occurred.
The value returned by the handler is ignored, unless it is of TYPE
DISMISS (PRIMTYPE ATOM), in which case none of the remaining
actions in the list will be performed.
The processing of an interrupt's actions can terminate prematurely if
a handler calls the SUBR DISMISS (see below.)
21.4. OFF
<OFF iheader>
removes the association between iheader and the name of its interrupt, and then disables iheader and returns it. (An error occurs if there is no association.) If the interrupt is external, MDL arranges for the operating system not to signal its occurrences.
<OFF name which>
finds the IHEADER associated with name and proceeds as above,
returning the IHEADER. which must be given only for certain
names, as for EVENT. Caution: if you <OFF "CHAR" ,INCHAN>, MDL
will become deaf.
<OFF handler>
returns handler after removing it from its list of actions. There is
no effect on any other HANDLERs in the list.
Now that you know how to remove IHEADERs and HANDLERs from their
normal places, you need to know how to put them back:
<EVENT iheader>
If iheader was previously disabled or disassociated from its name,
EVENT will associate and enable it.
<HANDLER iheader handler>
If handler was previously removed from its list, HANDLER will add
it to the front of iheader's list of actions. Note that process
cannot be specified.
21.5. IHEADER and HANDLER (the TYPEs)
Both these TYPEs are of PRIMTYPE VECTOR, but they do not PRINT
that way, since they are self-referencing. Instead they PRINT as
#type most-interesting-component
The contents of IHEADERs and HANDLERs can be changed by PUT, and
the new values will then determine the behavior of MDL.
Before describing the elements of these TYPEs in detail, here are a
picture and a Pattern, both purporting to show how they look:
#IHEADER [name:atom or which
disabled?
*-----------> #HANDLER [*-----------> #HANDLER [#HANDLER []
priority] <-------------* +------*
applicable | applicable
process] <-------+ process]
<IHEADER <OR ATOM CHANNEL LOCATIVE>
<OR '#LOSE 0 '#LOSE -1>
<HANDLER HANDLER <OR HANDLER IHEADER> APPLICABLE PROCESS>
FIX>
21.5.1. IHEADER
The elements of an IHEADER are as follows:
- name of interrupt (
ATOM, orCHANNELif the name is"CHAR", orLOCATIVEif the name is"READ"or"WRITE") - non-zero if and only if disabled
- first
HANDLER, if any, else a zero-lengthHANDLER - priority
If you lose track of an IHEADER, you can get it via the association:
- For
"CHAR"interrupts,<GET channel INTERRUPT>returns theIHEADERor#FALSE ()if there is no association;<EVENT "CHAR" 0 channel>returns theIHEADER, creating it if there is no association. - For
"READ"interrupts,<GET locative READ!-INTERRUPTS>returns theIHEADERor#FALSE ()if there is no association;<EVENT "READ" 0 locative>returns theIHEADER, creating it if there is no association. - For
"WRITE"interrupts,<GET locative WRITE!-INTERRUPTS>returns theIHEADERor#FALSE ()if there is no association:<EVENT "WRITE" 0 locative>returns theIHEADER, creating it if there is no association. - Otherwise, the
IHEADERisPUTon the nameATOMwith the indicatorINTERRUPT. Thus, for example,<GET CLOCK!-INTERRUPTS INTERRUPT>returns theIHEADERfor the clock interrupt or#FALSE ()if there is no association;<EVENT "CLOCK" 0>returns theIHEADER, creating it if there is no association.
21.5.2. HANDLER
A HANDLER specifies a particular action for a particular
interrupt. The elements of a HANDLER are as follows:
- next
HANDLERif any, else a zero-lengthHANDLER - previous
HANDLERor theIHEADER(Thus theHANDLERs of a given interrupt form a "doubly-linked list" chaining between each other and back to theIHEADER.) - handler to be applied (anything but
APPLICABLEthat evaluates its arguments -- the application is done not byAPPLYbut byRUNINT, which can take aPROCESSargument: see next line) PROCESSin which the handler will be applied, or#PROCESS 0, meaning whateverPROCESSwas running when the interrupt occurred (In the former case,RUNINTis applied to the handler and its arguments in the currently runningPROCESS, which causes anAPPLYin thePROCESSstored in theHANDLER, whichPROCESSmust beRESUMABLE. The runningPROCESSbecomesRESUMABLE, and the storedPROCESSbecomesRUNNING, but no otherPROCESSvariables (for exampleRESUMER) are changed.)
21.6. Other SUBRs
<ON name applicable priority:fix process which>
is equivalent to
<HANDLER <EVENT name priority which>
applicable process>
ON is a combination of EVENT and HANDLER: it creates (or finds)
the IHEADER, associates and enables it, adds a HANDLER to the
front the list (first to be performed), and returns the HANDLER.
<DISABLE iheader>
is effectively <PUT iheader 2 #LOSE -1>. Actually the TYPE LOSE
is unimportant, but the -1 signifies that iheader is disabled.
<ENABLE iheader>
is effectively <PUT iheader 2 #LOSE 0>. Actually the TYPE LOSE
is unimportant, but the 0 signfies that iheader is enabled.
21.7. Priorities and Interrupt Levels
At any given time there is a defined interrupt level. This is a
FIX which determines which interrupts can really "interrupt" -- that
is, cause the current processing to be suspended while their wants are
satisfied. Normal, non-interrupt programs operate at an interrupt
level of 0 (zero.) An interrupt is processed at an interrupt level
equal to the interrupt's priority.
21.7.1. Interrupt Processing
Interrupts "actually" only occur at well-defined points in time:
during a call to a Subroutine, or at critical places within
Subroutines (for example, during each iteration of MAPF on a LIST,
which may be circular), or while a PROCESS is "BLOCKED" (see
below). No interrupts can occur during garbage collection.
What actually happens when an enabled interrupt occurs is that the priority of the interrupt is compared with the current interrupt level, and the following is done:
If the priority is greater than the current interrupt level, the current processing is "frozen in its tracks" and processing of the action(s) specified for that interrupt begins.
If the priority is less than or equal to the current interrupt level, the interrupt occurrence is queued -- that is, the fact that it occurred is saved away for processing when the interrupt level becomes low enough.
When the processing of an interrupt's actions is completed, MDL usually (1) "acts as if" the previously-existing interrupt level is restored, and processing continues on what was left off (perhaps for no time duration); and (2) "acts as if" any queued interrupt occurrences actually occurred right then, in their original order of occurrence.
21.7.2. INT-LEVEL
The SUBR INT-LEVEL is used to examine and change the current
interrupt level directly.
<INT-LEVEL>
simply returns the current interrupt level.
<INT-LEVEL fix>
changes the interrupt level to its argument and returns the previously-existing interrupt level.
If INT-LEVEL lowers the priority of the interrupt level, it does not
"really" return until all queued occurrences of interrupts of higher
priority than the target priority have been processed.
Setting the INT-LEVEL extremely high (for example, <INT-LEVEL
<CHTPE <MIN> FIX>>) effectively disables all interrupts (but
occurrences of enabled interrupts will still be queued).
If LISTEN or ERROR is called when the INT-LEVEL is not zero,
then the typeout will be
LISTENING-AT-LEVEL I PROCESS p INT-LEVEL i
21.7.3. DISMISS
DISMISS permits a handler to return an arbitrary value for an
arbitrary ACTIVATION at an arbitrary interrupt level. The call is as
follows:
<DISMISS value:any activation int-level:fix>
where only the value is required. If activation is omitted, return
is to the place interrupted from, and value is ignored. If
int-level is omitted, the INT-LEVEL prior to the current interrupt
is restored.
21.8. Specific Interrupts
Descriptions of the characteristics of particular "built-in" MDL
interrupts follow. Each is named by its STRING name. Expect this
list to be incomplete yesterday.
"CHAR" is currently the most complex built-in interrupt, because it
serves duty in several ways. These different ways will be described in
several different sections. All ways are concerned with characters or
machine words that arrive or depart at unpredictable times, because
MDL is communicating with a person or another processor. Each "CHAR"
IHEADER has a CHANNEL for the element that names the interrupt,
and the mode of the CHANNEL tells what kinds of "CHAR" interrupts
occur to be handled through that IHEADER.
- If the
CHANNELis forINPUT, "CHAR" occurs every time an "interesting" character (see below) is received from theCHANNEL's real terminal, or any character is received from theCHANNEL's pseudo-terminal, or a character or word is received from theCHANNEL's Network socket, or indeed (in the ITS version) the operating system generates an interrupt for any reason. - If the
CHANNELis for output to a pseudo-terminal or Network socket,"CHAR"occurs every time a character or word is wanted. - If the
CHANNELis for output to a terminal,"CHAR"occurs every time a line-feed character is output or (in the ITS version) the operating system generates a screen-full interrupt for the terminal.
21.8.1. "CHAR" received
A handler for an input "CHAR" interrupt on a real terminal must take
two arguments: the CHARACTER which was typed, and the CHANNEL on
which it was typed.
In the ITS version, the "interesting" characters are those "enabled for interrupts" on a real terminal, namely ^@ through ^G, ^K through ^_, and DEL (that is, ASCII codes 0-7, 13-37, and 177 octal.)
In the Tenex and Tops-20 versions, the operating system can be told
which characters typed on a terminal should cause this interrupt to
occur, by calling the SUBR ACTIVATE-CHARS with a STRING
argument containing those characters (no more than six, all with
ASCII codes less than 33 octal). If called with no argument,
ACTIVATE-CHARS returns a STRING containing the characters that
currently interrupt. Initially, only ^G, ^S,
and ^O interrupt.
An initial MDL already has "CHAR" enabled on ,INCHAN with a
priority 8 (eight), the SUBR QUITTER for a handler to run in
#PROCESS 0 (the running PROCESS); this is how ^G and
^S are processed. In addition, every time a new
CHANNEL is OPENed in "READ" mode to a terminal, a similar
IHEADER and HANDLER are associated with that new CHANNEL
automatically. These automatically-generated IHEADERs and
HANDLERs use the standard machinery, and they can be DISABLEd or
OFFed at will. However, the IHEADER for ,INCHAN should not
be OFFed: MDL knows that $ is typed only by an interrupt!
Example: the following causes the given message to be printed out
whenever a ^Y is typed on .INCHAN:
<SET H <HANDLER <GET .INCHAN INTERRUPT>
#FUNCTION ((CHAR CHAN)
#DECL ((VALUE) ANY (CHAR) CHARACTER (CHAN) CHANNEL)
<AND <==? .CHAR !\^Y>
<PRINC " [Some of the best friends are ^Ys.] ">>)>>$
#HANDLER #FUNCTION **CHAR CHAN) ...)
<+ 2 ^Y [Some of my best friends are ^Ys.] 2>$
4
<OFF .H>$
#HANDLER #FUNCTION (...)
Note that occurrences of "CHAR" do not wait for the $ to be
typed, and the interrupting character is omitted from the input
stream.
A "CHAR" interrupt can also be associated with an input CHANNEL
open to a Network socket ("NET" device). A handler gets applied to
a NETSTATE array (which see) and the CHANNEL.
In the ITS version, a "CHAR" interrupt can also be associated with
an input CHANNEL open to a pseudo-terminal ("STY" device and
friends). An interrupt occurs when a character is available for
input. These interrupts are set up in exactly the same way as
real-terminal interrupts, except that a handler gets applied to only
one argument, the CHANNEL. Pseudo-terminal are not available in
the Tenex and Tops-20 versions.
For any other flavor of ITS channel interrupt, a handler gets applied
to only one argument, the CHANNEL.
21.8.2. "CHAR" wanted
A "CHAR" interrupt can be associated with an output CHANNEL open
to a Network socket ("NET" device). A handlers gets applied to a
NETSTATE array (which see) and the CHANNEL.
In the ITS version, a "CHAR" interrupt can also be associated with
an output CHANNEL open to a pseudo-terminal ("STY" device and
friends). An interrupt occurs when the program at the other end needs
a character (and the operating-system buffer is empty). A handler gets
applied to one argument, the CHANNEL. Pseudo-terminals are not
available in the Tenex and Tops-20 versions.
21.8.3. "CHAR" for new line
A handler for an output "CHAR" interrupt on a real terminal must
take one or two arguments (using "OPTIONAL" or "TUPLE"): if
two arguments are supplied by the interrupt system, they are the line
number (FIX) and the CHANNEL, respectively, and the interrupt is
for a line-feed; if only one argument is supplied (only in the ITS
version), it is the CHANNEL, and the interrupt is for a full
terminal screen. Note: the supplied line number comes from the
CHANNEL, and it may not be accurate if the program alters it in
subtle ways, for example, via IMAGE calls or special control
characters. (The program can compensate by putting the proper line
number into the CHANNEL.)
21.8.4. "GC"
"GC" occurs just after every garbage collection. Enabling this
interrupt is the only way a program can know that a garbage collection
has occurred. A handler for "GC" takes three arguments. The first is
a FLOAT indicating the number of seconds the garbage collection took.
The second argument is a FIX indicating the cause of the garbage
collection, as follows (chapter 22):
- Program called GC.
- Movable storage was exhausted.
- Control stack overflowed.
- Top-level LVALs overflowed.
- GVAL vector overflowed.
- TYPE vector overflowed.
- Immovable garbage-collected storage was exhausted.
- Internal stack overflowed.
- Both control and internal stacks overflowed (rare).
- Pure storage was exhausted.
- Second, exhaustive garbage collection occurred.
The third argument is an ATOM indicating what initiated the garbage
collection: GC-READ, BLOAT, GROW, LIST, VECTOR, SET,
SETG, FREEZE, GC, NEWTYPE, PURIFY, PURE-PAGE-LOADER (pure
storage was exhausted), or INTERRUPT-HANDLER (stack overflow,
unfortunately).
21.8.5. "DIVERT-AGC"
"DIVERT-AGC" ("Automatic Garbage Collection") occurs just before
a deferrable garbage collection that is needed because of exhausted
movable garbage-collected storage. Enabling this interrupt is the only
way a program can know that a garbage collection is about to occur. A
handler takes two arguments: A FIX telling the number of machine
words needed and an ATOM telling what initiated the garbage
collection (see above). If it wishes, a handler can try to prevent a
garbage collection by calling BLOAT with the FIX argument. If the
pending request for garbage-collected storage cannot then be
satisfied, a garbage collection occurs anyway. AGC-FLAG is SET to
T while the handler is running, so that new storage requests do not
try to cause a garbage collection.
21.8.6. "CLOCK"
"CLOCK", when enabled, occurs every half second (the ITS
"slow-clock" tick.) It is not available in the Tenex or Tops-20
versions. It wants handlers which take no arguments. Example:
<ON "CLOCK" <FUNCTION () <PRINC "TICK ">> 1>
21.8.7. "BLOCKED"
"BLOCKED" occurs whenever any PROCESS (not only the PROCESS
which may be in a HANDLER) starts waiting or terminal input: that
is, an occurrence indicates that somewhere, somebody did a READ,
READCHR, NEXTCHR, TYI, etc. to a console. The handler for a
"BLOCKED" interrupt should take one argument, namely the PROCESS
which started waiting (which will also be the PROCESS in which the
handler runs, if no specific one is in the HANDLER).
Example: the following will cause MDL to acquire a * prompting
character.
<ON "BLOCKED" #FUNCTION ((IGNORE) <PRINC !\*>) 5>
21.8.8. "UNBLOCKED"
"UNBLOCKED" occurs whenever a $ (ESC) is typed on a
terminal if a program was hanging and waiting for input, or when a
TYI call (which see) is satisfied. A handler takes one argument: the
CHANNEL via which the $ or character is input.
21.8.9. "READ" and "WRITE"
"READ" and "WRITE" are associated with read or write references to
MDL objects. These interrupts are often called "monitors", and
enabling the interrupt is often called "monitoring" the associated
object. A "read reference" to an ATOM's local value includes
applying BOUND? or ASSIGNED? to the ATOM; similarly for a global
value and GASSIGNED?. If the INT-LEVEL is too high when "READ"
or "WRITE" occurs, an error occurs, because occurrences of these
interrupts cannot be queued.
Monitors are set up with EVENT or ON, using a locative to the
object being monitored as the extra which argument, just as a
CHANNEL is given for "CHAR". A handler for "READ" takes two
arguments: the locative and the FRAME of the function application
that make the reference. A handler for "WRITE" takes three
arguments: the locative, the new value, and the FRAME. For example:
<SET A (1 2 3)>$
(1 2 3)
<SET B <AT .A 2>>$
#LOCL 2
<ON "WRITE" <FUNCTION (OBJ VAL FRM)
#DECL ((VALUE VAL ANY (OBJ) LOCATIVE (FRM) FRAME)
<CRLF>
<PRINC "Program changed ">
<PRIN1 .OBJ>
<PRINC " to ">
<PRIN1 .VAL>
<PRINC " via ">
<PRINC .FRM>
<CRLF>>
4 0 .B>$
#HANDLER FUNCTION (...)
<1 .A 10>$
(10 2 3)
<2 .A 20>$
Program changed #LOCL 2 to 20 via #FRAME PUT
(10 20 3)
<OFF "WRITE" .B>$
#IHEADER #LOCL 20
21.8.10. "SYSDOWN"
"SYSDOWN" occurs when a system-going-down or system-revived signal
is received from ITS. It is not available in the Tenex or Tops-20
versions. If no IHEADER is associated and enabled, a warning message
is printed on the terminal. A handler takes one argument: a FIX
giving the number of thirtieths of a second until the shutdown (-1
for a reprieve).
21.8.11. "ERROR"
In an effort to simplify error handling by programs, MDL has a
facility allowing errors to be handled like interrupts. SETGing
ERROR to a user function is a distasteful method, not safe if any
bugs are around. An "ERROR" interrupt wants a handler that takes any
number of arguments, via "TUPLE". When an error occurs, handlers are
applied to the FRAME of the ERROR call and the TUPLE of ERROR
arguments. If a given handler "takes care of the error", it can
ERRET with a value from the ERROR FRAME, after having done
<INT-LEVEL 0>. If no handler takes care of the error, it falls into
the normal ERROR.
If an error occurs at an INT-LEVEL greater than or equal to that of
the "ERROR" interrupt, real ERROR will be called, because
"ERROR"interrupts cannot be queued.
21.8.12. "IPC"
"IPC" occurs when a message is received on the ITS IPC device
(chapter 23). It is not available in the Tenex and Tops-20 versions.
21.8.13. "INFERIOR"
"INFERIOR" occurs when an inferior ITS process interrupts the MDL
process. It is not available in the Tenex and Tops-20 versions. A
handler takes one argument: A FIX between 0 and 7 inclusive,
telling which inferior process is interrupting.
21.8.14. "RUNT and "REALT"
These are not available in the Tenex and Tops-20 versions.
"RUNT", if enabled, occurs once, N seconds of MDL running
time (CPU time) after calling <RUNTIMER N:fix-or-float>, which
returns its argument. A handler takes no arguments. If RUNTIMER is
called with no argument, it returns a FIX, the number of run-time
seconds left until the interrupt occurs, or #FALSE () if the
interrupt is not going to occur.
"REALT", if enabled, occurs every N seconds of real-world time
after calling <REALTIMER N:fix-or-float>, which returns its
argument. A handler takes no arguments. <REALTIMER 0> tells the
operating system not to generate real-time interrupts. If REALTIMER
is called with no argument, it returns a FIX, the number of
real-time seconds given in the most recent call to REALTIMER with an
argument, or #FALSE () if REALTIMER has not been called.
21.8.15. "Dangerous" Interrupts
"MPV" ("memory protection violation") occurs if MDL tries to refer
to a storage address not in its address space. "PURE" occurs if MDL
tries to alter read-only storage. "ILOPR" occurs if MDL executes and
illegal instruction ("operator"). "PARITY" occurs if the CPU detects
a parity error in MDL's address space. All of these require a handler
that takes one argument: the address (TYPE WORD) following the
instruction that was being executed at the time.
"IOC" occurs if MDL tries to deal illegally with an I/O channel. A
handler must take two arguments: a three-element FALSE like one
that OPEN might return, and the CHANNEL that got the error.
Ideally these interrupts should never occur. In fact, in the Tenex and Tops-20 versions, these interrupts always go to the superior operating system process instead of to MDL. In the ITS version, if and when a "dangerous" interrupt does occur:
- If no
IHEADERis associated with the interrupt, then the interrupt goes to the superior operating system process. - If an
IHEADERis associated but disabled, the errorDANGEROUS-INTERRUPT-NOT-HANDLEDoccurs (FILE-SYSTEM-ERRORfor `"IOC"). - If an
IHEADERis associated and enabled, but theINT-LEVELis too high, the errorATTEMPT-TO-DEFER-UNDEFERABLE-INTERRUPToccurs.
21.9. User-Defined Interrupts
If the interrupt name given to EVENT or ON is not one of the
standard predefined interrupts of MDL, they will gleefully create an
ATOM in <INTERRUPTS> and an associated IHEADER anyway, making
the assumption that you are setting up a "program-defined" interrupt.
Program-defined interrupts are made to occur by applying the SUBR
INTERRUPT, as in
<INTERRUPT name arg1 ... argN>
where name is a STRING, ATOM or IHEADER, and arg1 through
argN are the arguments wanted by the handlers for the interrupt.
If the interrupt specified by INTERRUPT is enabled, INTERRUPT
returns T; otherwise it returns #FALSE (). All the usual priority
and queueing rules hold, so that even if INTERRUPT returns T, it
is possible that nothing "really happened" (yet).
INTERRUPT can also be used to cause "artificial" occurrences of
standard predefined MDL interrupts.
Making a program-defined interrupt occur is similar to calling a
handler directly, but there are differences. The value returned by a
handler is ignored, so side effects must be used in order to
communicate information back to the caller, other than whether any
handler ran or will run. One good use for a program-defined interrupt
is to use the priority and queueing machinery of INT-LEVEL to
control the execution of functions that must not run concurrently. For
example, if a "CHAR" handler just deposits characters in a buffer,
then a function to process the buffered characters should probably run
at a higher priority level -- to prevent unpredictable changes to the
buffer during the processing -- and it is natural to invoke the
processing with INTERRUPT.
In more exotic applications, INTERRUPT can signal a condition to be
handled by an unknown number of independent and "nameless" functions.
The functions are "nameless" because the caller doesn't know their
name, only the name of the interrupt. This programming style is
modular and event-driven, and it is one way of implementing
"heuristic" algorithms. In addition, each HANDLER has a PROCESS
in which to run its handler, and so the different handlers for a
given condition can do their thing in different environments quite
easily, with less explicit control than when using RESUME.
21.10. Waiting for Interrupts
21.10.1. HANG
<HANG pred>
hangs interruptibly, without consuming any CPU time, potentially
forever. HANG is nice for a program that cannot do anything until an
interrupt occurs. If the optional pred is given, it is evaluated
every time an interrupt occurs and is dismissed back into the HANG;
if the result of evaluation is not FALSE, HANG unhangs and returns
it as a value. If pred is not given, there had better be a named
ACTIVATION somewhere to which a handler can return.
21.10.2. SLEEP
<SLEEP time:fix-or-float pred>
suspends execution, interruptibly, without consuming any CPU time,
for time seconds, where time is non-negative, and then returns
T. pred is the same as for HANG.