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.