Error trapping with Dyalog APL: Difference between revisions

From APL Wiki
Jump to navigation Jump to search
m (Text replacement - "<source" to "<syntaxhighlight")
 
(One intermediate revision by the same user not shown)
Line 22: Line 22:
It is good programming style to avoid using numbers in code. Instead of talking about 1001, for example, we should use a meaningful name:
It is good programming style to avoid using numbers in code. Instead of talking about 1001, for example, we should use a meaningful name:


<source lang=apl>
<syntaxhighlight lang=apl>
⎕CS 'Events' #.⎕NS ''
⎕CS 'Events' #.⎕NS ''
⎕FX 'r←StopVector' 'r←1001'
⎕FX 'r←StopVector' 'r←1001'
⎕FX 'r←WeakInterrupt' 'r←1002'
⎕FX 'r←WeakInterrupt' 'r←1002'
⎕FX 'r←StrongInterrupt' 'r←1003'
⎕FX 'r←StrongInterrupt' 'r←1003'
</source>
</syntaxhighlight>


In a large system you want those to be constants, so a user cannot change them. That's why they are niladic functions.
In a large system you want those to be constants, so a user cannot change them. That's why they are niladic functions.
Line 33: Line 33:
We need also a user-defined event for restarting the application. This is explained soon:
We need also a user-defined event for restarting the application. This is explained soon:


<source lang=apl>
<syntaxhighlight lang=apl>
#.Events.RestartAppl←501
#.Events.RestartAppl←501
</source>
</syntaxhighlight>


According to the help file, users should use the range from 500 to 999 to define their own events.
According to the help file, users should use the range from 500 to 999 to define their own events.


== Setting <source lang=apl inline>⎕TRAP</source> ==
== Setting <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> ==


<source lang=apl inline>⎕TRAP</source> allows us to implement a general mechanism on a global level. For discussion purposes let's assume the following:
<syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> allows us to implement a general mechanism on a global level. For discussion purposes let's assume the following:
# <source lang=apl inline>⎕LX</source> is set to run function <source lang=apl inline>Run</source>
# <syntaxhighlight lang=apl inline>⎕LX</syntaxhighlight> is set to run function <syntaxhighlight lang=apl inline>Run</syntaxhighlight>
# This function calls 3 sub-functions: <source lang=apl inline>Initial</source>, <source lang=apl inline>Work</source> and <source lang=apl inline>Shutdown</source>
# This function calls 3 sub-functions: <syntaxhighlight lang=apl inline>Initial</syntaxhighlight>, <syntaxhighlight lang=apl inline>Work</syntaxhighlight> and <syntaxhighlight lang=apl inline>Shutdown</syntaxhighlight>
# <source lang=apl inline>Initial</source> initializes the application: it opens files, interprets an INI file, takes the Windows registry into account, builds the GUI and so forth.
# <syntaxhighlight lang=apl inline>Initial</syntaxhighlight> initializes the application: it opens files, interprets an INI file, takes the Windows registry into account, builds the GUI and so forth.
# <source lang=apl inline>Work</source> simply runs <source lang=apl inline>⎕DQ</source>
# <syntaxhighlight lang=apl inline>Work</syntaxhighlight> simply runs <syntaxhighlight lang=apl inline>⎕DQ</syntaxhighlight>
# <source lang=apl inline>Shutdown</source> cleans up: it closes files, says good-by.
# <syntaxhighlight lang=apl inline>Shutdown</syntaxhighlight> cleans up: it closes files, says good-by.


=== Solving the stop vector problem ===
=== Solving the stop vector problem ===


Let's start with solving the stop vector problem:
Let's start with solving the stop vector problem:
<source lang=apl>
<syntaxhighlight lang=apl>
⎕TRAP←⊂(#.Events.StopVector 'E' '→⎕LC')
⎕TRAP←⊂(#.Events.StopVector 'E' '→⎕LC')
</source>
</syntaxhighlight>
<source lang=apl inline>#.Events.StopVector</source> returns 1001 which is the event number a stop vector is associated with. As soon as APL stops on a stop vector <source lang=apl inline>⎕EN</source> is set to 1001. This event can be caught with <source lang=apl inline>⎕TRAP</source>, so we can tell APL to execute (<source lang=apl inline>'E'</source>) the expression given as third argument. In this case it tells APL to simply ignore stop vectors by resuming execution.
<syntaxhighlight lang=apl inline>#.Events.StopVector</syntaxhighlight> returns 1001 which is the event number a stop vector is associated with. As soon as APL stops on a stop vector <syntaxhighlight lang=apl inline>⎕EN</syntaxhighlight> is set to 1001. This event can be caught with <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight>, so we can tell APL to execute (<syntaxhighlight lang=apl inline>'E'</syntaxhighlight>) the expression given as third argument. In this case it tells APL to simply ignore stop vectors by resuming execution.


=== Preventing users from interrupting an application ===
=== Preventing users from interrupting an application ===
Line 61: Line 61:


Here, however, we will use this simple approach:
Here, however, we will use this simple approach:
<source lang=apl>
<syntaxhighlight lang=apl>
events←#.Events.WeakInterrupt #.Events.StrongInterrupt
events←#.Events.WeakInterrupt #.Events.StrongInterrupt
⎕TRAP,←⊂(events 'E' '→⎕LC')
⎕TRAP,←⊂(events 'E' '→⎕LC')
</source>
</syntaxhighlight>


=== Restarting the application ===
=== Restarting the application ===


For reasons explained in a minute we now have to define the “Restart the application” procedure. For this, for the first time we do not use the <source lang=apl inline>'E'</source> statement but the <source lang=apl inline>'C'</source> statement. The ''C'' is short for ''Cut back''. This instructs APL to cut the status indicator back to the level where <source lang=apl inline>⎕TRAP</source> '''is localized''' – that is not  necessarily where it was set – and execute the expression in the 3rd argument there. However, if <source lang=apl inline>⎕TRAP</source> is not localized at all, i.e. it is in the workspace, the status indicator is cut back completely and the expression is executed in the workspace.
For reasons explained in a minute we now have to define the “Restart the application” procedure. For this, for the first time we do not use the <syntaxhighlight lang=apl inline>'E'</syntaxhighlight> statement but the <syntaxhighlight lang=apl inline>'C'</syntaxhighlight> statement. The ''C'' is short for ''Cut back''. This instructs APL to cut the status indicator back to the level where <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> '''is localized''' – that is not  necessarily where it was set – and execute the expression in the 3rd argument there. However, if <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> is not localized at all, i.e. it is in the workspace, the status indicator is cut back completely and the expression is executed in the workspace.
<source lang=apl>
<syntaxhighlight lang=apl>
⎕TRAP,←⊂(#.Events.RestartAppl 'C' '→∆Restart')
⎕TRAP,←⊂(#.Events.RestartAppl 'C' '→∆Restart')
</source>
</syntaxhighlight>
To make this work the function in which <source lang=apl inline>⎕TRAP</source> is localized must have a label <source lang=apl inline>∆Restart</source> or a function that returns a valid line number to branch to of course.
To make this work the function in which <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> is localized must have a label <syntaxhighlight lang=apl inline>∆Restart</syntaxhighlight> or a function that returns a valid line number to branch to of course.


=== Catching Errors ===
=== Catching Errors ===


If an unexpected error occurs, we want to execute a particular function to do the hard work.
If an unexpected error occurs, we want to execute a particular function to do the hard work.
<source lang=apl>
<syntaxhighlight lang=apl>
⎕TRAP,←⊂((0 1000) 'E' '#.HandleError' )
⎕TRAP,←⊂((0 1000) 'E' '#.HandleError' )
</source>
</syntaxhighlight>
The 0 stands for all the events from 1 to 999 while the 1000 stands for all events larger than 1000.
The 0 stands for all the events from 1 to 999 while the 1000 stands for all events larger than 1000.


<source lang=apl inline>⎕TRAP</source> may contain more than one error catching group. Since the contents of <source lang=apl inline>⎕TRAP</source> is scanned from left to right, a statement will ONLY be executed for an event not processed earlier. That is the reason why we must define the restart event first.
<syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> may contain more than one error catching group. Since the contents of <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> is scanned from left to right, a statement will ONLY be executed for an event not processed earlier. That is the reason why we must define the restart event first.


For example, in the following statement:
For example, in the following statement:
<source lang=apl>
<syntaxhighlight lang=apl>
⎕TRAP←(333 'E' 'expA') (0 'E' 'expB')
⎕TRAP←(333 'E' 'expA') (0 'E' 'expB')
</source>
</syntaxhighlight>
event 333 will be caught by the 1st group and NOT by the 2nd even though 0 stands for “events from 1 to 999”. Only the expression <source lang=apl inline>expA</source> will be executed.
event 333 will be caught by the 1st group and NOT by the 2nd even though 0 stands for “events from 1 to 999”. Only the expression <syntaxhighlight lang=apl inline>expA</syntaxhighlight> will be executed.


== The <source lang=apl inline>#.HandleError</source> function ==
== The <syntaxhighlight lang=apl inline>#.HandleError</syntaxhighlight> function ==


The <source lang=apl inline>#.HandleError</source> function should do at least the following:
The <syntaxhighlight lang=apl inline>#.HandleError</syntaxhighlight> function should do at least the following:


* Perhaps neutralize <source lang=apl inline>⎕LX</source>
* Perhaps neutralize <syntaxhighlight lang=apl inline>⎕LX</syntaxhighlight>
* Save the <source lang=apl inline>⎕DM</source> and <source lang=apl inline>⎕EN</source> settings
* Save the <syntaxhighlight lang=apl inline>⎕DM</syntaxhighlight> and <syntaxhighlight lang=apl inline>⎕EN</syntaxhighlight> settings
* Save the snapshot
* Save the snapshot
* Maybe create an HTML page with general information about the error
* Maybe create an HTML page with general information about the error
Line 110: Line 110:
Keep in mind that you want to have an easy opportunity to test the system with error trapping. So you may need another parameter that tells the system that error trapping has to be used. Last but not least, there should be an easy opportunity to let the application crash on purpose. I prefer to have a “developers menu”, which is displayed only to developers. Among other useful commands it offers a “Let's crash” option.
Keep in mind that you want to have an easy opportunity to test the system with error trapping. So you may need another parameter that tells the system that error trapping has to be used. Last but not least, there should be an easy opportunity to let the application crash on purpose. I prefer to have a “developers menu”, which is displayed only to developers. Among other useful commands it offers a “Let's crash” option.


=== Control Structure <source lang=apl inline>:Trap</source> ===
=== Control Structure <syntaxhighlight lang=apl inline>:Trap</syntaxhighlight> ===


If you use <source lang=apl inline>:Trap</source>, keep in mind that <source lang=apl inline>⎕TRAP</source> and <source lang=apl inline>:Trap</source> are both taken into account. That means that in case of
If you use <syntaxhighlight lang=apl inline>:Trap</syntaxhighlight>, keep in mind that <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> and <syntaxhighlight lang=apl inline>:Trap</syntaxhighlight> are both taken into account. That means that in case of
<source lang=apl>
<syntaxhighlight lang=apl>
:Trap 0
:Trap 0
-'a'
-'a'
Line 119: Line 119:
.
.
:EndTrap
:EndTrap
</source>
</syntaxhighlight>


the error caused by the <source lang=apl inline>-'a'</source> statement is caught by the <source lang=apl inline>:Else</source>, while the <source lang=apl inline>.</source> is caught by the <source lang=apl inline>⎕TRAP</source> setting.
the error caused by the <syntaxhighlight lang=apl inline>-'a'</syntaxhighlight> statement is caught by the <syntaxhighlight lang=apl inline>:Else</syntaxhighlight>, while the <syntaxhighlight lang=apl inline>.</syntaxhighlight> is caught by the <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> setting.


When using <source lang=apl inline>:Trap</source> try to be as specific as possible. For example, this code is faulty:
When using <syntaxhighlight lang=apl inline>:Trap</syntaxhighlight> try to be as specific as possible. For example, this code is faulty:
<source lang=apl>
<syntaxhighlight lang=apl>
:Trap 0
:Trap 0
filename ⎕FTIE 0
filename ⎕FTIE 0
Line 130: Line 130:
filename ⎕FCREATE 0
filename ⎕FCREATE 0
:EndTrap
:EndTrap
</source>
</syntaxhighlight>
because it tries to create a file not only if this file does not already exist but also if the current user lacks the right to tie it, for example because somebody else has already tied it exclusively. Therefore, it is a better to be specific:
because it tries to create a file not only if this file does not already exist but also if the current user lacks the right to tie it, for example because somebody else has already tied it exclusively. Therefore, it is a better to be specific:
<source lang=apl>
<syntaxhighlight lang=apl>
:Trap 22
:Trap 22
filename ⎕FTIE 0
filename ⎕FTIE 0
Line 138: Line 138:
filename ⎕FCREATE 0
filename ⎕FCREATE 0
:EndTrap
:EndTrap
</source>
</syntaxhighlight>
The best idea, however, is to use <source lang=apl inline>⎕NEXISTS</source> to check the file for already being created. In general it is a good idea to use error trapping only for extraordinary problems.
The best idea, however, is to use <syntaxhighlight lang=apl inline>⎕NEXISTS</syntaxhighlight> to check the file for already being created. In general it is a good idea to use error trapping only for extraordinary problems.


=== The <source lang=apl inline>⎕SIGNAL</source> system function ===
=== The <syntaxhighlight lang=apl inline>⎕SIGNAL</syntaxhighlight> system function ===


Note that an event which is <source lang=apl inline>⎕SIGNAL</source>led can be intercepted with <source lang=apl inline>⎕TRAP</source> but not <source lang=apl inline>:Trap</source>
Note that an event which is <syntaxhighlight lang=apl inline>⎕SIGNAL</syntaxhighlight>led can be intercepted with <syntaxhighlight lang=apl inline>⎕TRAP</syntaxhighlight> but not <syntaxhighlight lang=apl inline>:Trap</syntaxhighlight>
If you execute this function:
If you execute this function:
<source lang=apl>
<syntaxhighlight lang=apl>
∇test
∇test
  ⎕TRAP←501 'E' '⎕←''caught by ⎕TRAP'''
  ⎕TRAP←501 'E' '⎕←''caught by ⎕TRAP'''
Line 154: Line 154:
  :EndTrap
  :EndTrap
</source>
</syntaxhighlight>
you get this:
you get this:
<source lang=apl>
<syntaxhighlight lang=apl>
caught by ⎕TRAP
caught by ⎕TRAP
</source>
</syntaxhighlight>


=== Ensure future trouble ===
=== Ensure future trouble ===


A very easy way to create problems in the future is to do this:
A very easy way to create problems in the future is to do this:
<source lang=apl>
<syntaxhighlight lang=apl>
:Trap 0
:Trap 0
DoSomethingHere
DoSomethingHere
:EndTrap
:EndTrap
</source>
</syntaxhighlight>
This technique is called “silent trapping”. If something is going wrong, do not take care and do not tell anybody about it!
This technique is called “silent trapping”. If something is going wrong, do not take care and do not tell anybody about it!


Line 174: Line 174:
When you use error trapping, make sure that you can switch off error trapping on a general level. The easiest way to implement this idea is something like this:
When you use error trapping, make sure that you can switch off error trapping on a general level. The easiest way to implement this idea is something like this:


<source lang=apl>
<syntaxhighlight lang=apl>
:Trap #.ErrorTrapFlag/0
:Trap #.ErrorTrapFlag/0
DoSomething
DoSomething
Line 180: Line 180:
TakeCare
TakeCare
EndTrap
EndTrap
</source>
</syntaxhighlight>
If the flag is true, error trapping is active, if not, the <source lang=apl inline>DoSomeThing</source> statement will fail if an error occurs. This makes is much easier to debug an application.
If the flag is true, error trapping is active, if not, the <syntaxhighlight lang=apl inline>DoSomeThing</syntaxhighlight> statement will fail if an error occurs. This makes is much easier to debug an application.


You might need a more sophisticated mechanism for this, because under some circumstances you want to switch off most but not all error trapping statements. For example, if you use a logging mechanism which is logging every user action for analyzing purposes, the code doing this may cause an interrupt itself, for example because the disk is full which holds the logging files. In such a case it might be inappropriate that the logging code breaks the application. Therefore, you might control this code with <source lang=apl inline>:Trap</source>-statements.
You might need a more sophisticated mechanism for this, because under some circumstances you want to switch off most but not all error trapping statements. For example, if you use a logging mechanism which is logging every user action for analyzing purposes, the code doing this may cause an interrupt itself, for example because the disk is full which holds the logging files. In such a case it might be inappropriate that the logging code breaks the application. Therefore, you might control this code with <syntaxhighlight lang=apl inline>:Trap</syntaxhighlight>-statements.


In such a case it might be a good idea to control the behavior of the application on different levels for code which is really essential in terms of business logic, for example, and for code which is not essential.
In such a case it might be a good idea to control the behavior of the application on different levels for code which is really essential in terms of business logic, for example, and for code which is not essential.
Line 191: Line 191:
== Code ==
== Code ==


{{Collapse|The below workspace contains all the code needed to implement the ideas discussed above.|<source lang=apl>
{{Collapse|The below workspace contains all the code needed to implement the ideas discussed above.|<syntaxhighlight lang=apl>
(⎕IO ⎕ML ⎕WX)←1 1 3
(⎕IO ⎕ML ⎕WX)←1 1 3


Line 766: Line 766:


:EndNamespace
:EndNamespace
</source>
</syntaxhighlight>
}}
}}
== See also ==
== See also ==

Latest revision as of 22:09, 10 September 2022

To write bug-free code in a complex system and to forecast all errors is impossible. Therefore, implementing some kind of error trapping in an application which is supposed to run in a productive environment is a must. But to do this in a general and efficient manner is not easy. This article discusses techniques to solve this problem.

Introduction

Versions covered

The techniques we are going to discuss have been available in Dyalog APL for a long time. When you try to implement error trapping you should be very careful: it is easy to implement a non-interruptible loop. If this happens you have to kill the process in the task manager and the workspace is lost, so it is a good idea to save the workspace before you execute code after a change.

Considerations

When an application is executed in a production environment, error trapping can be used to solve the following goals:

  • Save a workspace as a snapshot reflecting the state of the application when the error appeared. This makes it easy to analyze a problem, and sometimes it is the only way to do so.
  • After having saved a snapshot it might be possible to restart the application. This might enable the user to run the application with different data, or to run different parts of the application on the same data.
  • Prevent the user from interrupting the application by pressing the keys the strong and the weak interrupt are associated with.
  • Continue execution in case a developer has forgotten to remove a stop vector.

When an application is started, it needs to be initialized. If an error occurs at this early stage, normally there is no way to recover. Once an application is fully initialized, it might be a good idea to try to restart it. However, if this procedure crashes itself, we must prevent an endless loop from generating tons of useless snapshot workspaces.

Preparation

It is good programming style to avoid using numbers in code. Instead of talking about 1001, for example, we should use a meaningful name:

⎕CS 'Events' #.⎕NS ''
⎕FX 'r←StopVector' 'r←1001'
⎕FX 'r←WeakInterrupt' 'r←1002'
⎕FX 'r←StrongInterrupt' 'r←1003'

In a large system you want those to be constants, so a user cannot change them. That's why they are niladic functions.

We need also a user-defined event for restarting the application. This is explained soon:

#.Events.RestartAppl←501

According to the help file, users should use the range from 500 to 999 to define their own events.

Setting ⎕TRAP

⎕TRAP allows us to implement a general mechanism on a global level. For discussion purposes let's assume the following:

  1. ⎕LX is set to run function Run
  2. This function calls 3 sub-functions: Initial, Work and Shutdown
  3. Initial initializes the application: it opens files, interprets an INI file, takes the Windows registry into account, builds the GUI and so forth.
  4. Work simply runs ⎕DQ
  5. Shutdown cleans up: it closes files, says good-by.

Solving the stop vector problem

Let's start with solving the stop vector problem:

⎕TRAP←⊂(#.Events.StopVector 'E' '→⎕LC')

#.Events.StopVector returns 1001 which is the event number a stop vector is associated with. As soon as APL stops on a stop vector ⎕EN is set to 1001. This event can be caught with ⎕TRAP, so we can tell APL to execute ('E') the expression given as third argument. In this case it tells APL to simply ignore stop vectors by resuming execution.

Preventing users from interrupting an application

The same technique can be used to prevent the user from interrupting the application, accidentally or purposely. Depending on the type of the application it might be a good idea to allow the user to interrupt the application by pressing either the key for the weak or the strong interrupt, to ask the user for confirmation and then to restart the application. This would allow the user to quit a lengthy operation that needs more time than expected.

Here, however, we will use this simple approach:

events←#.Events.WeakInterrupt #.Events.StrongInterrupt
⎕TRAP,←⊂(events 'E' '→⎕LC')

Restarting the application

For reasons explained in a minute we now have to define the “Restart the application” procedure. For this, for the first time we do not use the 'E' statement but the 'C' statement. The C is short for Cut back. This instructs APL to cut the status indicator back to the level where ⎕TRAP is localized – that is not necessarily where it was set – and execute the expression in the 3rd argument there. However, if ⎕TRAP is not localized at all, i.e. it is in the workspace, the status indicator is cut back completely and the expression is executed in the workspace.

⎕TRAP,←⊂(#.Events.RestartAppl 'C' '→∆Restart')

To make this work the function in which ⎕TRAP is localized must have a label ∆Restart or a function that returns a valid line number to branch to of course.

Catching Errors

If an unexpected error occurs, we want to execute a particular function to do the hard work.

⎕TRAP,←⊂((0 1000) 'E' '#.HandleError' )

The 0 stands for all the events from 1 to 999 while the 1000 stands for all events larger than 1000.

⎕TRAP may contain more than one error catching group. Since the contents of ⎕TRAP is scanned from left to right, a statement will ONLY be executed for an event not processed earlier. That is the reason why we must define the restart event first.

For example, in the following statement:

⎕TRAP←(333 'E' 'expA') (0 'E' 'expB')

event 333 will be caught by the 1st group and NOT by the 2nd even though 0 stands for “events from 1 to 999”. Only the expression expA will be executed.

The #.HandleError function

The #.HandleError function should do at least the following:

  • Perhaps neutralize ⎕LX
  • Save the ⎕DM and ⎕EN settings
  • Save the snapshot
  • Maybe create an HTML page with general information about the error
  • Ask the user about a restart
  • Either try a restart of shut the application down

Misc

Developers and others

Of course the error trapping mechanism must distinguish between developers and others. Often it is good enough to check the APL version: use error trapping in case of runtime, otherwise not. If this is not possible, because some or all of the users are running the development version too, you can specify a parameter to tell the application that you are a developer. By default the application can then use error trapping.

Testing Error Trapping

Keep in mind that you want to have an easy opportunity to test the system with error trapping. So you may need another parameter that tells the system that error trapping has to be used. Last but not least, there should be an easy opportunity to let the application crash on purpose. I prefer to have a “developers menu”, which is displayed only to developers. Among other useful commands it offers a “Let's crash” option.

Control Structure :Trap

If you use :Trap, keep in mind that ⎕TRAP and :Trap are both taken into account. That means that in case of

:Trap 0
	-'a'
:Else
	.
:EndTrap

the error caused by the -'a' statement is caught by the :Else, while the . is caught by the ⎕TRAP setting.

When using :Trap try to be as specific as possible. For example, this code is faulty:

:Trap 0
	filename ⎕FTIE 0
:else
	filename ⎕FCREATE 0
:EndTrap

because it tries to create a file not only if this file does not already exist but also if the current user lacks the right to tie it, for example because somebody else has already tied it exclusively. Therefore, it is a better to be specific:

:Trap 22
	filename ⎕FTIE 0
:else
	filename ⎕FCREATE 0
:EndTrap

The best idea, however, is to use ⎕NEXISTS to check the file for already being created. In general it is a good idea to use error trapping only for extraordinary problems.

The ⎕SIGNAL system function

Note that an event which is ⎕SIGNALled can be intercepted with ⎕TRAP but not :Trap If you execute this function:

∇test
 ⎕TRAP←501 'E' '⎕←''caught by ⎕TRAP'''
 :Trap 501
     ⎕SIGNAL 501
 :Else
     ⎕←'Caugth in :Else'
 :EndTrap
∇

you get this:

caught by ⎕TRAP

Ensure future trouble

A very easy way to create problems in the future is to do this:

:Trap 0
	DoSomethingHere
:EndTrap

This technique is called “silent trapping”. If something is going wrong, do not take care and do not tell anybody about it!

Switching Error Trapping on or off

When you use error trapping, make sure that you can switch off error trapping on a general level. The easiest way to implement this idea is something like this:

:Trap #.ErrorTrapFlag/0
	DoSomething
:else
	TakeCare
EndTrap

If the flag is true, error trapping is active, if not, the DoSomeThing statement will fail if an error occurs. This makes is much easier to debug an application.

You might need a more sophisticated mechanism for this, because under some circumstances you want to switch off most but not all error trapping statements. For example, if you use a logging mechanism which is logging every user action for analyzing purposes, the code doing this may cause an interrupt itself, for example because the disk is full which holds the logging files. In such a case it might be inappropriate that the logging code breaks the application. Therefore, you might control this code with :Trap-statements.

In such a case it might be a good idea to control the behavior of the application on different levels for code which is really essential in terms of business logic, for example, and for code which is not essential.

But even in such a case the problem should be communicated. I found the idea of a watchdog application very useful, which, among other tasks, is listening to UDP telegrams on a particular port. An application in trouble can then send a telegram to the watchdog, telling about the problem. Using a type of error class, the client can tell the watchdog about the seriousness of the problem, and the watchdog can then decide to simply display it on it's GUI or send a SMS message or/and an email to the admin.

Code

The below workspace contains all the code needed to implement the ideas discussed above.
(⎕IO ⎕ML ⎕WX)←1 1 3

∇ Info;∆
  ⎕←,[1.5]15⍴''
  ⎕←∆←'Error Trapping Sample Workspace'
  ⎕←'='⍴⍨⍴∆
  ⎕←''
  ⎕←'First check the setting in function:'
  ⎕←'#.INI.Set'
  ⎕←''
  ⎕←'Then run function:'
  ⎕←'#.App.Run (0|1)'
  ⎕←''
  ⎕←'Specify a 1 to enforce an error during the initialisation phase,'
  ⎕←'otherwise a 0'
  ⎕←'To enfore a "restartable" error, press the "Let''s crash" button'
  ⎕←''
  ⎕←'Kai Jaeger ⋄ APL Team Ltd ⋄ 15/2/2007'
  ⎕←'http://aplteam.com'
∇

∇ Reset
  :Trap 0
      App.CloseApp ⍬
  :EndTrap
∇

:Namespace App
⍝ === VARIABLES ===

    ErrorCounter←0

    LastErrorWasSavedAt←57008

    no←0

    yes←1

    ∆Btns←1

    ∆Caption←'Error Trapping Sample'

    ∆Style←'Error'


⍝ === End of variables definition ===

    (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

    ∇ {r}←{parms}Ask Question;∆STYLE;∆BTNS;∆CAPTION;∆EDGESTYLE;allowed;f;∆DEFAULT;∆
     ⍝⍝ Ask a question
     ⍝⍝ By default, 3 buttons (Y/N/Cancel) are displayed. You may change this by setting ∆BTNS (default=3) to 2
     ⍝⍝ The result is one of (1 2 3)
      ∆STYLE←'QUERY'
      ∆BTNS←3
      ∆CAPTION←'Question'
      ∆EDGESTYLE←'None'
      ∆DEFAULT←1
      :If 0<⎕NC'parms'
      :AndIf ~0∊⍴parms
          allowed←'Style' 'Caption' 'Btns' 'Default' 'EdgeStyle'
          ⍎(0∊⍴parms←CheckParms parms allowed)/'. ⍝ should never happen in production'
          ⍎(0∊⍴GetVarsFromParms parms)/'. ⍝ should never happen in production'
      :EndIf
      :If ~(⊂∆BTNS)∊1 2 3
          'Invalid parameter: BTNS'⎕SIGNAL 11
      :EndIf
      ∆←⊂'MsgBox'
      ∆,←⊂'Caption'∆CAPTION
      ∆,←⊂'Style'∆STYLE
      :Select ∆BTNS
      :Case 1
          ∆,←⊂'Btns' 'OK'
      :Case 2
          ∆,←⊂'Btns'('YES' 'NO')
      :Case 3
          ∆,←⊂'Btns'('YES' 'NO' 'CANCEL')
      :EndSelect
      ∆,←⊂'EdgeStyle'∆EDGESTYLE
      ∆,←⊂'Default'∆DEFAULT
     
      ∆,←⊂'Text'Question
      ∆,←⊂'Event'('MsgBtn1' 'MsgBtn2' 'MsgBtn3')1
      'f'⎕WC ∆
      r←⎕DQ'f'
      r←⍎¯1↑2⊃r
    ∇

    ∇ BuildGui dummy;∆
      ∆←⊂'Form'
      ∆,←⊂'Caption' 'Error-Trapping Test Workspace'
      ∆,←⊂'Coord' 'Pixel'
      ∆,←⊂'Size'(300 350)
      ∆,←⊂'Posn'(35 20)
      ∆,←⊂'Event'#.Events.QuitDQ 1
      ⎕WC ∆
     
      ∆←⊂'Button'
      ∆,←⊂'Caption' 'Let''s crash!'
      ∆,←⊂'Size'(35 200)
      ∆,←⊂'Event' 'Select' 'onSelectCrashBtn'
      ∆,←⊂'Default' 1
      'crash'⎕WC ∆
     
      ∆←⊂'Button'
      ∆,←⊂'Caption' 'Close'
      ∆,←⊂'Size'(10 10)
      ∆,←⊂'Posn'(5 5)
      ∆,←⊂'Visible' 0
      ∆,←⊂'Event' 'Select' 'onCloseApp'
      ∆,←⊂'Cancel' 1
      'cancel'⎕WC ∆
     
      'sb'⎕WC'Statusbar'
     
      ∆←⊂'StatusField'
     
     ⍝⍝⍝⍝⍝⍝ This does not work in 10.1 ⍝⍝⍝⍝⍝
      ∆,←⊂'Coord' 'Prop'
      ∆,←⊂'Size'(⍬ 98)
     ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
     
      'sb.f1'⎕WC ∆
     
     ⍝ sb.f1.Size←sb.Size-6 10 ⍝ only needed in 10.1
    ∇

    ∇ R←CheckParms Parms∆Allowed;Allowed;Parms;⎕IO;Depth;Bool;Buffer;⎕ML
     ⍝⍝ Check a parameter vector for valid entries. Valid entries are defined by Allowed.
     ⍝S Allowed is interpreted as a vector of possible valid names.
     ⍝S If ALLOWED is empty, no checks are performed.
     ⍝S R becomes a matrix. [;1] contains the parameter names, [;2] the values
     ⍝S In case of an invalid parameter, an error is generated by ⎕SIGNAL.
     ⍝S If no parameter is defined:  R←0 2⍴' '
     ⍝S If something is wrong with the data structures, R gets an empty vector.
     ⍝V Version: 3.0
      ⎕IO←1
      ⎕ML←3
      R←0 2⍴''                            ⍝ Initialyze the result
      :If ~0∊⍴Parms∆Allowed
          (Parms Allowed)←Parms∆Allowed   ⍝ Separate right argument
          :If 0∊⍴Parms
              R←0 2⍴''
              :Return
          :EndIf
          :If 0∊⍴Allowed
              'Invalid Parameter'⎕SIGNAL 11
          :EndIf
          :If 2=≡Parms
          :AndIf 2=⍴Parms
              Parms←,⊂Parms
          :EndIf
          :If 3>≡Parms                    ⍝ Handle...
          :AndIf 2∨.≠↑∘⍴¨Parms
              Parms←,⊂Parms               ⍝ ...Parms!
          :EndIf
          :If 0<+/Bool←2<↑∘⍴¨Parms
              (Bool/Parms)←{(↑⍵)(1↓⍵)}¨Bool/Parms
          :EndIf
          :If 2∨.≠↑∘⍴¨Parms←,Parms        ⍝ Check for proper structure.
              R←''                        ⍝ Structure invalid, complete faulty!
              :Return
          :EndIf
          Parms←↑,/Parms                  ⍝ Make simple vector
          Depth←≡Parms                    ⍝ Save the Depth
          :If 0=Depth
              R←0 2⍴''                    ⍝ Ready if empty: nothing right, nothing wrong...
              :Return
          :EndIf
          :If Depth∊0 1                   ⍝ Jump if not simple
              Parms←,⊂Parms               ⍝ Enforce a nested vector
          :EndIf
          :If 0≠2|⍴Parms←,Parms           ⍝ Jump if even number of items
              R←''                        ⍝ Structur invalid, get out!
          :Else
              Buffer←((⌊0.5×⍴Parms),2)⍴Parms ⍝ Build a matrix
              Buffer[;1]←' '~¨⍨ToUpper¨Buffer[;1]
              :If ~0∊⍴Allowed
                  :If (|≡Allowed)∊0 1     ⍝ Jump if Allowed is not simple
                      Allowed←⊂Allowed    ⍝ Enforce nested
                  :EndIf
                  Allowed←,Allowed        ⍝ Enforce a vector
                  Allowed←ToUpper¨Allowed
                  Bool←Buffer[;1]∊Allowed ⍝ Column 1 must be member of Allowed
                  :If 1∨.≠Bool
                      ('Invalid Parameter: ',1↓↑,/' ',¨(~Bool)/Buffer[;1])⎕SIGNAL 11
                  :EndIf
              :EndIf
              R←Buffer                    ⍝ All is fine
          :EndIf
      :EndIf
    ∇

    ∇ CloseApp dummy
      :Trap 0
          Close
      :EndTrap
    ∇

    ∇ {htmlfilename}←CreateHtmlInfoPage wsid;tag;html;wsid;head;htmlfilename
     ⍝⍝ Create or overwrite an HTML file with useful information about the snapshot
      tag←{⍺←'p' ⋄ ⎕TC[2 3],'<',⍺,'>',⍵,'</',⍺,'>'}
      html←'h1'tag wsid
      html,←'h2'tag'Previous WSID'
      html,←tag #.Error.WSID
      html,←'h2'tag'Error Message'
      html,←⊃,/tag¨#.Error.QDM
      html,←'h2'tag'Status Indicator'
      :If 0∊⍴#.Error.QSI
          html,←tag'--- Empty!'
      :Else
          html,←⊃,/tag¨↓#.Error.QSI
      :EndIf
      html,←'h2'tag'Error Line'
      :If 1=⍴⎕LC
          html,←tag'{None}'
      :Else
          html,←⎕LC[2]{0∊⍴body←⎕NR ⍵:'-- cannot provide information on "',⍵,'"' ⋄ ⊃,/tag(1+⍺)⊃body}2⊃⎕XSI
      :EndIf
      html←'style="font-family:Dyalog Std"'{w←¯1+⍵⍳'>' ⋄ (w↑⍵),(' ',⍺),w↓⍵}'body'tag html
      head←'title'tag'Error Workspace ',wsid
      head,←'<meta http-equiv="Content-Type" content="text/html;charset=ascii">'
      head←'head'tag head
      html←head,html
      html←'<html>',html,'</html>'
      html←'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',⎕TC[2 3],html
      htmlfilename←(739⌶0),'/',(⎕AN∩⎕A,819⌶⎕A),'-.html'
      ⎕NUNTIE htmlfilename ⎕NCREATE⍠'Unique' 1⊢0
      html ⎕NPUT htmlfilename 1
    ∇

    ∇ R←{CalledFromNamespace}GetVarsFromParms Parms;⎕IO;∆DUMMY;⎕ML
     ⍝⍝ Takes parameters like
     ⍝⍝     ('NEW' true) ('CAPTION' 'A Title')
     ⍝⍝ and create variables ∆NEW and ∆CAPTION from this.
     ⍝S The parameter vars are created in the namespace, where GetVarsFromParms
     ⍝S was called from or, if "CalledFromNamespace" was specified, within this
     ⍝S specified Namespace.
     ⍝S R is a list of the created variable names.
     ⍝S R is an empty vector, if the operation fails.
     ⍝V Version: 3.2.0
      ⎕IO←1
      ⎕ML←3
      R←''
      :If 0=⎕NC'CalledFromNamespace'
          CalledFromNamespace←↑⎕NSI
      :EndIf
      ⎕CS CalledFromNamespace            ⍝ Change to the namespace this fns was called from
      Parms⍪←'DUMMY'⍬                    ⍝ DUMMY is added to make the statement run if Parms is empty.
      Parms[;1]←'∆',¨Parms[;1]           ⍝ Add "∆" to avoid name conflicts.
      ⍎(↑,/' ',¨Parms[;1]),'←Parms[;2]'  ⍝ Create external parameters.
      R←¯1↓Parms[;1]                     ⍝ ¯1↓ drops the DUMMY.
    ∇

    ∇ HandleError type;Wsid_;Opt_;Rc_;Path_;wsid;oldWsid;⎕IO;⎕ML;txt;saveTrap;restartFlag
     ⍝⍝ General error handler which is executed by ⎕TRAP in production in case of an error
     ⍝⍝ Creates an error workspace (if not already to many Error Workspaces) and then
     ⍝⍝ signals a "Restart" event which should be captured on a high level by the application
     ⍝⍝ type←0 = Early state, restart would not work
     ⍝⍝ type←1 = Application is fully initialzed, restart should work
      ⎕IO←1
      ⎕ML←1
      saveTrap←⎕TRAP            ⍝ Remember original setting
      ⎕TRAP←(0 1000)'S'         ⍝ To prevent endles loops
      ErrorCounter=←+1          ⍝ Allows us to keep control over the number of snapshots
      Color.Red SetStatusbar'Error Snapshot is going to be saved...'
      :If #.INI.MaxNumberOfErrorWorkspaces>ErrorCounter
          :If 0≠LastErrorWasSavedAt
          :AndIf #.INI.ErrorDelay>(24 60 60⊥3↑3↓⎕TS)-LastErrorWasSavedAt
          ⍝ Too many error in a too small space of time
          ⍝ To prevent error trapping from creating countless
          ⍝ error workspaces we finish now.
              txt←''
              txt,←⊂'Sorry, we have encountered too many problems.'
              txt,←⊂'Therefore, the application is closed now.'
              ('Btns' 1)('Style' 'Error')('Caption' 'Error Trapping Sample')Ask txt
              Off 1
          :EndIf
          :If #.INI.SaveErrorWS
              '.'⎕WS'Cursor' 1
              ⎕EX'#.Error'
              '#.Error'⎕NS''
              #.Error.QDM←⎕DM
              #.Error.QSI←↑1↓⎕XSI,¨{'[',(⍕⍵),']'}¨⎕LC
              #.Error.WSID←⎕WSID
              #.Error.SaveTrap←saveTrap
              ⎕LX←'⍝',⎕LX
              wsid←({⍵↓⍨1+-⌊/'/\'⍳⍨⌽⍵}⎕WSID),#.INI.ErrorFolder,{,'ZI4,ZI2,ZI2,<_>,ZI2,ZI2,ZI2'⎕FMT,[0.5]⍵}6↑⎕TS
              SaveWsid wsid
              ⎕WSID←#.Error.WSID
              CreateHtmlInfoPage wsid
          :EndIf
          ⎕LX↓⍨←+/∧\'⍝'=⎕LX
          LastErrorWasSavedAt←24 60 60⊥3↑3↓⎕TS
          txt←'' 'Houston, we have a problem.' ''
          :If 2=⎕NC'wsid'
              txt,←⊂'System details have been saved in:'
              txt,←⊂wsid
              txt,←⊂''
          :EndIf
     
          :If type=0
              txt,←⊂'The application cannot proceed!'
              ('Btns' 1)('Style' 'Error')('Caption' 'Error Trapping Sample')Ask txt
              Off 1
          :Else
              txt,←⊂'The system will try to restart when you press "Yes".'
              restartFlag←yes=('Btns' 2)('Style' 'Error')('Caption' 'Error Trapping Sample')Ask txt
              ⎕TRAP←saveTrap
              :If ~restartFlag
                  Off 1
              :Else ⍝ okay, give it a second go:
                  sb.f1.Text←'Try to restart..'
                  sb.f1.BCol←¯1
                  ⎕DL 2
                  ⎕SIGNAL #.Events.RestartAppl ⍝ Try to restart
              :EndIf
          :EndIf
     
      :Else
     
          txt←''
          txt,←⊂'Sorry, we have encountered too many problems.'
          txt,←⊂'Therefore, the application is closed now.'
          ('Btns' 1)('Style' 'Error')('Caption' 'Error Trapping Sample')Ask txt
          Off 1
     
      :EndIf
    ∇

    ∇ Initial enforeEarlyError
     ⍝ Open files...
     ⍝ Create TCP listener...
     ⍝ Open connection to the database...
     
      LastErrorWasSavedAt←0 ⍝ No error workspaces...
      ErrorCounter←0        ⍝ ... so far
     
      ⍎enforeEarlyError/'.'
     
      BuildGui ⍬
    ∇

    ∇ r←IsDeveloper
     ⍝ Returns 1 if application is run in he Development version of APL
      r←'Development'≡(⎕IO+3)⊃'.'⎕WG'APLVersion'
    ∇


    ∇ Off status
      :If IsDeveloper
          :If status
              . ⍝ Developer: )RESET followed by #.Reset may be appropriate
          :Else
              →
          :EndIf
      :Else
          ⎕OFF
      :EndIf
    ∇

    ∇ Run enforeEarlyError;⎕TRAP
     ⍝⍝ Main function
      ⎕TRAP←SetTrap 0
      Initial enforeEarlyError
      Work
      Shutdown
    ∇

    ∇ SaveWsid wsid
      :Trap 0
          ⎕SAVE wsid
      :Else
          ⍝ TThat should not happen but can in case of WS FULL,
          ⍝ lack of access rights or when in the Tracer...
      :EndTrap
    ∇

    ∇ {color}SetStatusbar msg
      color←{2=⎕NC ⍵:⍎⍵ ⋄ Color.Default}'color'
      sb.f1.Text←msg
      sb.f1.BCol←color
    ∇

    ∇ r←SetTrap type
     ⍝⍝ Set ⎕TRAP accordingly to the current user (developer, others)
     ⍝⍝ type is:
     ⍝⍝ 0 = early stage (#.App.Initial is running, for example)
     ⍝⍝ 1 = application is fully initialized, restarted might be possible
      r←''
      :If IsDeveloper
          r,←⊂(1002 1003)'S'
          :If #.INI.TestErrorTrapping
              :If 1=type ⍝ Does a restart make possibly sense?!
                  r,←⊂#.Events.RestartAppl'C' '→∆Restart'
              :EndIf
              r,←⊂(0 1000)'E'('#.App.HandleError ',⍕type)
          :EndIf
          r,←⊂(0 1000)'S'
      :Else
          r,←⊂#.Events.StopVector'E' '→⎕LC'
          r,←⊂#.Events.(WeakInterrupt StrongInterrupt)'E' '→⎕LC'
          :If 1=type ⍝ Does a restart make possibly sense?!
              r,←⊂#.Events.RestartAppl'C' '→∆Restart'
          :EndIf
          r,←⊂(0 1000)'E'('#.App.HandleError ',⍕type)
      :EndIf
    ∇

    ∇ Shutdown
      :Trap 0
          CloseApp ⍬
      :EndTrap
      ⎕←'By'
      Off 0
    ∇

    ∇ string←ToUpper string;TOUPP
      'TOUPP'⎕NA'I4 USER32.C32|CharUpperA =0T'
      string←2⊃TOUPP⊂string
    ∇

    ∇ Work;⎕TRAP
     ⍝⍝ Basically run ⎕DQ
     ⍝⍝ In charge for restart application after an error
      ⎕TRAP←SetTrap 1
     
     ∆Restart:
     
      '.'⎕WS'Cursor' 0
     
      :If ErrorCounter=0
          Color.Green SetStatusbar'I''m fine, thank you'
      :Else
          Color.Blue SetStatusbar'So far ',(⍕ErrorCounter),' snapshots saved'
      :EndIf
     
      ⎕DQ''
     ⍝
    ∇

    ∇ onCloseApp msg;ref
      CloseApp ⍬
    ∇

    ∇ onSelectCrashBtn msg
      . ⍝ To enforce an error; Press <Ctrl+Enter> in this line
    ∇

    :Namespace Color
⍝ === VARIABLES ===

        Blue←0 0 255

        Default←0

        Green←0 255 0

        Red←255 0 0


⍝ === End of variables definition ===

        (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

    :EndNamespace
    :Namespace cancel
        (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

    :EndNamespace
    :Namespace crash
        (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

    :EndNamespace
    :Namespace sb
        (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

        :Namespace f1
⍝ === VARIABLES ===

            BCol←255 0 0

            Text←'Error Snapshot is going to be saved...'


⍝ === End of variables definition ===

            (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

        :EndNamespace
    :EndNamespace
:EndNamespace
:Namespace Error

    ⎕ML  ←0 ⍝ *** DO NOT change these system variables here, only after the variables definition

⍝ === VARIABLES ===

    _←⍬
    _,←'SYNTAX ERROR' 'Off[3] . ⍝ Developer: )RESET followed by #.Reset may be appropriate'
    _,←,⊂'                                                                   ∧'
    QDM←_

    _←⍬
    _,←⊂'#.App.Off[3]             '
    _,←⊂'#.App.HandleError[56]    '
    _,←⊂'#.App.onSelectCrashBtn[1]'
    _,←⊂'#.App.Work[15]           '
    _,←⊂'#.App.Run[4]             '
    QSI←↑_

    SaveTrap←((1002 1003) 'S') ((,501) 'C' '→∆Restart') ((0 1000) 'E' '#.App.HandleError 1') ((0 1000) 'S')

    WSID←'C:\Users\Adam.DYALOG\Downloads\ErrorTrapping'

    ⎕ex '_'

⍝ === End of variables definition ===

    (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

:EndNamespace
:Namespace Events
⍝ === VARIABLES ===

    QuitDQ←2000

    RestartAppl←501


⍝ === End of variables definition ===

    (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

    ∇ r←StopVector
      r←1001
    ∇

    ∇ r←StrongInterrupt
      r←1003
    ∇

    ∇ r←WeakInterrupt
      r←1002
    ∇

:EndNamespace
:Namespace INI
⍝ === VARIABLES ===

    ErrorDelay←5

    ErrorFolder←''

    MaxNumberOfErrorWorkspaces←5

    SaveErrorWS←1

    TestErrorTrapping←1


⍝ === End of variables definition ===

    (⎕IO ⎕ML ⎕WX ⎕DIV)←1 0 1 1

    ∇ Set
      TestErrorTrapping←1            ⍝ Normally, error trapping is not used in a Developer Version
      MaxNumberOfErrorWorkspaces←5   ⍝ How often is it possible for a user to restart the system?
      ErrorDelay←5                   ⍝ Number of seconds until the next error WS is saved
      SaveErrorWS←1                  ⍝ Should a snapshot workspace be saved?
      ErrorFolder←''                 ⍝ Folder the snapshot WS will be saved in
    ∇

:EndNamespace

See also

External links

Lessons

Documentation

APL development [edit]
Interface SessionTyping glyphs (on Linux) ∙ FontsText editors
Publications IntroductionsLearning resourcesSimple examplesAdvanced examplesMnemonicsISO 8485:1989ISO/IEC 13751:2001A Dictionary of APLCase studiesDocumentation suitesBooksPapersVideosAPL Quote QuadVector journalTerminology (Chinese, German) ∙ Neural networksError trapping with Dyalog APL (in forms)
Sharing code Backwards compatibilityAPLcartAPLTreeAPL-CationDfns workspaceTatinCider
Implementation ResourcesOpen-sourceMagic functionPerformanceAPL hardware
Developers Timeline of corporationsAPL2000DyalogIBMIPSASTSC