Handling Errors in BPL
This topic explains how BPL business processes support error handling. BPL provides fault handlers that allow your business process to throw and catch errors, and compensation handlers that allow your business process to specify how it recovers from errors by undoing the actions that led to the error condition.
When you use this error handling system with <call> statements that communicate with other business hosts, make sure that the target business hosts return an error status in the case of an error. If the target component returns success even in the case of an error, the BPL process will not trigger <catchall> logic.
The BPL elements involved in error handling are <scope>, <throw>, <catch>, <catchall>, <compensate>, <compensationhandlers>, <compensationhandler>, and <faulthandlers>. This topic introduces these elements and explains how they work together to support the different error handling scenarios.
System Error with No Fault Handling
The following is an example of a BPL business process that produces an error condition and provides no error handling:
This BPL business process does the following:
-
The first <trace> element generates the message before assign.
-
The <assign> element tries to set SomeProperty equal to the expression 1/0. This attempt produces a divide-by-zero system error.
-
The business process ends and sends a message to the Event Log.
The second <trace> element is never used.
Event Log Entries
The corresponding Event Log entries look like this.
For background information, see Event Log.
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before assign"'/>
<assign property="SomeProperty" value="1/0"/>
<trace value='"after assign"'/>
</sequence>
</process>
}
System Error with Catchall
To enable error handling, BPL defines an element called <scope>. A scope is a wrapper for a set of activities. This scope may contain one or more activities, one or more fault handlers, and zero or more compensation handlers. The fault handlers are intended to catch any errors that activities within the <scope> produce. The fault handlers may invoke compensation handlers to compensate for those errors.
The following example provides a <scope> with a <faulthandlers> block that includes a <catchall>. Because the <scope> includes a <faulthandlers> element, the rectangle includes a horizontal dashed line across the middle; the area below this line displays the contents of the <faulthandlers>.
This BPL business process does the following:
-
The first <trace> element generates the message before scope.
-
The <scope> element starts the scope.
-
The second <trace> element generates the message before assign.
-
The <assign> element tries to evaluate the expression 1/0. This attempt produces a divide-by-zero system error.
-
Control now goes to the <faulthandlers> defined within the <scope>. The <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element. In this case, there is no <catch>, but there is a <catchall> element, so control goes there.
Note that InterSystems IRIS® skips the <trace> element message immediately after the <assign> element.
If we drill down into <catchall>, we see this:
-
Within <catchall>, a <trace> element generates the message in catchall faulthandler.
-
Within <catchall>, another <trace> element generates a message that explores the nature of the error using $System.Status methods and the special variables %Context and %LastError. See the details in Event Log Entries.
-
The <scope> ends.
-
The last <trace> element generates the message after scope.
Event Log Entries
The corresponding Event Log entries look like this.
If an unexpected system error occurs, and a <faulthandlers> block is present inside a <scope>, the BPL business process does not automatically place entries in the Event Log as shown in the System Error with No Fault Handling example. Rather, the <faulthandlers> block determines what the business process will do. In the current example, it outputs a <trace> message that contains information about the error. The Event Log entry showing the actual error is produced by the following statement within the <catchall> block:
<trace value=
'"%LastError "_
$System.Status.GetErrorCodes(..%Context.%LastError)_
" : "_
$System.Status.GetOneStatusText(..%Context.%LastError)'
/>
The BPL context variable %LastError always contains a %StatusOpens in a new tab value. If the error was an unexpected system error such as <UNDEF> this %StatusOpens in a new tab value is created from the error “ObjectScript error” which has code 5002, and the text of the $ZERROR special variable. To get the corresponding error code and text out of %LastError, use the $System.Status methods GetErrorCodes and GetOneStatusText, then concatenate them into a <trace> string, as shown above.
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before scope"'/>
<scope>
<trace value='"before assign"'/>
<assign property="SomeProperty" value="1/0"/>
<trace value='"after assign"'/>
<faulthandlers>
<catchall>
<trace value='"in catchall faulthandler"'/>
<trace value=
'"%LastError "_
$System.Status.GetErrorCodes(..%Context.%LastError)_
" : "_
$System.Status.GetOneStatusText(..%Context.%LastError)'
/>
</catchall>
</faulthandlers>
</scope>
<trace value='"after scope"'/>
</sequence>
</process>
}
Thrown Fault with Catchall
When a <throw> statement executes, its fault value is an expression that evaluates to a string. Faults are not objects, as in other object-oriented languages such as Java; they are string values. When you specify a fault string it needs the extra set of quotes to contain it, as shown below:
<throw fault='"thrown"'/>
When a <throw> statement executes, control immediately goes to the <faulthandlers> block inside the same <scope>, skipping all intervening statements after the <throw>. Inside the <faulthandlers> block, the program attempts to find a <catch> block whose value attribute matches the fault string expression in the <throw> statement. This comparison is case-sensitive.
If there is a <catch> block that matches the fault, the program executes the code within this <catch> block and then exits the <scope>. The program resumes execution at the next statement following the closing </scope> element.
If a fault is thrown, and the corresponding <faulthandlers> block contains no <catch> block that matches the fault string, control goes from the <throw> statement to the <catchall> block inside <faulthandlers>. After executing the contents of the <catchall> block, the program exits the <scope>. The program resumes execution at the next statement following the closing </scope> element. It is good programming practice to ensure that there is always a <catchall> block inside every <faulthandlers> block, to ensure that the program catches any unanticipated errors.
Suppose you have the following BPL. For reasons of space, the <start> and <end> elements are not shown.
This BPL business process does the following:
-
The first <trace> element generates the message before scope.
-
The <scope> element starts the scope.
-
The second <trace> element generates the message before assign.
-
The <throw> element throws a specific, named fault ("MyFault").
-
Control now goes to the <faulthandlers> defined within the <scope>. The <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element. In this case, there is no <catch> but there is a <catchall> element, so control goes there.
Note that InterSystems IRIS skips the third <trace> element.
If we drill down into <catchall>, we see this:
-
Within <catchall>, the first <trace> element generates the message in catchall faulthandler.
-
Within <catchall>, the second <trace> element generates the message that provides information on the fault using $System.Status methods and the special variables %Context and %LastError. The %LastError value as the result of a thrown fault is different from its value as the result of a system error:
-
GetErrorCodes returns <Ens>ErrBPLThrownFault
-
GetOneStatusText returns text derived from the fault expression in the <throw> statement
-
-
Within <catchall>, the third <trace> element generates a message that provides information on the fault using the BPL context variable %LastFault. It contains the text derived from the fault expression from the <throw> statement.
-
The <scope> ends.
-
The last <trace> element generates the message after scope.
Event Log Entries
The corresponding Event Log entries look like this:
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before scope"'/>
<scope>
<trace value='"before assign"'/>
<throw fault='"MyFault"'/>
<trace value='"after assign"'/>
<faulthandlers>
<catchall>
<trace value='"in catchall faulthandler"'/>
<trace value=
'"%LastError "_
$System.Status.GetErrorCodes(..%Context.%LastError)_
" : "_
$System.Status.GetOneStatusText(..%Context.%LastError)'
/>
<trace value='"%LastFault "_..%Context.%LastFault'/>
</catchall>
</faulthandlers>
</scope>
<trace value='"after scope"'/>
</sequence>
</process>
}
Thrown Fault with Catch
A thrown fault may reach a <catchall>, as in the previous example, or it may have a specific <catch>.
Suppose you have the following BPL:
This BPL business process does the following:
-
The first <trace> element generates the message before scope.
-
The <scope> element starts the scope.
-
The second <trace> element generates the message before throw.
-
The <throw> element throws a specific, named fault ("MyFault").
-
Control now goes to the <faulthandlers> defined within the <scope>. The <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element. In this case, a <catch> element exists whose fault value is "MyFault", so control goes there. The <catchall> element is ignored.
Note that InterSystems IRIS skips the <trace> element message after the <throw> element.
If we drill down into <catch>, we see this:
Note:If a <catchall> is provided, it must be the last statement in the <faulthandlers> block. All <catch> blocks must appear before <catchall>.
-
Within <catch>, the <trace> element generates the message in catch faulthandler for ‘MyFault’.
-
The <scope> ends.
-
The last <trace> element generates the message after scope.
Event Log Entries
The corresponding Event Log entries look like this:
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before scope"'/>
<scope>
<trace value='"before throw"'/>
<throw fault='"MyFault"'/>
<trace value='"after throw"'/>
<faulthandlers>
<catch fault='"MyFault"'>
<trace value='"In catch faulthandler for 'MyFault'"'/>
</catch>
<catchall>
<trace value='"in catchall faulthandler"'/>
<trace value=
'"%LastError "_
$System.Status.GetErrorCodes(..%Context.%LastError)_
" : "_
$System.Status.GetOneStatusText(..%Context.%LastError)'
/>
<trace value='"%LastFault "_..%Context.%LastFault'/>
</catchall>
</faulthandlers>
</scope>
<trace value='"after scope"'/>
</sequence>
</process>
}
Nested Scopes, Inner Fault Handler Has Catchall
It is possible to nest <scope> elements. An error or fault that occurs within the inner scope may be caught within the inner scope, or the inner scope may ignore the error and allow it to be caught by the <faulthandlers> block in the outer scope. The next several topics illustrate how BPL handles errors and faults that occur within an inner scope, when two or more scopes are nested.
Suppose you have the following BPL (shown here without the <start> and <end> elements):
This BPL business process does the following:
-
The first <trace> element generates the message before outer scope.
-
The first <scope> element starts the outer scope.
-
The second <trace> element generates the message in outer scope, before inner scope.
-
The second <scope> element starts the inner scope.
-
The next <trace> element generates the message in inner scope, before assign.
-
The <assign> element tries to evaluate the expression 1/0. This attempt produces a divide-by-zero system error.
-
Control now goes to the <faulthandlers> defined within the inner <scope>. This <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element. In this case, there is no <catch> but there is a <catchall>, so control goes there.
Note that InterSystems IRIS skips the <trace> element immediately after the <assign> element.
If we drill into this <catchall>, we see this:
-
Within this <catchall>, the <trace> element generates the message in inner scope, catchall.
-
The inner <scope> ends.
-
The next <trace> element generates the message in outer scope, after inner scope.
-
The outer <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element that contains a <catchall>. Because there is no fault, this <catchall> is ignored.
-
The outer <scope> ends.
-
The last <trace> element generates the message after outer scope.
Event Log Entries
The corresponding Event Log entries look like this:
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before outer scope"'/>
<scope>
<trace value='"in outer scope, before inner scope"'/>
<scope>
<trace value='"in inner scope, before assign"'/>
<assign property="SomeProperty" value="1/0"/>
<trace value='"in inner scope, after assign"'/>
<faulthandlers>
<catchall>
<trace value='"in inner scope, catchall"'/>
</catchall>
</faulthandlers>
</scope>
<trace value='"in outer scope, after inner scope"'/>
<faulthandlers>
<catchall>
<trace value='"in outer scope, catchall"'/>
</catchall>
</faulthandlers>
</scope>
<trace value='"after outer scope"'/>
</sequence>
</process>
}
Nested Scopes, Outer Fault Handler Has Catchall
Suppose you have the following BPL (partially shown):
The rest of this BPL is as follows:
This BPL business process does the following:
-
The first <trace> element generates the message before outer scope.
-
The first <scope> element starts the outer scope.
-
The next <trace> element generates the message in outer scope, before inner scope.
-
The second <scope> element starts the inner scope.
-
The next <trace> element generates the message in inner scope, before assign.
-
The <assign> element tries to evaluate the expression 1/0. This attempt produces a divide-by-zero system error.
-
Control now goes to the <faulthandlers> defined within the inner <scope>. This <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element. In this case, a <catch> exists, but its fault value does not match the thrown fault. There is no <catchall> in the inner scope.
Note that InterSystems IRIS skips the <trace> element that is immediately after <assign>.
-
Control now goes to the <faulthandlers> block in the outer <scope>. No <catch> matches the fault, but there is a <catchall> block. Control goes to this <catchall>.
If we drill into this <catchall>, we see this:
-
Within this <catchall>, the <trace> element generates the message in outer scope, catchall.
-
The outer <scope> ends.
-
The last <trace> element generates the message after outer scope.
Event Log Entries
The corresponding Event Log entries look like this:
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before outer scope"'/>
<scope>
<trace value='"in outer scope, before inner scope"'/>
<scope>
<trace value='"in inner scope, before assign"'/>
<assign property="SomeProperty" value="1/0"/>
<trace value='"in inner scope, after assign"'/>
<faulthandlers>
<catch fault='"MismatchedFault"'>
<trace value=
'"In catch faulthandler for 'MismatchedFault'"'/>
</catch>
</faulthandlers>
</scope>
<trace value='"in outer scope, after inner scope"'/>
<faulthandlers>
<catchall>
<trace value='"in outer scope, catchall"'/>
</catchall>
</faulthandlers>
</scope>
<trace value='"after outer scope"'/>
</sequence>
</process>
}
Nested Scopes, No Match in Either Scope
Suppose you have the following BPL (partially shown):
The rest of this BPL is as follows:
This BPL business process does the following:
-
The first <trace> element generates the message before outer scope.
-
The first <scope> element starts the outer scope.
-
The next <trace> element generates the message in outer scope, before inner scope.
-
The second <scope> element starts the inner scope.
-
The next <trace> element generates the message in inner scope, before assign.
-
The <assign> element tries to evaluate the expression 1/0. This attempt produces a divide-by-zero system error.
-
Control now goes to the <faulthandlers> block in the inner <scope>. The <scope> rectangle includes a horizontal dashed line across the middle; the area below this dashed line displays the contents of the <faulthandlers> element. In this case, a <catch> exists, but its fault value does not match the thrown fault. There is no <catchall> in the inner scope.
-
Control now goes to the <faulthandlers> block in the outer <scope>. No <catch> matches the fault, and there is no <catchall> block.
-
The BPL immediately stops, sending a message to the Event Log.
Event Log Entries
The corresponding Event Log entries look like this.
There is an important difference between this Event Log and the one in the System Error with No Fault Handling example. The two examples have this in common: Each fails to provide adequate fault handling for the case when the divide-by-zero error occurs.
The difference is that the System Error with No Fault Handling example has no <scope> and no <faulthandlers> block. Under these circumstances, InterSystems IRIS automatically outputs the system error to the Event Log, as shown in the first example.
The current example is different because each <scope> does include a <faulthandlers> block. Under these circumstances, InterSystems IRIS does not automatically output the system error to the Event Log, as it did in the System Error with No Fault Handling example. It is up to the BPL business process developer to decide to output <trace> messages to the Event Log in case of an unexpected error. In the current example, no <faulthandlers> block catches the fault, so the only information that is traced regarding the system error is contained in the automatic message about business process termination (item 4 above).
The system error message does appear in the ObjectScript shell:
ERROR #5002: ObjectScript error: <DIVIDE>zS4+3^Test.Scope.BusinessProcess.Thread1.1
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before outer scope"'/>
<scope>
<trace value='"in outer scope, before inner scope"'/>
<scope>
<trace value='"in inner scope, before assign"'/>
<assign property="SomeProperty" value="1/0"/>
<trace value='"in inner scope,after assign"'/>
<faulthandlers>
<catch fault='"MismatchedFault"'>
<trace value=
'"In catch faulthandler for 'MismatchedFault'"'/>
</catch>
</faulthandlers>
</scope>
<trace value='"in outer scope, after inner scope"'/>
<faulthandlers>
<catch fault='"MismatchedFault"'>
<trace value=
'"In catch faulthandler for 'MismatchedFault'"'/>
</catch>
</faulthandlers>
</scope>
<trace value='"after outer scope"'/>
</sequence>
</process>
}
Nested Scopes, Outer Fault Handler Has Catch
Suppose you have the following BPL (partially shown):
The rest of this BPL is as follows:
This BPL business process does the following:
-
The first <trace> element generates the message before outer scope.
-
The first <scope> element starts the outer scope.
-
The next <trace> element generates the message in outer scope, before inner scope.
-
The second <scope> element starts the inner scope.
-
The next <trace> element generates the message in inner scope, before throw.
-
The <throw> element throws a specific, named fault ("MyFault").
-
Control now goes to the <faulthandlers> defined within the inner <scope>. A <catch> exists, but its fault value is "MismatchedFault". There is no <catchall> in the inner scope.
-
Control goes to the <faulthandlers> block in the outer <scope>. It contains a <catch> whose fault value is "MyFault".
-
The next <trace> element generates the message in outer scope catch faulthandler for 'MyFault'.
-
The second <scope> ends.
-
The last <trace> element generates the message after outer scope.
Event Log Entries
The corresponding Event Log entries look like this:
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<sequence>
<trace value='"before outer scope"'/>
<scope>
<trace value='"in outer scope, before inner scope"'/>
<scope>
<trace value='"in inner scope, before throw"'/>
<throw fault='"MyFault"'/>
<trace value='"in inner scope, after throw"'/>
<faulthandlers>
<catch fault='"MismatchedFault"'>
<trace value=
'"In inner scope catch faulthandler for 'MismatchedFault'"'/>
</catch>
</faulthandlers>
</scope>
<trace value='"in outer scope, after inner scope"'/>
<faulthandlers>
<catch fault='"MyFault"'>
<trace value=
'"In outer scope catch faulthandler for 'MyFault'"'/>
</catch>
</faulthandlers>
</scope>
<trace value='"after outer scope"'/>
</sequence>
</process>
}
Thrown Fault with Compensation Handler
In business process management, it is often necessary to reverse some segment of logic. This convention is known as “compensation.” The ruling principle is that if the business process does something, it must be able to undo it. That is, if a failure occurs, the business process must be able to compensate by undoing the action that failed. You need to be able to unroll all of the actions from that failure point back to the beginning, as if the problem action never occurred. BPL enables this with a mechanism called a compensation handler.
BPL <compensationhandler> blocks are somewhat like subroutines, but they do not provide a generalized subroutine mechanism. You can “call” them, but only from <faulthandler> blocks, and only within the same <scope> as the <compensationhandler> block. The <compensate> element invokes a <compensationhandler> block by specifying its name as a target. Extra quotes are not needed for this syntax:
<compensate target="general"/>
Compensation handlers are only useful if you can undo the actions already performed. For example, if you transfer money into the wrong account, you can transfer it back again, but there are some actions that cannot be neatly undone. You must plan compensation handlers accordingly, and also organize them according to how far you want to roll things back.
Suppose you have the following BPL:
This BPL business process does the following:
-
The Context tab (not shown) defines a property called MyBalance and sets its value to 100.
-
The first <trace> element generates the message before scope balance is, followed by the value of MyBalance.
-
The <scope> element starts the scope.
-
The next <trace> element generates the message before debit.
-
The <assign> element decrements MyBalance by 1.
-
The next <trace> element generates the message after debit.
-
The <throw> element throws a specific, named fault ("BuyersRegret").
-
Control now goes to the <faulthandlers>. A <catch> exists whose fault value is "BuyersRegret", so control goes there.
If we drill down into this <catch> element, we see the following:
-
Within this <catch>, the first <trace> element generates the message in catch faulthandler for 'BuyersRegret'.
-
Within this <catch>, the second <trace> element generates the message before restore balance is, followed by the current value of MyBalance.
-
The <compensate> element is used. For this element, target is a <compensationhandler> whose name is RestoreBalance. Within this <compensationhandler> block:
-
A <trace> statement outputs the message “Restoring Balance”
-
An <assign> statement increments MyBalance by 1.
Note:It is not possible to reverse the order of <compensationhandlers> and <faulthandlers>. If both blocks are provided, <compensationhandlers> must appear first and <faulthandlers> second.
-
-
The next <trace> element generates the message after restore balance is, followed by the current value of MyBalance.
-
The <scope> ends.
-
The last <trace> element generates the message after scope balance is, followed by the current value of MyBalance.
Event Log Entries
The corresponding Event Log entries look like this:
XData for This BPL
This BPL is defined by the following XData block:
XData BPL
{
<process language='objectscript'
request='Test.Scope.Request'
response='Test.Scope.Response' >
<context>
<property name="MyBalance" type="%Library.Integer" initialexpression='100'/>
</context>
<sequence>
<trace value='"before scope balance is "_context.MyBalance'/>
<scope>
<trace value='"before debit"'/>
<assign property='context.MyBalance' value='context.MyBalance-1'/>
<trace value='"after debit"'/>
<throw fault='"BuyersRegret"'/>
<compensationhandlers>
<compensationhandler name="RestoreBalance">
<trace value='"Restoring Balance"'/>
<assign property='context.MyBalance' value='context.MyBalance+1'/>
</compensationhandler>
</compensationhandlers>
<faulthandlers>
<catch fault='"BuyersRegret"'>
<trace value='"In catch faulthandler for 'BuyersRegret'"'/>
<trace value='"before restore balance is "_context.MyBalance'/>
<compensate target="RestoreBalance"/>
<trace value='"after restore balance is "_context.MyBalance'/>
</catch>
<catchall>
<trace value='"in catchall faulthandler"'/>
<trace value=
'"%LastError "_
$System.Status.GetErrorCodes(..%Context.%LastError)_
" : "_
$System.Status.GetOneStatusText(..%Context.%LastError)'
/>
<trace value='"%LastFault "_..%Context.%LastFault'/>
</catchall>
</faulthandlers>
</scope>
<trace value='"after scope balance is "_context.MyBalance'/>
</sequence>
</process>
}