Try..Recover

Description

"Try..Recover" provides structured exception handling. Exception handling in other languages is sometimes called "Try..Throw..Catch". Although more involved than other control structures Try..Recover can simplify application design. It is important to understand Try..Recover because many object methods use this structure and may expect applications calling these methods to respond appropriately.

An "exception" or "escape" is a system response to some problem that occurs. For example getting a word from the user when a number was explicitly requested generates an exception. Discovering that a file contains no more information when more information was expected generates an exception. Attempting to control an axis which is currently being controlled by something else generates an exception. In a sense exceptions are problems which actively seek out their solutions. Using Try..Recover involves making arrangements so that exceptions will find their solutions.

The syntax of "Try..Recover" is:

Try 
  <statement>
Recover
  <statement>;

In a manner similar to the "if..else" construct a semicolon should not be present before the Recover keyword. Also like the if..else construct multiple statements need to be enclosed in a begin..end compound statement.

During execution the Try keyword is encountered and the program constructs a kind of "safety net" with one end pegged to the try keyword and the other end of the net ?pegged? to the recover keyword. The statement (or multiple statements if within a begin..end) between the try and recover is then spanned by this safety net. Execution of the try statement commences. If at any place in this group of statements an exception is generated the execution of the statements stops at that point and execution "drops out" onto the safety net. Like the circus performing trapeze artist who misses the bar, lands on the safety net and then rolls to the end of the net to get off, program execution is captured by the net and "rolls" to the recover block end of the net and continues execution with the instructions in the recover block. If no problem was encountered during the try statements there is no need for recovery and the statements in the recover block are ignored.

The following example would illustrate how a Try..Recover block might be used:

Try
  SpeedEditor.Read(aSpeed)
Recover
  Prompter.Writeln(‘Speed value not a number’);

In this example the Read method is expecting to get from the editor information compatible with the type of the Read parameter. If the user types in a name instead of a number an exception is generated by the Read method and execution moves to the recover block where an error is displayed.

If there are several statements and different types of exceptions could occur how are the exceptions distinguished? Exception types are identified by unique numbers. When responding to an exception in a recover block it is good practice to look at the number of the exception to gain some understanding as to what problem occurred and what a suitable response might be. The exception number or "escape code" is found with the global function EscapeCode. Typical recover blocks have a set of tests that check if it is this problem or that problem. If the problem is beyond the capabilities of the recover block the block ends by issuing an exception itself in the hopes that the procedure or function that called it may know how to handle the problem. Exceptions "climb up" the procedure invocation ladder looking for a recover block that is willing to handle the problem. This recover block exception is created with the statement Escape(EscapeCode). Escape is a procedure which causes an exception to be generated. The value of the escape is EscapeCode, the currently unhandled error. If no recover block handles the problem the exception eventually makes its way back to Snap2Motion which has a default recover block that displays the escape code on the screen.

An example of a more advanced try recover block might be the following:

try
  begin
  DestinationEditor.Read(aDestination);
  XAxis.BeginMoveTo(aDestination);
  end
recover
  begin
  if EscapeCode=ReadEscapeCode then
    Prompter.Writeln(‘Please provide a longint’)
  else if EscapeCode=MotionOverrunEscapeCode then
    Prompter.Writeln(‘Impossible move requested’)
  else
    Escape(EscapeCode);
  end;

In this example there are two statements in the try block. The first statement, the editor read, has the capacity to escape if the user provides a type which is not suitable. The second statement, the x axis movement, has the capacity to escape if while in motion a destination is given to the axis which it cannot perform. The recover block checks for each of these conditions using the symbolic names for escape codes. These names can be found in the help system by looking for the commands used and seeing what exceptions they generate. If the escape was neither of these two cases the recover block escapes with the value of EscapeCode so that the routine that called it can possibly respond. Note that the chain of recourse for an escape is up the call ladder. An escape will attempt to get an answer from the immediate routine, then the caller, then the caller of that etc. until either it finds an answer or finds the top level default recover block which displays the escape code on the screen. Note that this ascent up the call chain is enabled by using Escape(EscapeCode) in the recover block. If this escape is not included in the program then the error handling terminates at that point and is not addressed or reported. This is not a good practice because an important piece of run-time information relating to something failing is not being acknowledged. Always end recover blocks with a none-of-the-above response of Escape(EscapeCode).

The advantage of Try..Recover might not be clear compared to alternatives. It seems that there are several simpler alternatives for accomplishing what Try..Recover is accomplishing such as using an unconditional jump to an error handler or passing status parameters. The next example makes an important distinction between try..recover and these other techniques. Consider the following procedures:

procedure PerformMotion(Destination:longint);
  begin
  XAxis.BeginMoveTo(Destination);
  end;
  
procedure PerformManyMoves
  var scanner:longint;
  begin
  for scanner:=1 to 10 do
    PerformMotion(scanner*1000);
  end;
  
....  
try
  PerformManyMoves
recover
  begin
  if EscapeCode=MotionOverrunEscapeCode then
    Prompter.Writeln(‘Motion Overrun Occurred’)
  else
    Escape(EscapeCode);
  end;

The first procedure abbreviates an X axis motion. The second procedure performs many of these motions. The try statement invokes PerformManyMotions however it is possible for the motor to be actively moving to a destination from some previous activity. If the BeginMoveTo which eventually gets called in PerformMotion cannot successfully splice a new destination onto the current motion a MotionOverrunEscapeCode will be generated. PerformManyMoves contains no error management code at all. Never the less, the MotionOverrunEscapeCode will travel through this intermediate procedure and get caught in the recover block of the routine which called PerformManyMoves. This emphasizes the "safety net" model of try..recover. Escapes can traverse through different call levels to find a solution to a problem.

The ability of Try..Recovers to be nested indirectly through procedure calls is a very powerful capability. This allows subordinate routines to attempt recovery operations themselves, handling the day-to-day problems as underling managers. However if major problems come along that they cannot handle they have a way to express that they cannot cope with the problem and request help from their manager.

Exception handling allows a high level procedure to express responsibility for handling a particular class of problem and permits intermediate-manager procedures from having to be concerned at all. If a low level exception is generated execution goes right past the intermediate managers directly to the one that expressed responsibility where it gets handled. This greatly simplifies error management.

One language design question with respect to exception handling which comes up is "Should exceptions be resumable?" i.e. should there be some way, after responding to an escape code in a recover block to resume execution at the point where the escape occurred? The language does not include resumable exceptions. Motion systems in particular tend to have a physical state as well as an informational or program state. If something goes wrong it is usually necessary not to go back to where things failed but rather to go back before that point to some prior physical state to setup another attempt. Rather than reproduce this setup in the recover block it is usually better to take a fresh "full cycle" attempt through an enclosing repeat loop or higher level repeat loop. For example:

repeat
  try
    begin
    XAxis.BeginMoveTo(aDestination);
    TroubleEncountered:=false;
    end
  recover
    begin
    TroubleEncountered:=true;
    if EscapeCode=MotionOverrunEscapeCode then
      XAxis.WaitForMoveToFinish {motion stop}
    else
      Escape(EscapeCode);
    end;
until not TroubleEncountered;

When considering the requirement to leave a loop if successful the " exit" command might be considered in the "try" section if the successful action was the last step in a procedure or function. It is very important not to exit from the try or recover sections. There are run-time activities that occur to install and remove the "safety nets". By exiting from the middle of this construct there is an unbalanced number of installations and removals resulting in a recover stack underflow or overflow. If it is important to leave in the middle of the construct consider using an exception.

A discussion of multitasking capabilities will follow later in the document however one point needs to be made now. Once a task is started, even by another task, it is accountable to no one and operates on its own. Escapes generated within a spawned task do not travel back to recover blocks in the task which spawned the escaping task. They will instead travel up to the default handler in Snap2Motion. Tasks need to take responsibility for their own errors.

Error handling can be shared by writing a single handler routine which is used by many different tasks, i.e.:

procedure ManageEscapeCode;
  begin
  if EscapeCode=MotionOverrunEscapeCode then
    Prompter.Writeln(‘Motion Overrun’)
  else if EscapeCode=ReadEscapeCode then
    Prompter.Writeln(‘Problem with Read Operation’)
  else
    Escape(EscapeCode);
  end;
  
  
...
try
  <do task operations>
recover
  ManageEscapeCode;

The routine ManageEscapeCode can be used by many different tasks in top level recover blocks to avoid having to copy the recover behavior into each one. However it's usually the case that the recover block is targeting the handling of specific expect problems.