Next: , Previous: Printing Conditions, Up: Condition System Concepts

9.1.4 Signaling and Handling Conditions

The operation of the condition system depends on the ordering of active applicable handlers from most recent to least recent.

Each handler is associated with a type specifier that must designate a subtype of type condition. A handler is said to be applicable to a condition if that condition is of the type designated by the associated type specifier.

Active handlers are established by using handler-bind (or an abstraction based on handler-bind, such as handler-case or ignore-errors).

Active handlers can be established within the dynamic scope of other active handlers. At any point during program execution, there is a set of active handlers. When a condition is signaled, the most recent active applicable handler for that condition is selected from this set. Given a condition, the order of recentness of active applicable handlers is defined by the following two rules:

  1. Each handler in a set of active handlers H1 is more recent than every handler in a set H2 if the handlers in H2 were active when the handlers in H1 were established.
  2. Let h1 and h2 be two applicable active handlers established by the same form. Then h1 is more recent than h2 if h1 was defined to the left of h2 in the form that established them.

Once a handler in a handler binding form (such as handler-bind or handler-case) has been selected, all handlers in that form become inactive for the remainder of the signaling process. While the selected handler runs, no other handler established by that form is active. That is, if the handler declines, no other handler established by that form will be considered for possible invocation.

The next figure shows operators relating to the handling of conditions.

handler-bind handler-case ignore-errors

Figure 9.4: Operators relating to handling conditions. Signaling

When a condition is signaled, the most recent applicable active handler is invoked. Sometimes a handler will decline by simply returning without a transfer of control. In such cases, the next most recent applicable active handler is invoked.

If there are no applicable handlers for a condition that has been signaled, or if all applicable handlers decline, the condition is unhandled.

The functions cerror and error invoke the interactive condition handler (the debugger) rather than return if the condition being signaled, regardless of its type, is unhandled. In contrast, signal returns nil if the condition being signaled, regardless of its type, is unhandled.

The variable *break-on-signals* can be used to cause the debugger to be entered before the signaling process begins.

The next figure shows defined names relating to the signaling of conditions.

*break-on-signals* error warn
cerror signal

Figure 9.5: Defined names relating to signaling conditions. Resignaling a Condition

During the dynamic extent of the signaling process for a particular condition object, signaling the same condition object again is permitted if and only if the situation represented in both cases are the same.

For example, a handler might legitimately signal the condition object that is its argument in order to allow outer handlers first opportunity to handle the condition. (Such a handlers is sometimes called a “default handler.”) This action is permitted because the situation which the second signaling process is addressing is really the same situation.

On the other hand, in an implementation that implemented asynchronous keyboard events by interrupting the user process with a call to signal, it would not be permissible for two distinct asynchronous keyboard events to signal identical condition objects at the same time for different situations. Restarts

The interactive condition handler returns only through non-local transfer of control to specially defined restarts that can be set up either by the system or by user code. Transferring control to a restart is called “invoking” the restart. Like handlers, active restarts are established dynamically, and only active restarts can be invoked. An active restart can be invoked by the user from the debugger or by a program by using invoke-restart.

A restart contains a function to be called when the restart is invoked, an optional name that can be used to find or invoke the restart, and an optional set of interaction information for the debugger to use to enable the user to manually invoke a restart.

The name of a restart is used by invoke-restart. Restarts that can be invoked only within the debugger do not need names.

Restarts can be established by using restart-bind, restart-case, and with-simple-restart. A restart function can itself invoke any other restart that was active at the time of establishment of the restart of which the function is part.

The restarts established by a restart-bind form, a restart-case form, or a with-simple-restart form have dynamic extent which extends for the duration of that form's execution.

Restarts of the same name can be ordered from least recent to most recent according to the following two rules:

  1. Each restart in a set of active restarts R1 is more recent than every restart in a set R2 if the restarts in R2 were active when the restarts in R1 were established.
  2. Let r1 and r2 be two active restarts with the same name established by the same form. Then r1 is more recent than r2 if r1 was defined to the left of r2 in the form that established them.

If a restart is invoked but does not transfer control, the values resulting from the restart function are returned by the function that invoked the restart, either invoke-restart or invoke-restart-interactively. Interactive Use of Restarts

For interactive handling, two pieces of information are needed from a restart: a report function and an interactive function.

The report function is used by a program such as the debugger to present a description of the action the restart will take. The report function is specified and established by the :report-function keyword to restart-bind or the :report keyword to restart-case.

The interactive function, which can be specified using the :interactive-function keyword to restart-bind or :interactive keyword to restart-case, is used when the restart is invoked interactively, such as from the debugger, to produce a suitable list of arguments.

invoke-restart invokes the most recently established restart whose name is the same as the first argument to invoke-restart. If a restart is invoked interactively by the debugger and does not transfer control but rather returns values, the precise action of the debugger on those values is implementation-defined. Interfaces to Restarts

Some restarts have functional interfaces, such as abort, continue, muffle-warning, store-value, and use-value. They are ordinary functions that use find-restart and invoke-restart internally, that have the same name as the restarts they manipulate, and that are provided simply for notational convenience.

The next figure shows defined names relating to restarts.

abort invoke-restart-interactively store-value
compute-restarts muffle-warning use-value
continue restart-bind with-simple-restart
find-restart restart-case
invoke-restart restart-name

Figure 9.6: Defined names relating to restarts. Restart Tests

Each restart has an associated test, which is a function of one argument (a condition or nil) which returns true if the restart should be visible in the current situation. This test is created by the :test-function option to restart-bind or the :test option to restart-case. Associating a Restart with a Condition

A restart can be “associated with” a condition explicitly by with-condition-restarts, or implicitly by restart-case. Such an assocation has dynamic extent.

A single restart may be associated with several conditions at the same time. A single condition may have several associated restarts at the same time.

Active restarts associated with a particular condition can be detected by calling a function such as find-restart, supplying that condition as the condition argument. Active restarts can also be detected without regard to any associated condition by calling such a function without a condition argument, or by supplying a value of nil for such an argument.