Undo Handler Not Executing When Traversing Execution Levels with Long Jump

DBrowne
DBrowne Auckland, New Zealand
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<br>&nbsp;&nbsp;&nbsp; VAR errnum MY_ERROR := -1;<br>&nbsp;&nbsp;&nbsp; VAR intnum timer_interrupt;<br><br>&nbsp;&nbsp;&nbsp; PROC main()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IEnable;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IDelete timer_interrupt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CONNECT timer_interrupt WITH TimerTrap;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ITimer 3.5, timer_interrupt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Procedure1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; ERROR (MY_ERROR)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TPWrite "Error handled from long jump";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RETURN;<br>&nbsp;&nbsp;&nbsp; ENDPROC<br>&nbsp;&nbsp;&nbsp; <br><br>&nbsp;&nbsp;&nbsp; PROC Procedure1()<br>        TPWrite "Executing the program...";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitTime 100;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; UNDO<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TPWrite "Cleaning up 1...";<br>&nbsp;&nbsp;&nbsp; ENDPROC&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; <br><br>&nbsp;&nbsp;&nbsp; TRAP TimerTrap<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BookErrNo MY_ERROR;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RAISE MY_ERROR;<br>&nbsp;&nbsp;&nbsp; ERROR<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RAISE;<br>&nbsp;&nbsp;&nbsp; ENDTRAP<br><br>ENDMODULE

My expected output from this would be:
Executing Program<br>Cleaning up 1...<br>Error handled from long jump

Instead I get the following, indicating that the UNDO handler is not executing:
Executing Program<br>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()<br>&nbsp;&nbsp;&nbsp; Procedure2;<br>UNDO<br>&nbsp;&nbsp;&nbsp; TPWrite "Cleaning up 1...";<br>ENDPROC<br><br><br>PROC Procedure2()<br>&nbsp;&nbsp;&nbsp; TPWrite "Executing the program...";<br>&nbsp;&nbsp;&nbsp; WaitTime 100;<br>UNDO<br>&nbsp;&nbsp;&nbsp; TPWrite "Cleaning up 2...";<br>ENDPROC
Output:

<code>Cleaning up 1...<br>Error handled from long jumpExecuting Program<br>Cleaning up 2...

This seems like a bug to me. Can somebody explain why in the first case no UNDO handler is executing?

Thanks,
David

Comments

  • soup
    soup USA ✭✭✭
    What happens if you stop it before the 3.5 second timer and manually PP to Main?
  • lemster68
    lemster68 United States ✭✭✭
    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.
    Lee Justice
  • DBrowne
    DBrowne Auckland, New Zealand
    edited March 2018
    soup said:
    What happens if you stop it before the 3.5 second timer and manually PP to Main?
    I have just tried this and got the expected output from the Undo handler:
    Executing the program...<br>Cleaning up 1...

    lemster68 said:
    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.

    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.


  • soup
    soup USA ✭✭✭
    edited March 2018
    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?
  • lemster68
    lemster68 United States ✭✭✭
    Interesting little tidbit from the Kernel manual:

    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. 
    Lee Justice
  • DBrowne
    DBrowne Auckland, New Zealand
    @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.



  • DBrowne
    DBrowne Auckland, New Zealand
    So no explanation then? Is anybody from ABB able to weigh in on what's going on here?
  • Ash
    Ash Uk
    Hi DBrown here is my example of how a long jump error handle works. Ive tested it and it works as written: 

    !EXAMPLE OF ERROR HANDLING WITH LONG JUMP

    MODULE LONGJUMPEXAMPLE
    !
    VAR num How:=0;
    !
    PROC mainMOD()
    !
    routine1;
    !
    ! Error recovery point 
    !
    ERROR (LONG_JMP_ALL_ERR)
    !
    IF ERRNO = 56 THEN
    RETRY;
    ELSEIF ERRNO = 45 THEN
    TRYNEXT;
    ELSEIF ERRNO = 37 THEN
      TRYNEXT;
    ENDIF
    ENDPROC

    PROC routine1() 
    !
    how:=how+1;
    routine2;
    !
    UNDO 
      how:=how-1;
    ENDPROC

    PROC routine2()
    !
    RAISE 56;
    !
    RAISE 45;
    !
    RAISE 37;
    !
    ERROR 
    !
    ! This will propagate the error 56 to main
    !
    RAISE;
    !
    ENDPROC
    ENDMODULE