Error Trapping with $ETRAP

Here is a thread regarding $ETRAP

Here is a quick and dirty solution: do . new $etrap set $etrap="write ""(Invalid M Code!.  Error Trapped.)"",! set $etrap="""",$ecode=""""" . do SomeThingRisky

Kevin

Exception handling in Mumps

---			Jawad

Subject: Exception handling in Mumps

Exception handling can be performed in any modern programming language, I was wondering if there is any kind of exception handling in Mumps. Main goal is to avoid code crash. I don't think there are any "try" "Catch" blocks in Mumps. I would like to know any approach that mumps programmers use to avoid code crash.

Jawad

---

Gregory Woodhouse

Yes. It's called error trapping in MUMPS. The basic pattern is to NEW  $ETRAP and $ESTACK and set $ETRAP to the code reference for your handler. Control will be transferred there if an error is raised by setting $ECODE, or if an error occurs during execution. The value of $ECODE will be the error code or codes. You need to be sure to clear the error if you want execution to continue.

You can clear a single error by setting $ECODE to "", but Kernel provides a utility to unwind the error stack and quit back to the calling routine, D UNWIND^ %ZTER. To log an error simply D ^%ZTER.

So, your code may look something like this N $ET,$ES S $ET="HNDLR^MYRTN" ;do risky stuff HNDLR ; ;your own logic here D ^%ZTER ;if you want to log the error D UNWIND^%ZTER Q

Gregory Woodhouse

---			kdtop

Greg, can you educate me a bit more about UNWIND. You say it "unwinds" the error stack and quits back to the calling routine. Does this mean that you don't really need a QUIT on the line after it? Does it go back to the same line that caused the error, or the next line after? Thanks Kevin

---			Woodhouse, Gregory J.

I always put QUIT at the end of my code. Call it a bad habit.

Anyway, I've never found a very clear explanation in the documentation of what UNWIND^%ZTER does. Let's look at the code:

UNWIND  ;Unwind stack for new error trap. Called by app code. S $ECODE="" S $ETRAP="D UNW^%ZTER Q:'$QUIT Q -9" S $ECODE=",U1," UNW     Q:$ESTACK>1  S $ECODE="" Q

This can be paraphrased as follows:

First, clear the current error (S $ECODE="") and set the error handler to "D UNW^%ZTER Q:'$QUIT Q -9" and set the error code to ",U1,".

Okay, so what does THAT mean? Let's take it a piece at a time. The first step in handling any error will be to call UNW^%ZTER, then just QUIT if the code was not invoked as a function, or QUIT with the value -9 (if it was). After resetting the error trap, set the error code to ",U1,". To make sense of that, you need to know that errors are either part of the MUMPS standard and begin with M (e.g., M13), are vendor specific and begin with Z, or user defined and begin with U. The list of errors is always comma delimited, hence ",U1," (Hmm...so I guess this means the SAC needs to prohibit use of U1.) We're almost there. The last piece of the puzzle is to look at UNW^%ZTER. The code here is just Q:$ESTACK>1 S $ECODE="" Q. That simply looks at $ESTACK, and if it's greater than 1 (there is more than one error in the error stack), it QUITs, effectively popping the stack. Finally, it clears the error condition with S $ECODE="" and QUITs.

Okay, so how do you translate that into something more human friendly? At the most basic level, it's just popping off any errors that might be left on the stack (without handling them), and clearing the error condition.

In response to your other question: No, you don't need a QUIT, but it doesn't hurt either. I tend to put a QUIT at the end of each section of code to prevent unintentional "fall through" errors, and when code is meant to fall through, I say so in a comment. When you call UNWIND^%ZTER control returns to the calling routine, not where you left off. If you want to go back to where you left off when the error occured, you need to write your code differently, but I recommend placing any code likely to raise an error in a DO block by itself.

---			kdtop

...

Right, I'm confused already.

> Let's take it a piece at a time. The first step > in handling any error will be to call UNW^%ZTER, then just QUIT if the > code was not invoked as a function, or QUIT with the value -9 (if it > was).

If an error was encountered, and the user trapped it (as in your original example), and then called UNWIND^%ZTER, then I don't see what setting $ETRAP does to handle the current problem. It seems only that is will redirect the error handler for NEXT time. Furthermore, we have set $ECODE="" initially, clearing the error state.

Let's see, after changing $ECODE to UNWIND^%ZTER, it sets another error state and then quits. Will this cause the $ECODE to be executed a SECOND time? This time launching UNW^%ZTER??

> After resetting the error trap, set the error code to ",U1,". To > make sense of that, you need to know that errors are either part of the > MUMPS standard and begin with M (e.g., M13), are vendor specific and > begin with Z, or user defined and begin with U. The list of errors is > always comma delimited, hence ",U1," (Hmm...so I guess this means the  > SAC needs to prohibit use of U1.) We're almost there.

I have seen documentation of the error codes...

> The last piece of > the puzzle is to look at UNW^%ZTER. The code here is just Q:$ESTACK>1 S  > $ECODE="" Q. That simply looks at $ESTACK, and if it's greater than 1 > (there is more than one error in the error stack), it QUITs, effectively > popping the stack. Finally, it clears the error condition with S > $ECODE="" and QUITs.

Well, it doesn't seem to be able to remove more than one level of error code. I don't know if there would ever be a situation where more than that would be needed. But the term "unwind" implies to me "back up as far as needed" instead of just "pop 1 level back"

> Okay, so how do you translate that into something more human friendly? > At the most basic level, it's just popping off any errors that might be > left on the stack (without handling them), and clearing the error > condition.

I had thought that there was some error logging here somehow, but I don't see in this part of the code...

> In response to your other question: No, you don't need a QUIT, but it > doesn't hurt either. I tend to put a QUIT at the end of each section of > code to prevent unintentional "fall through" errors, and when code is  > meant to fall through, I say so in a comment.

>When you call UNWIND^%ZTER control returns to the calling routine, not where you left off.

So in your example below:

1> N $ET,$ES 2> S $ET="HNDLR^MYRTN" 3> ;do risky stuff 4> HNDLR ; 5> ;your own logic here 6> D ^%ZTER ;if you want to log the error 7> D UNWIND^%ZTER 8> Q

"the calling routine" would be where? I would say that UNWIND^%ZTER would quit back to the line after it was called, namely line 8. In which case the Q would be needed. And the Q in line 8 would then jump back somewhere around line 3 ("risky stuff")

> If you want to go back to where you left off when the error occurred, you > need to write your code differently, but I recommend placing any code likely > to raise an error in a DO block by itself.

I will say that I was pleased to learn about $QUIT. I hadn't encountered that before. I looked it up and understand it now. I have had times where I could have used that... :-)

Thanks Kevin

---

...

Right, I'm confused already.

[Greg] My experience is that everyone finds it confusing.

> Let's take it a piece at a time. The first step in handling any error will > be to call UNW^%ZTER, then just QUIT if the code was not invoked as a > function, or QUIT with the value -9 (if it was).

If an error was encountered, and the user trapped it (as in your original example), and then called UNWIND^%ZTER, then I don't see what setting $ETRAP does to handle the current problem. It seems only that is will redirect the error handler for NEXT time. Furthermore, we have set $ECODE="" initially, clearing the error state.

Let's see, after changing $ECODE to UNWIND^%ZTER, it sets another error state and then quits. Will this cause the $ECODE to be executed a SECOND time? This time launching UNW^%ZTER??

[Greg] That's right. The current error is cleared (S $ECODE=""), but errors are stacked, and there may be multiple outstanding errors. That way, if there are further errors, they will be handled by UNW^%ZTER.

>After resetting the error trap, set the error code to ",U1,". To make >sense of that, you need to know that errors are either part of the >MUMPS standard and begin with M (e.g., M13), are vendor specific and >begin with Z, or user defined and begin with U. The list of errors is  >always comma delimited, hence ",U1," (Hmm...so I guess this means the   >SAC needs to prohibit use of U1.) We're almost there.

I have seen documentation of the error codes...

> The last piece of > the puzzle is to look at UNW^%ZTER. The code here is just > Q:$ESTACK>1 S $ECODE="" Q.  > That simply looks at $ESTACK, and if it's greater than 1 > (there is more than one error in the error stack), it QUITs, > effectively popping the stack. Finally, it clears the error condition > with S $ECODE="" and QUITs.

Well, it doesn't seem to be able to remove more than one level of error code. I don't know if there would ever be a situation where more than that would be needed. But the term "unwind" implies to me "back up as far as needed" instead of just "pop 1 level back"

If $ESTACK>1 the error handler will quit, popping an error off the stack, but if there are outstanding unhandled errors, it may be invoked again. The idea is to allow this new error handler to consume the stack.

> Okay, so how do you translate that into something more human friendly? > At the most basic level, it's just popping off any errors that might > be left on the stack (without handling them), and clearing the error > condition.

I had thought that there was some error logging here somehow, but I don't see in this part of the code...

[Greg] This code does not log the error. In order to log an error D ^%ZTER (different entry point). In most cases, you will make the two calls one after another, i.e.,


 * D ^%ZTER ;log the error
 * D UNWIND^%ZTER ;unwind the stack (so you can continue with a clean slate)

> In response to your other question: No, you don't need a QUIT, but it > doesn't hurt either. I tend to put a QUIT at the end of each section > of code to prevent unintentional "fall through" errors, and when code > is meant to fall through, I say so in a comment.When you call UNWIND^%ZTER > control returns to the calling routine, not where you left off.

So in your example below:

1> N $ET,$ES 2> S $ET="HNDLR^MYRTN" 3> ;do risky stuff 4> HNDLR ; 5> ;your own logic here 6> D ^%ZTER ;if you want to log the error 7> D UNWIND^%ZTER 8> Q

"the calling routine" would be where? I would say that UNWIND^%ZTER would quit back to the line after it was called, namely line 8. In which case the Q would be needed. And the Q in line 8 would then jump back somewhere around line 3 ("risky stuff")

[Greg] No, line 7 is part of the error handler. I mean control will be transferred back to the block of code that invoked the code causing the error to be generated.

>If you > want to go back to where you left off when the error occurred, you need >to write your code differently, but I recommend placing any code likely >to raise an error in a DO block by itself.

I will say that I was pleased to learn about $QUIT. I hadn't encountered that before. I looked it up and understand it now. I have had times where I could have used that... :-)

[Greg] Unfortunately, it is not currently allowed by the SAC, but that is subject to change.

Thanks Kevin