ABL unhandled Exceptions, or the need to catch these untrapa

Posted by slacroixak on 12-Jan-2016 07:53

Hi all, I' like to start a discussion about minimum error handling requirements for some situations that remain in unmanageable corner cases for some historical reasons.  Like many of you, we would like to catch and log any kind of ABL System Exception on the fields to better serve our customers.

You all know this famous error:       ** "<file-name>" was not found. (293)

We somehow agree it cannot be handled like any catchable exception because it is likely to be caused by a missing artefact, so a corrupted application (plus changing that would break many Apps).  Our applications sometimes rely on some problematic occurrences of RUN VALUE(), which are a bit hard to wrap with a SEARCH(), but my point is not to change fundamentally this ABL behaviour and I‘d rather find a way to handle it like an unhandled exception.

Error 293 leads to an untrapable STOP condition that restarts your application, with the procedure of your -p startup param.  This leads to many problems when your app uses Static members, because these do remain with the context of your session before it was restarted next to that STOP.  As a consequence, in a restart case, you may want to exit your App quickly.  Happily, it is easy to detect this situation and exit: make your launcher procedure increment a static integer variable and make sure it does equal to 1.  If greater than 1 (should be 2) then log/message something then QUIT.

But... like us, you may want to go one step further: be notified what particular procedure file is missing.  So far, the _msg() construct still holds the last error-numbers in case of restart, and does report 293, but without any message detail so we still do not know which procedure is missing (ERROR-STATUS:GET-MESSAGE(1) does not work indeed).

I am not sure, but perhaps the loss of a database connection also falls in a similar situation.

Q1: are you aware of another trick to catch the name of a missing procedure file?  Please note that relying on client log is not an acceptable option.

 

Q2: KBase 28152 (http://knowledgebase.progress.com/articles/Article/000028152   RUN STATEMENT ERROR IS NOT CAUGHT BY STRUCTURED ERROR HANDLING) refers to Enhancement Request OE00217716 about this topic.  Is it a good ER or should we log a new one covering all the aspects?

 

<Q3 title=”How about this ER?”>

Provide a way to achieve minimum error handling for the so called untrapable stop conditions, like unhandled exception in other technologies (like UnhandledExceptionEventHandler in C#)

A few suggestions, from quick and dirty to smarter:

a)     Associate the _msg() construct to a new _msgDetail() stack that keeps track of the full error message, and not only the message number.

b)     Provide a new special ON UNTRAPABLE-STOP phrase to give the ability to trigger a piece of code that would eventually end up with an implicit QUIT to override the default restart.  Also make sure an error 293 gets added to the ERROR-STATUS:GET-MESSAGE() stack so we can obtain the details about what has caused it (same for other similar types of errors)

c)     Provide a new set of low level ABL exceptions such as Progress.Lang.System.Exception or Progress.Lang.UntrapableStopCondition.Exception that could be caught in a CATCH BLOCK, bypassing the default untrapable STOP.

</Q3>

/Sébastien L.

All Replies

Posted by ske on 12-Jan-2016 08:19

> Provide a new special ON UNTRAPABLE-STOP phrase ...

What is the difference compared to the regular ON STOP phrase?

(The regular one does catch error 293, like the KBase article says.)

Did I misunderstand your suggestion?

Posted by Laura Stern on 12-Jan-2016 09:18

There is an initiative afoot within Progress to simply eliminate raising the STOP condition altogether (governed by a startup parameter).  Therefore, trying to RUN a .p that the AVM cannot find would not raise STOP.  It would just be an error like anything else that can be trapped with NO-ERROR or a CATCH block.  Any comment on this idea would be appreciated.  This is not currently scheduled for any particular version.  

Posted by Laura Stern on 12-Jan-2016 09:20

Just a reminder that the ON STOP phrase works fine for affecting flow of control, but it does not stop an error message from being displayed.  Currently there is no way to do that.  That is why treating it as any other error would be desirable.

Posted by slacroixak on 12-Jan-2016 11:40

I has tried to use a ON STOP, UNDO, RETRY without success:

DO ON ERROR   UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK

   ON END-KEY UNDO MAIN-BLOCK, LEAVE MAIN-BLOCK

   ON STOP UNDO, RETRY:  

   Tools:QuitIfInStopRestart().

   IF RETRY THEN DO:

       MESSAGE "RETRY case" SKIP

        tools:GetAblErrorDetails("withCallStack")

          VIEW-AS ALERT-BOX.

       LEAVE.

   END.

=> a button Choose trigger tries to run a NonExisting.p  

Then I thought the ON STOP Phrase was Not working because error 293 was firing an untrap-able condition.

Still my point is was to detect a STOP has occurred, which I can now do with the incremented static variable, but I am still missing the details of the error message (the name of the missing .p file)

Posted by slacroixak on 12-Jan-2016 11:42

Hi Laura, this is exactly what I was wishing Laura.  That would be a great addition to provide consistency to the ABL.  At then end of the day, it is nothing less than providing a UnhandledException Handler that does exist in other technologies.

Posted by slacroixak on 12-Jan-2016 12:08

Just to insist a bit on that: my top goal is not to avoid such an error message to be displayed, but to be able to catch the detail of that message after the end user clicks on OK, so we can log and gather those errors and be aware of what is happening on the fields.

For now, we are blind in this respect, and the only option we have is to ask our customers to send us screenshots.

Of course the new mechanism that you mention would be much better, and we would then display some part of the message with our own message.

Now I guess that in order to not break compatibility, we would need a new Exception Super Type a bit like the Throwable type in Java which the Exception type inherits from.   I mean, in the Java world, when a Sql Connection is dropped, you cannot catch it with a try catch Exception myException, but I believe I could with a try catch Throwable myThrowable.

So, in order to catch an error 293, I would suggest that the block that does the RUN job would not be able to catch the error with the current top generic Progress.Lang.Error interface, but with a new super type above it called Progress.Lang.Throwable or Progress.Lang.StopError:

DO ON ERROR UNDO, THROW:

    RUN nonExisting.p.

    [...]

    CATCH myError Progress.Lang.Error:

    /* will not catch err 293 /

   END.

    CATCH myStopError Progress.Lang.StopError:

      /* this would catch err 293 */

   END.

END

Posted by Laura Stern on 12-Jan-2016 12:38

What I had suggested (making STOP conditions ERROR conditions instead) is not the same as having an UnhandledException Handler.  In C# for example, the UnhandledException is an event you can subscribe to and it doesn't fire until you have bubbled up to the top of your application - out of any I/O blocking.  This is not what you want.

And what I suggested can be done without breaking compatibility by requiring a startup parameter to enable it.

Posted by ske on 12-Jan-2016 13:10

> I has tried to use a ON STOP, UNDO, RETRY without success:

...

>  a button Choose trigger tries to run a NonExisting.p

Exactly where is the code where the STOP condition is raised?

You mean somewhere you wait for input and a trigger is called during that input, and the trigger tries to run a file that does not exist?

I tried a few variations of procedures like that, and ON STOP actually did catch the STOP condition even when it occurred in a called sub-procedure or a trigger. I'm a bit surprised myself. But maybe your code is even more complicated in some way.

Other than that, if triggers are involved, then this discussion reminds me of another discussion last month, that concluded that at least CATCH in a block containing WAIT-FOR will NOT catch errors raised inside triggers. Not because of the nature of the error raised, but because of the way errors during input (WAIT-FOR) are handled by AVM.

community.progress.com/.../21871

One might wonder if that case would be much helped even by the suggestion to replace STOP with ERROR conditions? It seems to me they would still not be trapped by anything outside the WAIT-FOR, not even any UnhandledException, if they are already caught by AVM with it's own handling of it. Maybe Laura can explain.

Posted by Laura Stern on 12-Jan-2016 13:40

On your first point - yes, if you try to run a file that does not exist and you have a DO ON STOP anywhere above where that is called, it should "catch" it.  STOP conditions bubble up until they are handled with DO ON STOP or you reach the top of the application.

Re the WAIT-FOR issue, changing STOP to ERROR does not change the fact that you would still need to put CATCH blocks at least at the top of all UI triggers or event handlers.  It will still not be particularly useful in the block containing the WAIT-FOR statement.  Though there are times when .NET will throw an exception that will kick you out of Application:Run (i.e., the WAIT-FOR) and it would be nice to be able to CATCH it and show the error in a more friendly way.  However, you would still not be able to RETRY that block.  When .NET kicks you out of Application:Run like that, you cannot call it again.  That has nothing to do with the AVM.  It is a .NET "feature".  

Posted by Simon L. Prinsloo on 12-Jan-2016 22:47

Laura

Eliminating the STOP altogether would be first prize.

If this can also be controlled with a READ/WRITE property on the SESSION system handle, one can build it into the bootstrap to eliminate deployment errors.

I would also prefer that such new behaviour be the default and the startup parameter re-instate old behaviour, like we had with -v6q.

Posted by slacroixak on 13-Jan-2016 08:19

OOOps, my wrong... the ON STOP UNDO, RETRY  approach actually works fine, even for the classic MAIN-BLOCK.  I was doing something wrong before my IF RETRY THEN DO:       block.

So at the end of the day

 1) I have a way to detect a STOP condition  : I will use the RETRY option only for the ON STOP Phrase, so the RETRY function says TRUE only in case of a STOP.

 2) I notice the ON STOP UNDO, RETRY can even catch the STOP condition for a classic MAIN-BLOCK with an active WAIT-FOR (although I may use it at a parent level without any WAIT-FOR)

 3) The last missing bit is to catch the error message details with the name of the offending missing procedure.  For now, I would work it around with a SessionManager Class a few static members:

   DEFINE PUBLIC STATIC PROPERTY IsPendingRunValue       AS LOGICAL   NO-UNDO GET.

       PRIVATE SET.

   DEFINE PUBLIC STATIC PROPERTY PendingRunValueFileName AS CHARACTER NO-UNDO GET.

       PRIVATE SET.

   METHOD PUBLIC STATIC VOID SetPendingRunValue(pcFileName AS CHAR):

       SessionManager:IsPendingRunValue = TRUE.

       SessionManager:PendingRunValueFileName = pcFileName.

   END.

   METHOD PUBLIC STATIC VOID ResetPendingRunValue():

       SessionManager:IsPendingRunValue = FALSE.

   END.

Then use it this way:

/* launcher.p */

DO ON ERROR UNDO, LEAVE

   ON STOP UNDO, RETRY:

   IF RETRY AND SessionManager:IsPendingRunValue = TRUE THEN

   DO:

       MESSAGE "RETRY case probably coming next to a STOP due to Error 293 for procedure " SessionManager:PendingRunValueFileName SKIP

           VIEW-AS ALERT-BOX.

       QUIT.

   END.

   RUN mainwindow.w.

END.

/* mainWindow.w:*/

[...]

ON CHOOSE OF btn293 IN FRAME fMain /* Cause error 293   RUN nonExisting.p */

   DO:

       DEFINE VARIABLE cFileName AS CHARACTER NO-UNDO.

       cFileName = "nonExisting.p".

       SessionManager:SetPendingRunValue(cFileName).

       RUN VALUE (cFileName). /* on purpose */

       SessionManager:ResetPendingRunValue().

   END.

The last point I do not like is that if a STOP occurs in the flow after the RUN VALUE statement, but before the next line (so while the called .p remains in the stack), then my IsPendingRunValue property is still set, that could make me wrongly assume an error 293 occurred.    The solution is to call SessionManager:ResetPendingRunValue() at first line in every .p called by a RUN VALUE...

So indeed, Laura, a new mechanism to just raise an ERROR instead of a STOP condition would be much nicer.

Still I would appreciate if you could reveal me another secret way to retrieve the last displayed error message (something with more details that  _msg()   ).

Best regards

/SL

Posted by slacroixak on 13-Jan-2016 08:21

The following is slightly better then:

 IF RETRY AND SessionManager:IsPendingRunValue = TRUE AND _msg(1) = 293 THEN

   DO:

       MESSAGE "RETRY case probably coming next to a STOP due to Error 293 for procedure " SessionManager:PendingRunValueFileName SKIP

        ERROR-STATUS:GET-MESSAGE(1)

           VIEW-AS ALERT-BOX.

       QUIT.

   END.

Posted by Laura Stern on 13-Jan-2016 09:42

Yes, certainly, ON STOP UNDO, RETRY works around a classic WAIT-FOR.  I guess I missed that you said it did not.  It will generally not work however for a WAIT-FOR Application:Run() (.NETt-style) as .NET itself governs some of this behavior.  The main form will be Disposed and gone and would have to be recreated.  Plus .NET will often not let you call Run() again.

The way I would do the ON STOP logic is this - just using a local variable for this example, but you could replace that with a static class member.

DEFINE VAR haveStop AS LOGICAL.

DO ON STOP UNDO, LEAVE:    

   haveStop = yes.

   RUN bar.p.

   haveStop = NO. /* won't execute if STOP actually  happens */

END.

IF haveStop THEN ...  

But what I'm not understanding now is that the way you've coded this, you will first see the system error message "xxx.p was not found (293)".  So why would you need to trap the name and display this message yourself.  It is redundant.  So this only makes sense if you can suppress the system error message.  Though I don't see that the message you are showing is any nicer than just seeing error 293, so I don't get the point.

But to complete the story, the only way not to see that system error is to have something like OUTPUT TO <file> in effect when the RUN happens. If it fails the error message will go to the file not to the screen.  But then you probably do NOT want this in effect during execution of the .p if it does exist and does run.  So you could do OUTPUT TO <file> before each run and OUTPUT CLOSE at the top of each .p.  

Posted by slacroixak on 13-Jan-2016 10:12

Many thanks for your prompt replies indeed Laura

Sorry if my last post was a bit too long (it woudl not be here if the topic was mickey mouse).  I am am using the same trick as you suggest, which will wrongly assume a 293 stop occurred if a stop occurs for another reason (like Ctrl-Break pressed) in bar.p (if that bar.p does exists).

So as I said, I would have to turn this haveStop to a NEW GLOBAL SHARED VAR (of my preferred class static member way) and write NO in it in the very first line of every single .p file launched with a RUN VALUE (such as foobar)... but wait... what if some .p's are sometimes launched with a RUN VALUE() and sometimes launched with a simple RUN bar.p?    oh Gee...

  => another good reason to stress the need of stopping those stops for err293, and make it simply catchable

At last, about your last paragraph that is the very key problem (again sorry if I was not clear enough in that thread): the all problem is not *me* as an end-user, but our numerous regular *Joe* end-users.  When an error message comes with a red star comes, then he does not do not even read it, throws an expletive and immediately clicks OK, so the offending missing file name is gone to the paradise of bytes.   This is why I would be so keen in finding a magic way to retrieve that last untrap-able displayed error message (if only it could be pushed to the ERROR-STATUS system handle, and not only _msg())

Needless to say that redirecting the standard output to a file is not an option because some of these .p's handle some UI or some other OUTPUT TO, plus closing the default stream in case of stop might be a problem.

At last, we have multiple hundreds occurrences of RUN VALUE in that app, with variable signatures  (not easy to wrap...).  That's why I like to catch those with a ON STOP UNDO, RETRY construct in my top main block.

So I suppose there is no other magic construct like _msg() to trap the full last error messages.

Posted by Laura Stern on 13-Jan-2016 10:25

First the simple answer: No - there is no way to trap the error message of a STOP condition.

OK... I get everything you're saying except:

1. Why the distinction between RUN and RUN VALUE("xxx").  What's the difference in terms of this issue?  If you have a string expression, you use that to set the file name.  If it's hard coded, you hard code it again to set the name.

2. I question the psychology of Joe the end-user.  I suspect that if he utters an expletive and hits OK on a message with a red star, he will do the same for your even more cryptic error message (sorry - it may not seem cryptic to us but it will be for Joe!).

Posted by ske on 14-Jan-2016 01:11

> At last, we have multiple hundreds occurrences of RUN VALUE in that app, with variable signatures

> (not easy to wrap...).

You could create a single include file to wrap them regardless of signature.

In that case you could also just as well put the ON STOP right in there in the include file around each RUN and handle any errors there, in stead of detecting whether a restart has occurred in your start procedure.

Posted by Marian Edu on 14-Jan-2016 01:37

[quote user="Laura Stern"]

First the simple answer: No - there is no way to trap the error message of a STOP condition.

[/quote]

Laura, then that's the first thing that can be fixed... thing everyone agree that we should be able somehow to catch the message (might be file not found, invalid parameters, not compiling - yeah, include parameters anyone??), since we can catch the stop would be nice to know why the stop occurred in the first place isn't it? :)

Then, it was a discussion not so long ago about having a 'catch-all' or 'catch the not caught' errors/exceptions... not that much can be done if that happens but at least get the chance to log it and maybe the stacktrace will give one some useful information to help fixing the code.

Posted by Mike Fechner on 14-Jan-2016 01:41

[quote user="Marian Edu"]

Laura, then that's the first thing that can be fixed... thing everyone agree that we should be able somehow to catch the message (might be file not found, invalid parameters, not compiling - yeah, include parameters anyone??), since we can catch the stop would be nice to know why the stop occurred in the first place isn't it? :)

[/quote]

That would be wonderful :-)

Along with the ability to detect STOP-AFTER

Posted by jbijker on 14-Jan-2016 01:53

There is a undocumented function where you can get the last error message number _Msg(1)

We use it like this:

DO ON STOP UNDO, RETURN ERROR getErrorText(_Msg(1)):

 ... statements ...

END.

You should have a function to resolve the message number back to message text. You can do so by reading files from prohelp/msgdata/msg and find the correct entry in there. See knowledgebase.progress.com/.../19758.

Posted by Laura Stern on 14-Jan-2016 09:01

Re "there is no way to trap the error message of a STOP condition ... then that's the first thing that can be fixed"

The rationale behind changing existing STOP conditions to ERROR is two-fold.  One is that we then do not have to invent a new mechanism to trap STOP error messages.  We already have 2 mechanisms, NO-ERROR and CATCH.  We don't need yet another one.  Secondly, most conditions that raise STOP are not really different from any other error condition.  "File not found" is a good example.  Historically (wayyy back) this was deemed to be more serious than other errors since it implied that there was something seriously wrong with the app installation. Thus the STOP condition.  But with the advent of tooling and frameworks, etc. the distinction is now just a meaningless historical artifact.  Even if we do this, there will probably still be STOP conditions - e.g., from STOP AFTER and from CTRL-C.  But neither of these has an error message associated with it.

Posted by Mike Fechner on 14-Jan-2016 09:06

Good point. Keep it simple. CATCH is enough.

I didn't think about CTRL-C as it's unlikely to be pressed on the AppServer ... but together with being able to detect STOP-AFTER that would be a good thing.

Posted by cverbiest on 14-Jan-2016 09:52

I agree with Mike, catch is enough, just remove distinction between errors that were deemed unsolvable and "regular" errors

Maybe a bit off-topic but I think it's related. shouldn't we be able to handle errors in catch blocks and retry ?

The following code does not compile but demonstrates my intent. It could also be (re-)connect a database & retry ,

retryblock: do on error undo, retry:
    output to /tmp/dirdoesnotexist/test.txt.

    catch e as progress.lang.error:
        message "caught" e:getmessage(1) update doretry as logical.
        if doretry
        then do:
            os-create-dir value("/tmp/dirdoesnotexist").
            undo retryblock, retry .
        end.
    end.
end.
Posted by Laura Stern on 14-Jan-2016 10:21

It is the UNDO that causes an error, not the RETRY.  By the time the catch block runs the associated block has already been undone.  So undoing it again does not make sense.

Posted by slacroixak on 15-Jan-2016 02:30

Laura, to answer your two points:

> 1. Why the distinction between RUN and RUN VALUE("xxx").  What's the difference in terms of this issue?  If you have a string expression, you use that to set the file name.  If it's hard coded, you hard code it again to set the name. - See more at: https://community.progress.com/community_groups/openedge_general/f/26/p/78784/editpost?ContentTypeId=1#sthash.dbjs8kev.dpuf

Most of the times, RUN VALUE is a construct for optional custom hooks developed by our professional services peers, which are more likely to hit error 293 than our core product.

Error 293 cases for simple RUN some.p (no VALUE option) can be anticipated by analyzing compile XREF stuff in our Continuous Integration builds, as opposed to the pure runtime RUN VALUE() cases.  Hence my wish to properly log these missing procedure when they are hit by our end users.

> 2. I question the psychology of Joe the end-user.  I suspect that if he utters an expletive and hits OK on a message with a red star, he will do the same for your even more cryptic error message (sorry - it may not seem cryptic to us but it will be for Joe!).

The all point of my request is to not have to rely on Joe to report us such a problem.  If the ABL lets me trap the details of the error (the name of that missing file) then I can manage to log it somewhere then manage to forward it to my dev department so it ends up in our hands  The message I will issue to Joe will just say "We have detected an unexpected problem and have forwarded the details to support.  <a few skips> Technical details:  <bladibla>"      (the point of th tech details is for non Joe users like me).

That's the all issue Laura : for now, the core ABL strongly depends on the end user to report details of special unhandled exceptions.  We will probably always need to rely a minimum on our end users to investigate some problems, but I believe we all want to educate our systems so we would need less interactions with Joe about details that bother him.

Posted by slacroixak on 15-Jan-2016 02:40

> There is a undocumented function where you can get the last error message number _Msg(1)

Hi jbijker,   I've mentioned this _msg() in many posts saying it is too limited.  Somehow, the all point of this thread is to obtain details beyond an error number.

Posted by slacroixak on 15-Jan-2016 02:47

> You could create a single include file to wrap them regardless of signature.

> In that case you could also just as well put the ON STOP right in there in the include file around each RUN and handle any errors there, in stead of detecting whether a restart has occurred in your start procedure.

ske, as I tried to say in previous posts, it is too hard (if ever possible) to figure out if a STOP occurred because the RUN VALUE itself or because of something wrong in the procedure.p that we have managed to run with RUN VALUE... a never ending Russian puppets game...

As we now say on this forum, salvation will come with a simple CATCH block when the ABL lets us handle this kind of error like others.

Posted by ske on 15-Jan-2016 05:18

> ske, as I tried to say in previous posts, it is too hard (if ever possible) to figure out if a STOP occurred because the

> RUN VALUE itself or because of something wrong in the procedure.p that we have managed to run with RUN VALUE...

What do you mean? You can still use _MSG() to check if it was error code 293 or something else that occurred.

> salvation will come with a simple CATCH block when the ABL lets us handle this kind of error like others.

You don't want to do anything while waiting for salvation? All your previous messages with various attempts to instrument the code containing the RUN made me think you would be interested in discussing what can be done today too. All I'm saying is you could put the code you showed (or a slightly improved version) into an include file to make it easier to use, if you are going to use some such solution in the meantime.

Posted by slacroixak on 15-Jan-2016 06:56

> What do you mean? You can still use _MSG() to check if it was error code 293 or something else that occurred.

Well, as said in earlier posts, we now rely on _msg() to record and report there has been an error 293 case, without details ( ** "<file-name>" was not found. (293)).

If I was given a big budget to improve things, I would try to implement a wrapper, but with 1346 occurrences of RUN VALUE (yes it should have been done differently..) the remedy might be worse than the disease (I don't like the impact of includes in the debugger...) so we'd better remain in the purgatory until Laura's Salvation ;)

Posted by Laura Stern on 15-Jan-2016 07:53

Got it!  Thanks.

This thread is closed