Well, may be. When the house of cards you are in begins to fall, wouldn’t it be nice to grab Fluffy the cat, your wallet, phone, and perhaps the car keys? Sometimes a program engages in operations that just cannot be casually aborted without an orderly shutdown. Within Legato, there is a function that help you to just that. This can even apply when a run-time error occurs within your script.
The Grand Unwind
Under certain circumstances it can desirable or even mandatory to clean up after a script error, abnormal exit, or even during normal exit of a script. By default, Legato will close all handles and complete any pending operations on the referenced objects. However, in some cases the script may be working with an external system or website and need to perform more complex cleanup. In other cases, the order in which objects are closed may be important. An ‘unwind’ function can be used to complete any pending processing.
Upon exit of the internal ‘run’ function within the script engine, the engine will check to see if an unwind function has been defined. If so, it is executed. The unwind function does not change the script’s return value. In the event of a run-time error, the unwind function is executed and the script will still return the original error condition.
Creating an unwind function requires two things: defining an actual function of your own choosing and then letting the script engine know it is available.
The Unwind Function
The unwind function is defined by the SetUnwindFunction function:
int = SetUnwindFunction ( string function );
The SetUnwindFunction function takes a single parameter: a function name which is treated as a string. Since it is a string and processed at run-time, the function name does not require a prototype to be defined prior to using the function in the body of the code. If the function cannot be located or that name is defined as a variable, a formatted error code will be returned.
Defining the unwind function is rather simple:
void my_unwind_function() {
... some code ...
}
Upon entry, all global variables and handles are available for processing. Objects that have not been previously closed can be accessed by the unwind function. The unwind function then allows for cleanup of things such as SQL transactions and other information. Since the unwind operation can use global handles, for clarity purposes scripts should clear global handle variables as they are closed using either zero or NULL_HANDLE.
The last error code is set to the condition that resulted in the unwind function being called. The first function executed within the unwind operation should be GetLastError. If the unwind function was called as the result of a normal exit, the last value will be zero (ERROR_NONE).
An example of using an unwind function:
handle hWP; // A global handle for some activity
//////////////////////////////////////
int main() { // Normal Entry
SetUnwindFunction("my_unwind");
hWP = WordParseCreate(WP_GENERAL, "Test A B C"); // Start a process
// ... do a bunch of things ...
FormatString(); // <-- Causes run-time missing parameter error
// (never gets here)
CloseHandle(hWP); // Close the global handle
hWP = NULL_HANDLE; // An example of keeping things neat
MessageBox('i', "In main(), no force close"); // Should not get here
return 0;
}
//////////////////////////////////////
void my_unwind() { // Called on Error or Exit
int rc;
string s1;
rc = GetLastError(); // Get the last error condition
if (IsValidHandle(hWP)) { // Check the handle
s1 = WordParseGetWord(hWP); // Try to use global handle
}
if (s1 == "") { s1 = "ERROR on handle"; }
MessageBox('i', "Unwind - %08X\r\r%s", rc, s1); // Report the error and some data
CloseHandle(hWP); // Close the global handle
}
The example illustrates the basic structure of an unwind function. At the start, the unwind function my_unwind is set. For an example action, a word parse operation is started. Note that this is for illustration purposes only. A Word Parse Object, like most other objects in Legato, will cleanup automatically. Presumably, a more sophisticated action would be started, such as opening a transaction with a production or CRM system.
To cause an abort, the FormatString function is called with no parameters. An abort can also be forced using the ForceScriptError function, but we’ll talk more on that later. As a result of the error, the code below the FormatString function call in main is never executed. Since the FormatString function produces a run-time error, the error will be added to the run-time error log.
With the error causing a trigger, our my_unwind function is called as the script engine exits. Within the unwind function, the first action is to get the last error code. This is because if any other functions are called prior to the GetLastError function, the contents of the last error buffer will be replaced with the result of that function and whatever error state that triggered the unwind operation will be lost. If a script run-time error caused the unwind operation, the code will contain the specific script message causing the error. The last error can be used to determine if the unwind function was called as the result of an error versus just a normal program exit.
The unwind function can then perform any necessary processing and exit.
Forcing a Stop
Sometimes it can be necessary to force the script to have a run-time error either because of condition or for testing purposes. The ForceScriptError function can facilitate this action:
int = ForceScriptError ( dword code );
The code parameter is a dword to pass as the error. Note that the value must have the top bit set to indicate an error condition (ERROR_SOFT or 0x80000000). Otherwise the internal script processing will not consider the code to be an error. As a result, failing to set that bit causes the return value to contain a range error and a run-time error is not forced. For example:
ForceScriptError(ERROR_SOFT | 0x00001234);
will trigger the unwind function to be executed with 0x80001234 in the last error buffer.
The ForceScriptError function can also be used to have a controlled exit without creating an elaborate set of tests while each function on the stack processes a specific error.
The exit statement will force a script to quit at any point without crawling back up the call stack, but like ForceScriptError is messy and does not allow for any programmatic cleanup. The unwind will be run allowing for cleanup. The exit statement will set the error 0x808003E8 or translated to “Info: Script requested immediate exit”.
Is Unwind Defined?
In more complex setups, it may be necessary to determine if an unwind function has already been defined. The GetUnwindFunction function will return the name of the unwind function, if defined.
Conclusion
While most scripts do not require an unwind function, it is a good tool to have in the programming arsenal. Many languages have some method of capturing errors for further processing. For example, a try-catch construct is common in languages like JavaScript. As necessity is the mother of invention, unwinding was originally added to Legato to support SQL query activities where a database table was locked for editing and subsequently unlocked upon completion of a transaction. If the series of SQL queries did not complete normally, the specific table could remain in a locked state. Later, when the same script or other program would query again, that query would fail.
Another good application of an unwind operation is capturing an unlikely but serious error that can occur in multiple places in the code. Rather than write capture and recovery code in each location, the unwind function can serve as one solution fits all approach. This can reduce the amount complex error checking and recovery code.
Finally, one might be tempted to cleanup hooks using an unwind function. This seems like a good idea in theory, but since a hook runs a function directly rather than from the main entry point to the script engine, the unwind function will never be called. This functionality will be added in a later version of Legato.
Scott Theis is the President of Novaworks and the principal developer of the Legato scripting language. He has extensive expertise with EDGAR, HTML, XBRL, and other programming languages. |
Additional Resources
Novaworks’ Legato Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato