I have been trying to use a long jump error handler to allow a Trap routine to raise an error which can be handled in the ‘Normal’ execution context.
My main concern with this was ensuring that any active UNDO handlers in the Normal execution context are properly executed when the error is raised.
I put together a minimal example to test that this indeed would happen:
MODULE LongJump
VAR errnum MY_ERROR := -1;
VAR intnum timer_interrupt;
PROC main()
IEnable;
IDelete timer_interrupt;
CONNECT timer_interrupt WITH TimerTrap;
ITimer 3.5, timer_interrupt;
Procedure1;
ERROR (MY_ERROR)
TPWrite "Error handled from long jump";
RETURN;
ENDPROC
PROC Procedure1()
TPWrite "Executing the program...";
WaitTime 100;
UNDO
TPWrite "Cleaning up 1...";
ENDPROC
TRAP TimerTrap
BookErrNo MY_ERROR;
RAISE MY_ERROR;
ERROR
RAISE;
ENDTRAP
ENDMODULE
My expected output from this would be:
Executing Program
Cleaning up 1...
Error handled from long jump
Instead I get the following, indicating that the UNDO handler is not executing:
Executing Program
Error handled from long jump
What is really weird, is that when the Normal execution is happening inside a nested procedure call, the UNDO handlers are executed as expected:
PROC Procedure1()
Procedure2;
UNDO
TPWrite "Cleaning up 1...";
ENDPROC
PROC Procedure2()
TPWrite "Executing the program...";
WaitTime 100;
UNDO
TPWrite "Cleaning up 2...";
ENDPROC
Output:
Executing Program
Cleaning up 2...
Cleaning up 1...
Error handled from long jump
This seems like a bug to me. Can somebody explain why in the first case no UNDO handler is executing?
Try to add an error handler in your procedure1 and raise it again from there. The way I usually do my error handlers is with TEST, CASE and DEFAULT. Leave a placeholder in the default or merely a Stop instruction. There have been times when the error I was expecting or looking for wasn’t really what went wrong. If you then look in your data for ERRNO, you can match it with the list of error numbers to find the actual error.
I have just tried this and got the expected output from the Undo handler:
Executing the program...
Cleaning up 1...
My understanding of the Long Jump feature is that it doesn’t require the error to be handled by ‘intermediate’ routines on the call stack and allows the error to propagate to a high level handler without the need to explicitly re-raise. Here’s a quote from Technical reference manual: RAPID kernel:
Error recovery with long jump is typically used to pass execution control from a deeply nested code position, regardless of execution level, as quickly and simple as possible to a higher level.
Regardless, this technique would not work for me as in reality, the error raised from a Trap routine may happen at any point in the programs execution. This would require that every routine in my codebase have an error handler for this specific case which is not scalable.
It’s reasonable to want to clean up junk in any routine before yanking the pointer out via the error Trap. My guess is that it’s less a bug with the UNDO and more an issue caused with the Execution Levels being what they are – Trap Level dominates Normal Level – and I guess the UNDO is merely Normal.
You’ve described the UNDO not firing very well using TPWrites above, but could you give a little more tangible example of what kind of leftover junk you’re hoping to handle with the UNDOs in the real world?
Since a trap routine can only be called by the system (as a response to an interrupt),any propagation of an error from a trap routine is made to the system error handler.
@soup
I can see your reasoning behind the Trap Execution Level overriding Normal, that doesn’t explain why it’s working in the example with nested procedures though. I would expect it to either work, or not work.
A real word example of where I’d like to use this functionality might involve a Procedure that sets some digital outputs to another device, or opens a file to write some data. I always include UNDO handlers in such tasks to reset the state of the digital outputs, or close the file properly. My use-case for throwing an error from a Trap routine, is being able to interrupt the program when a digital input goes high (say an error flag from a PLC), and showing the operator a dialog asking if they want to restart the current process, or continue despite the error. If the user were to choose ‘restart the current process’, I would raise the error, which would propagate to the Normal execution level and be handled by the top-level procedure using the long jump syntax.
@lemster68
Have a look at Section 7.2 under the heading Error recovery through execution level boundaries. It is possible to handle Trap Level errors in the Normal execution level with the use of the Long Jump feature. This is what is occurring in the example from my first post. The problem is that the UNDO handler is not firing, despite the procedure exiting prematurely.