REF ASYNC Roger Evans Dec 1987
Revised John Gibson Sep 1996
COPYRIGHT University of Sussex 1996. All Rights Reserved.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< ASYNCHRONOUS TRAPS >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< AND SIGNALS >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
This REF file describes asynchronous trap procedures and signals in
Poplog.
CONTENTS - (Use <ENTER> g to access required sections)
1 Introduction
2 Asynchronous Trap Procedures
2.1 ast_p Argument Values
2.2 ASTP Execution
3 Signals
3.1 INCLUDE * SIGDEFS
4 Processing Asynchronous Traps & Signals
5 The Signal Table
5.1 Handlers & Flags
5.2 The Initial Signal Table
... Table (1) - Logical Signal Name to Initial Handlers
... Table (2) - Logical Signal Name to Signal Number
5.3 Built-in Signal Handlers
... SIG_HUP & SIG_TERM
... SIG_INT
... SIG_TSTP and SIG_TTIN
6 Other Signal Operations
7 Detection of Asynchronous Events
---------------
1 Introduction
---------------
Various facilities in Poplog make use of asynchronous trap procedures
(ASTPs), that is, procedures which are executed outside the normal flow
of control. For example, a timer set going with sys_timer will execute
its ast_p argument when the timer expires; this execution happens
asynchronously, i.e. inside whatever other procedures the system is
currently executing.
In addition to trap procedures associated with individual facilities
(sys_timer, sys_fork and sys_async_io), there is a second type of
asynchronous event: signals. A signal is a global condition (like typing
Ctrl-C to interrupt the system), which has a specific handler procedure
to be run asynchronously when the signal occurs.
Under certain conditions, asynchronous trap procedures and/or signal
handlers may be blocked: this means that instead of being executed
immediately, a procedure is queued (on pop_ast_queue) until such time as
the blocking condition is released.
ASTPs and signals can also be raised synchronously with the procedure
sys_raise_ast. Synchronous raising is basically equivalent to an
ordinary call of the handler procedure, except that it respects any
blocking condition currently in force, and so provides a model for
handling events within a program which is the same as that used for
asynchronous events.
-------------------------------
2 Asynchronous Trap Procedures
-------------------------------
An asynchronous event (such as a timer expiring) causes the asychronous
trap procedure (ASTP) associated with the event to be raised. (See the
section Detection of Asynchronous Events below for more details on
asynchronous event detection.)
Alternatively, ASTPs may be raised synchronously with the procedure
sys_raise_ast.
In all cases, individual ASTPs can have flags associated with them which
specify that the trap should be blocked under certain conditions; in
addition, global blocking of all traps is possible by assigning false to
the active variable pop_asts_enabled.
If blocked, the trap procedure is queued (on pop_ast_queue) until such
time as the blocking condition is released.
Whenever any trap is raised, ASTPs that were previously queued but have
now become enabled are executed first. Thus as far as possible, ASTPs
are executed in the order in which they were raised. (After each
execution of a trap procedure, queue processing resumes from the
beginning of the queue, on the assumption that the handler might have
processed the queue in some way, so the "current" queue context is no
longer valid.)
2.1 ast_p Argument Values
--------------------------
In addition to sys_raise_ast for raising a trap procedure synchronously,
the procedures
* sys_timer
* sys_fork
* sys_async_io
all take an argument ast_p, which is an ASTP to be run when the
appropriate event occurs (timer firing, child process dying, input
becoming available on device).
In all cases, this argument may be either the trap procedure directly,
or a pair of the form
conspair(p, flags)
where p is the actual procedure, and the (integer) flags value specifies
blocking (and other) conditions for its execution.
Symbolic names for the flags bits are defined in INCLUDE * AST.
Currently, these are:
ASTP_BLOCK_RECURSIVE
If set, recursive invocations of the trap procedure
are blocked; it will be executed only when outside
any calls of itself.
ASTP_BLOCK_IN_EXTERNAL
If set, the trap procedure will not be run inside
callback from external code; it will be executed
only when the system is in, or has returned to,
`top-level'.
(N.B. If a trap procedure with this flag set is
queued inside the Poplog X toolkit wait state, or
inside a toolkit callback, etc, an automatic
XptSetXtWakeup is performed.)
ASTP_BLOCK_NEVER
If set, the trap procedure will never be blocked,
even when pop_asts_enabled is false. (This flag is
HIGHLY DANGEROUS -- avoid using it, or (if you must)
do so only with procedures that perform the simplest
of operations (like assigning to a variable). A
procedure using it must not create garbage, call
external procedures, or do any other complicated
work.)
ASTP_ERROR_DELETE
If set, any invocations of this trap which have been
raised, but are currently blocked and queued, will
be deleted from the queue if an error occurs (i.e.
on a call of mishap or setpop). In addition,
supplying this flag for sys_timer causes the timer
to be cancelled altogether on an error.
ASTP_TEMP_PAIR
If set, the ast_p argument pair is temporary and can
be reclaimed when the trap procedure is executed.
ASTP_TEMP_CLOSURE
If set, the trap procedure p is a temporary closure
and can be garbaged (with * sys_grbg_closure) after
execution. Also in this case, the flag
ASTP_BLOCK_RECURSIVE is taken to refer to the
* pdpart of the closure, i.e. different closures
with the same pdpart will not be run inside one
another.
Note that for an actual procedure ast_p argument (i.e. with no flags
specified), the flags value defaults to just ASTP_BLOCK_RECURSIVE.
2.2 ASTP Execution
-------------------
Asynchronous trap procedures and signal handlers are executed at
arbitrary points inside the running system. Hence they must not
(1) Leave the user stack in a changed condition after execution. A
mishap will result if the stack length has changed after
executing an ASTP.
(2) Assume that standard global variables will have any particular
values, if these are likely to be dynamically localised and set
to different values by procedures in the main program.
Of particular relevance to (2) is the variable * cucharout. To allow
ASTPs to print output safely, this is locally set to * charout during
ASTP execution. (Other variables that affect printing must be locally
set by the ASTP itself.)
----------
3 Signals
----------
A signal is a global asynchronous event such as typing Ctrl-C to
interrupt the system. Signals are represented by small positive integers
(typically some signal numbers are reserved by the operating system to
have special meanings, but the user may also add new signals if
desired).
Signals are handled similarily to asychronous traps, except that there
is only one global handler procedure for each signal (specified by
sys_signal_handler for the signal number), and blocking actions are more
limited (specified by sys_signal_flag for the signal number).
As with asynchronous trap procedures, signals can also be raised
synchronously, by giving a signal number to sys_raise_ast. Assigning
false to pop_asts_enabled blocks signals as well as ASTPs.
Note that to enable Poplog to implement more modular and powerful
facilities itself (such as sys_timer), certain Unix signals are
commandeered by the system, and the user handlers for these are never
invoked (see the section on the initial signal table below for more
details).
3.1 INCLUDE * SIGDEFS
----------------------
INCLUDE * SIGDEFS is a library defining iconstant macros for all the
operating system signals available on the system you are running. The
constant names consist of SIG_ followed by the signal name (SIG_INT,
SIG_ALRM, etc). If the signal is not defined on your system, SIGDEFS
will not define a macro for it. Thus to achieve a degree of portability,
you can do, for example:
#_IF DEF SIG_USR1
myhandler -> sys_signal_handler(SIG_USR1);
#_ENDIF;
See HELP * DEF, * SYSDEFS
------------------------------------------
4 Processing Asynchronous Traps & Signals
------------------------------------------
pop_asts_enabled -> bool [active variable]
bool -> pop_asts_enabled
This active variable provides global control over blocking of
asynchronous traps and signals. If false, all ASTPs and signals
are blocked, that is, when raised they are added to the AST
queue but not acted upon. Setting this variable true allows
ASTPs and signals in the queue to be processed, and causes any
currently in the queue that are not individually blocked to be
dealt with.
sys_raise_ast(ast_p_or_signum_or_false) [procedure]
This procedure raises an asynchronous trap procedure ast_p or an
an instance of the signal signum (an integer) and processes the
AST queue (if necessary -- see description above) in an attempt
to handle it. If the argument is false, it just processes the
queue without raising a new trap.
Note that for a signal, if the handler expects an argument (that
is, its pdnargs is 1), the signal number is passed as argument
to it.
pop_ast_queue -> list [active variable]
list -> pop_ast_queue
This variable returns a copy of the current queue of
asynchronous trap procedures and signals awaiting
execution/handling (a list of astp_p values and signal numbers).
When updated, it makes a copy of the list given (which must not
be a dynamic list), and assigns it to be the current queue. (The
reason for the copying in these operations is to ensure that the
actual queue is private to the system, so it can successfully
inhibit the creation of garbage during normal operation.)
Note that assigning [] to pop_ast_queue also clears any
internally-queued asynchronous events that have not yet been
raised.
-------------------
5 The Signal Table
-------------------
The key data structures of the signal mechanism is the signal table,
which contains the information needed to handle each signal. The signal
table actually consists of two structures: a vector of signal handlers
(one for each signal), and a corresponding vector of flags controlling
whether the signal is enabled for handling or not.
5.1 Handlers & Flags
---------------------
sys_max_signal -> int [active variable]
int -> sys_max_signal
This variable returns the largest signal currently defined (an
integer). Initially, this value is set to the number of signals
defined by the operating system (which varies from system to
system), and it is not permitted to set it lower than this
value. It can, however, be increased, thereby creating new
signals, but such signals can only be raised synchronously.
sys_signal_flag(signum) -> flag [procedure]
flag -> sys_signal_flag(signum)
sys_signal_flag(sig_vec) -> flag
flag -> sys_signal_flag(sig_vec)
Returns/updates the status value(s) associated with the
specified signal signum, or with those given by the vector of
signals sig_vec.
For a single signum, legitimate values for flag are:
false
Handling is blocked -- the signal will be added to the
signal queue but not acted upon. (Setting the flag to a true
value again will automatically cause queued signals to be
handled.)
{signum1, ... signumN} (i.e. a vector of signals)
Handling is enabled, with handling of all the signals given
in the vector blocked while the handler is running, i.e.
sys_signal_flag is set false for each signal in the vector
before running the handler, and restored afterwards (at
which point, any queued signals resulting from the blocking
will be actioned).
true (the default for most signals)
This is equivalent to {signum}, i.e. a vector containing
just signum itself -- thus while the handler is running,
only signum itself is blocked. This value is the default, on
the basis that most handlers will not want to be re-entrant,
i.e. invoked again while already running (typically because
of problems with global data structures, etc). A handler can
be made re-entrant by using {}, i.e. an empty vector.
When a set of signals sig_vec is given, the flag value returned
depends on whether all signals in sig_vec have the same value.
If so, then that value is returned, but otherwise the result is
a list of values, one for each signal in sig_vec. Similarly on
updating, either a single flag value for all signals in sig_vec
can be given, or a list of different values for each signal.
Note that since the signal raising mechanism changes signal
flags locally while running a handler, any changes to those
flags made by the handler procedure will be lost.
sys_signal_handler(signum) -> handler [procedure]
handler -> sys_signal_handler(signum)
This procedure returns/updates the signal handler for the
specified signal (a positive integer not greater than
sys_max_signal). Legitimate values for handler are:
A procedure
which is called to handle the signal. If the procedure
expects an argument (that is, its pdnargs is 1), it is
passed the signal number.
A procedure identifier
whose idval is used as the handler. The syntax word ident
(see REF * IDENT) can be used to access the identifier
associated with a variable, with the effect that the handler
will be the current value of that variable. Note that the
variable must be declared as a procedure variable. Once
again, if the handler expects an argument, it is passed the
signal number.
true or false
for operating-system-defined signals in Unix systems, these
values cause the signal handling to be set to SIG_DFL
(default behaviour) and SIG_IGN (ignore signal) respectively
(see UNIX * signal etc). The Poplog signal mechanism is
bypassed completely for asynchronous raises, which result in
default/ignore behaviour outside Poplog control. For
synchronous raises of these signals, and all raises of other
signals (ALL signals in non-Unix systems), a boolean value
is equivalent to assigning identfn as the handler, ie the
signal is effectively ignored.
5.2 The Initial Signal Table
-----------------------------
When Poplog first starts up, the signal table is initialised with values
for the signals which can be raised asynchronously by the operating
system. (Users can extend the table to include new synchronous signals,
of course.) Exactly which signals these are varies from operating system
to operating system. The enable flag for all signals is set to true
initially. Table (1) below defines the initial handlers for logical
signal names (not all signals are available in all systems however),
table (2) defines the mapping of signal names to signal numbers for the
different operating systems:
... Table (1) - Logical Signal Name to Initial Handlers
--------------------------------------------------------
Signal Initial Handler
------ ---------------
HUP Exit, writing files, etc.
INT ident keyboard_interrupt
QUIT mishap - see note 1
ILL mishap - see note 3
TRAP mishap
IOT mishap
EMT mishap - see note 3
FPE mishap - see note 2
KILL mishap - see note 6
BUS mishap - see note 3
SEGV mishap - see note 3
SYS mishap
PIPE false - see note 9
ALRM See note 4
TERM Exit, writing files, etc.
USR1 mishap
USR2 mishap
CHLD true (i.e., ignored) - see note 7, 9
PWR mishap
WINCH false - see note 9
URG mishap
IO See note 5
POLL mishap
STOP mishap - see note 6
TSTP Reset terminal state, move cursor to bottom of screen,
stop
CONT false - see note 9
TTIN Move cursor to bottom of screen, stop
TTOU true (i.e., stop) - see note 8, 9
VTALRM See note 4
PROF mishap
XCPU mishap
XFSZ false - see note 9
LOST mishap
NOTES: Although handlers are provided for all the system signals, some
signals are trapped by low level system routines. For these signals, the
handler in the table is not always invoked if the signal is raised
asynchronously. Instead, the following actions are taken:
1) When an asynchronous QUIT is received, Poplog waits one second
and then executes setpop. If during this wait a second QUIT is
received, Poplog exits immediately (without tidying). This
mechanism allows Poplog to be interrupted cleanly at ANY TIME
during its execution - regardless of the settings of the various
control flags.
2) When an asynchronous FPE signal is received, Poplog checks
system dependent error codes to determine exactly what has gone
wrong. FPE signals can result in any one of (a) immediate
handling, if Poplog can recover from the error, (b) user
handling (default - mishap), if Poplog cannot recover from the
error, (c) "SYSTEM ERROR" mishap if the error is recoverable,
but the data required for recovery is not present (eg if the
error occurs in externally loaded code).
3) When an asynchronous EMT, ILL, BUS or SEGV signal is received,
Poplog interprets it as a system error condition which cannot be
handled by user routines. Instead, Poplog always mishaps, with
"SYSTEM ERROR" for ILL and EMT, "ATTEMPT TO ALTER NON_WRITEABLE
SYSTEM STRUCTURE" for BUS, and "STACK EMPTY" for SEGV. (Note:
this also explains why these mishaps sometimes occur during the
execution of incorrect externally loaded routines.).
4) Poplog reserves the asynchronous ALRM and VTALRM signals for the
management of interval timers installed by sys_timer (see
REF * TIMES).
5) Poplog reserves the asynchronous IO signal for the management of
asynchronous input on devices (see REF * SYSIO).
6) The KILL and STOP signals cannot be handled --- Poplog exits
(KILL) or suspends (STOP) without any further action if these
signals are received asynchronously.
7) Poplog reserves the asynchronous CHLD signal for the management
of child processes created by sys_fork and sys_vfork (see
REF * SYSUTIL).
8) The default action for TTOU is to stop immediately. In this
case, unlike TTIN and TSTP, Poplog does no tidying or restoring
of the screen before or after the stop.
9) Signals whose handler value is boolean are ignored when raised
synchronously.
... Table (2) - Logical Signal Name to Signal Number
-----------------------------------------------------
SunOS/
Signal SVR4 IRIX HP-UX Ultrix VMS
------ ---- ---- ----- ------ ---
HUP 1 1 1 1
INT 2 2 2 2 1
QUIT 3 3 3 3
ILL 4 4 4 4
TRAP 5 5 5 5
IOT 6 6 6 6
EMT 7 7 7 7
FPE 8 8 8 8
KILL 9 9 9 9
BUS 10 10 10 10
SEGV 11 11 11 11
SYS 12 12 12 12
PIPE 13 13 13 13
ALRM 14 14 14 14 2
TERM 15 15 15 15
USR1 16 16 16 30
USR2 17 17 17 31
CHLD 18 18 18 20 3
PWR 19 19 19
WINCH 20 25 23 28
URG 21 24 29 16
IO 22 23 22 23 4
POLL 22 22
STOP 23 20 24 17
TSTP 24 21 25 18
CONT 25 28 26 19
TTIN 26 29 27 21
TTOU 27 30 28 22
VTALRM 28 26 20 26
PROF 29 27 21 27
XCPU 30 31 24
XFSZ 31 32 25
LOST 30 29
5.3 Built-in Signal Handlers
-----------------------------
As well as the signals discussed above which are handled at a low level
by Poplog, some of the other signals have built-in handlers which
perform useful functions, briefly described in table (1). The functions
are discussed in more detail in this section. Note, however, that these
handlers can be overridden and so are not guaranteed. Note also that not
all the signals discussed here are available on all systems, in
particular, only INT and ALRM are available on VMS systems (see table
2).
... SIG_HUP & SIG_TERM
-----------------------
When HUP or TERM is received, Poplog exits "cleanly". That is it calls
sysexit, which calls popexit, writes Ved files etc. However before
exiting, it sets pop_exit_ok false, and this inhibits generation of any
terminal IO (see entry on pop_exit_ok in REF * SYSTEM). It also disables
any further signals. Note: the earlier behaviour of HUP (exit without
any tidying) can be recovered by assigning the system procedure
fast_sysexit as handler.
... SIG_INT
------------
When INT is received, Poplog invokes the variable procedure
keyboard_interrupt, which by default calls the procedure interrupt
(whose default value in turn is setpop). INT is sent (asynchronously)
when the user types the interrupt sequence (often Ctrl-C) on the
keyboard:
keyboard_interrupt() [procedure variable]
The procedure in this variable is called asynchronously in
response to a SIG_INT signal -- which is usually produced by
Ctrl-C typed on the keyboard.
The default value is a procedure which just calls interrupt().
interrupt() [procedure variable]
The procedure in this variable is called by the standard value
of keyboard_interrupt.
interrupt is also called by mishap after it has printed a mishap
message and before it calls setpop (thus redefining interrupt
can be used to alter the action taken after mishaps -- see
REF * MISHAPS).
The default value of this variable is setpop.
... SIG_TSTP and SIG_TTIN
--------------------------
When TSTP or TTIN is received, Poplog suspends after performing the
following tidying up operations: First it invokes a user procedure
popsuspend discussed below. Then it saves the current Ved state, and
moves the cursor to the bottom of the screen Then if the signal is TSTP
it restores the terminal to the state it was in when Poplog started up
(or last resumed after a suspension). Then it suspends itself, by
setting the the signal handler to the Unix default and resending the
signal.
When Poplog is restarted, it undoes the tidying done by the suspend: it
saves the current state of the terminal (to be restored on the next
suspend, or exit) and resets it to what Poplog expects.
It then restarts Ved, if it was active when Poplog suspended. For
non-windowed Ved, the variable vedstop_refresh_both is used to decide
how to do this: if false (the default), only the last window edited is
redisplayed, if true both windows (if appropriate) are redisplayed.
Finally it runs popsuspend again (defined below).
popsuspend(s) [procedure variable]
The procedure popsuspend provides a way of attaching user
actions to suspending and continuing without having to redefine
the handlers themselves. It takes one argument identifying the
context of use as follows:
positive integer s:
about to suspend in response to signal s
negative integer s:
continuing after suspension due to signal s
The default value of popsuspend is erase.
There are a number of known problems with these stop handlers. Firstly,
the STOP signal cannot be handled and so cannot be brought into this
scheme. It is recommended therefore that TSTP is used to suspend Poplog
rather than STOP. Indeed TSTP is the signal sent when the user types the
suspend sequence (usually ctrl Z, ctrl Y or ctrl \) from the terminal.
It is also the signal raised (synchronously) by the stop macro and the
Ved <ENTER> stop command (see HELP * STOP).
Finally note that Poplog gets sent a CONT signal when it restarts after
a suspension, which is ignored by default.
--------------------------
6 Other Signal Operations
--------------------------
sys_send_signal(pid, signum) -> bool (unix only) [procedure]
Sends the signal number signum to the process whose Process
IDentification number is the integer pid, returning true if
successful. false is returned if the specified process does not
exist, or you do not have the privilege to send that signal to
it.
If pid refers to a Poplog process, this causes an asynchronous
raise of signal signum in that process.
syskill(pid) -> bool [procedure]
In Unix, this is the same as sys_send_signal(pid, 9), i.e. send
a "kill" signal to the process specified by pid.
In VMS, it attempts to kill the VMS process whose Process
IDentification number is the integer pid, returning true if
successful.
sys_reset_signal() (unix only) [procedure]
Restores Poplog's low-level signal handling routines to be
consistent with the signal table. Signals with
procedure/identifier handlers are set to the Poplog standard low
level handler, those with boolean-valued handlers are set to
SIG_DFL/SIG_IGN for true/false respectively. This should only
need to be used to tidy up after an external routine has altered
the signal-handling.
-----------------------------------
7 Detection of Asynchronous Events
-----------------------------------
Asynchronous traps and signals can occur at any time during Poplog
processing. However, Poplog cannot immediately interrupt what it is
currently doing to handle the event, because there is no guarantee that
the system is in an appropriate state for running a user procedure at
the moment the event occurs (for example, Poplog may be in the middle of
a garbage collection). Thus Poplog's immediate response to an
asynchronous event is to record which event has occurred (on an internal
low-level queue), set a flag indicating that asynchronous handling is
required, and then return to whatever processing it was doing at the
time the event arrived.
In order for such events to actually be handled, Poplog must check
(during normal processing) whether the flag has been set. It does this
frequently, but only at times when it is safe to run the handler
procedure. If it finds the flag has been set, it retrieves the event,
finds the appropriate ASTP or signal number, then does a (synchronous)
raise of that (as if with sys_raise_ast). From that point on, it behaves
exactly as if the user program had raised the ASTP or signal.
So that this approach can work satisfactorily, Poplog must check for
asynchronous events frequently. Checks are automatically carried out at
the beginning of each user procedure, and whenever control branches
backwards, in particular, inside every loop construct. (But see the
compile_mode:vm flags -pentch and -bjmpch in HELP * compile_mode.) In
addition, certain system procedures also check for asynchronous events.
All of this ensures that in normal operation, ASTPs and signals are
handled promptly.
Nevertheless, there can be situations where checking does not take
place for several seconds (the main one being garbage collections). This
can cause Poplog's low-level queue to accumulate a number of events, all
of which will be handled at the next check; however, the queue only
allows space for 64 events, and if more than this arrive without being
handled, only the last 64 will be processed.
The following active variable allows users to block the checking of
asynchronous events. The only possible use for this is in an application
which must not under circumstances perform a garbage collection; in all
other contexts, assign false to pop_asts_enabled (so that at least the
contents of the low-level queue are constantly being drained and
transferred to the higher-level pop_ast_queue):
pop_enable_interrupts -> bool [active variable]
bool -> pop_enable_interrupts
This variable provides global control over the disabling of
asynchronous events. While false, asynchronous events are added
to an internal low-level queue, and not raised until this
variable becomes true again (but synchronous raising is still
possible).
The only exceptions to this are certain signals that are trapped
by the low level routines: in particular in Unix systems, QUIT
will allow Poplog to be interrupted even if
pop_enable_interrupts is false.
(Note that internally queued asynchronous signals can be cleared
by assigning [] to pop_ast_queue.)
+-+ C.all/ref/async
+-+ Copyright University of Sussex 1996. All rights reserved.