CATCH Brain-freeze

Posted by Jeff Ledbetter on 19-Feb-2014 15:35

Hi.

OE 11.3.2.

Why is the following code displaying both my caught error AND then the standard error output?  Shouldn't the CATCH cause the other to be supressed?

DEFINE TEMP-TABLE ttRow
  FIELD RowIdent AS CHARACTER
  INDEX ttRow1 IS UNIQUE RowIdent.

DEFINE VARIABLE i AS INTEGER NO-UNDO.

FOR EACH sports2000.customer NO-LOCK ON ERROR UNDO, THROW:
  CREATE ttRow.
  ASSIGN ttRow.Rowident = "1".
END.

CATCH e AS PROGRESS.Lang.SysError:
  MESSAGE "I got it! " SKIP(1) e:GetMessage(1) VIEW-AS ALERT-BOX.
END CATCH.


All Replies

Posted by Håvard Danielsen on 19-Feb-2014 20:55

The second error is thrown because the bad ttRow record still exists when the procedure (and its temp-table) goes out of scope. You must delete any bad records before they go out of scope to avoid this.  

Posted by Stefan Drissen on 20-Feb-2014 00:08

Why is the UNDO not UNDO'ing the temp-table create? The temp-table is /not/ defined with NO-UNDO so I would have expected it to be UNDOne?

Posted by Torben on 20-Feb-2014 04:40

Because you don't have a transaction.

Change your NO-LOCK to EXCLUSIVE-LOCK and you have a transaction and temp-table is being UN-DONE

/Torben

Posted by Marian Edu on 20-Feb-2014 04:50

hmm

it does have a 'create' there, sure it must be a transaction block only not sure what Stefan expected to be rolled-back... running the code I only end-up with one record in temp-table and that is for me the expected result (for each transaction in on every block iteration, only second record created gets roll-back)

why the error seems to still propagate even if was catch already escapes me, maybe a nasty bug? :)

Posted by Torben on 20-Feb-2014 04:59

Create need to be on a 'real' database object not a temp-table to initiate a transaction!

Posted by James Palmer on 20-Feb-2014 05:03

Interestingly, if I change it as below, I just get the catch error.

DEFINE TEMP-TABLE ttRow 
  FIELD RowIdent AS CHARACTER
  INDEX ttRow1 IS UNIQUE RowIdent.
 
DEFINE VARIABLE i AS INTEGER NO-UNDO.
 
do transaction ON ERROR UNDO, THROW:
  FOR EACH customer NO-LOCK ON ERROR UNDO, THROW:
    CREATE ttRow.
    ASSIGN ttRow.Rowident = "1".
  END.
end. 
 
CATCH e AS PROGRESS.Lang.SysError:
  MESSAGE "I got it! " SKIP(1) e:GetMessage(1) VIEW-AS ALERT-BOX.
END CATCH.

Edit: I should add I'm on 11.2.1

Posted by Stefan Drissen on 20-Feb-2014 05:16

Which was explained by Torben - I did not have a transaction to be undone. If you put the do transaction around the create and assign of the temp-table you also have enough transaction.

The amount of additional undoing and throwing always throws (pun intended) me off structured error handling.

Posted by Torben on 20-Feb-2014 05:31

DEFINE TEMP-TABLE ttRow
  FIELD RowIdent AS CHARACTER
  INDEX ttRow1 IS UNIQUE RowIdent.
 
DEFINE VARIABLE i AS INTEGER NO-UNDO.
 
FOR EACH sports2000.customer NO-LOCK ON ERROR UNDO, THROW:
  DO transaction ON ERROR UNDO, THROW:
    CREATE ttRow.
    ASSIGN ttRow.Rowident = "1".
  END.
END.
 
CATCH e AS PROGRESS.Lang.SysError:
  MESSAGE "I got it! " SKIP(1) e:GetMessage(1) VIEW-AS ALERT-BOX.
END CATCH.

Posted by Håvard Danielsen on 20-Feb-2014 09:00

Note that one can use the TRANSACTION keyword directly on the FOR EACH block.  

Also note that this issue is independent of the structured error handling. If you remove the catch block you will get more instances of the message. If you remove the ON ERROR , THROW entirely the infinite loop protection seems to get confused (you can get out with a couple of breaks).  

Posted by Jeff Ledbetter on 20-Feb-2014 09:27

Thanks everyone. Adding the TRANSACTION to the FOR..EACH scope does indeed resolve. I won't pretend to understand exactly why, but it does. :)

Posted by Laura Stern on 05-Mar-2014 10:11

The behavior of this code would be the same whether you were using structured error handling or not.  The multiple errors come from the fact that the record create and update were not undone - as you all have said.  It is not related to the fact that the error is being thrown or re-thrown.  For example, if you changed ON ERROR UNDO, THROW to ON ERROR UNDO, LEAVE and you removed the CATCH block, you will see the error 3 times - once on the ASSIGN line where the error is first raised, once when you leave the FOR EACH block and once when you leave the PROCEDURE block (which is the transaction block unless you explicitly code otherwise).  

So I'd be interested to hear more details on your statement that the additional undoing and throwing has discouraged you from using structured error handling.

Posted by Stefan Drissen on 05-Mar-2014 10:56

I agree that there is just as much undoing and leaving as there is undoing and throwing. In the first posts there were multiple (unnecessary) undo, throws which tripped me a bit (along with a missing transaction block)

When CATCH was initially introduced I had hoped that the CATCH would be a more ultimate CATCH (for example at the entry point of an AppServer call), but each block needs to be catching and throwing. Maybe this is unrealistic, but it could have made CATCH a very powerful thing.

Currently I'm only CATCHing OUTPUT TO statements which CATCH can deal with whereas NO-ERROR cannot.

Posted by Jeff Ledbetter on 05-Mar-2014 11:01

Hi.

"When CATCH was initially introduced I had hoped that the CATCH would be a more ultimate CATCH (for example at the entry point of an AppServer call), but each block needs to be catching and throwing."

That has not been my experience when using the ROUTINE-LEVEL ON ERROR UNDO, THROW. For one of our products, we have a single appserver entry point that catches all App and System errors.

There are some exceptions where we want to catch a specific error to handle it right away before it is thrown back up the stack but the ROUTINE-LEVEL definition seems to help with all the rest. I'm not sure if that is a best-practice, but it has worked for us.

Posted by Peter Judge on 05-Mar-2014 16:12

> There are some exceptions where we want to catch a specific error to handle it right away before it is thrown back up the stack but the ROUTINE-LEVEL definition seems to help with all the rest. I'm not sure if that is a best-practice, but it has worked for us.

+1 . FWIW I follow the same approach that Jeff does, with the minor exception that in 11.3 I'll start using the new BLOCK-LEVEL statement which is a superset of ROUTINE-LEVEL.
 
There are a couple of places you should have catches:
 
-  Currently you need to add a CATCH on the 'service interface' program on the AppServer (ie the program being called on the AppServer) to catch any errors thrown by the logic called there (since those errors cannot yet be returned/thrown across the AppServer boundary).
 
- If you're using GUI for .NET , you need catch blocks in your ABL event handlers since it's not currently possible to have a global catch during a WAIT-FOR.
 
 
*
Instead of adding the statement to source-code files, you can use the -undothrow 1 startup parameter to change the default error-handling on routine-level blocks to UNDO, THROW during compilation. See the OpenEdge Deployment: Startup Command and Parameter Reference for more information.
*
The BLOCK-LEVEL ON ERROR UNDO, THROW statement can be used if you want to change the default error-handling on REPEAT, FOR, and DO TRANSACTION blocks in addition to routine-level blocks. (You can use the -undothrow 2 startup parameter to change the default error-handling to UNDO, THROW on every block affected by the BLOCK-LEVEL statement during compilation.)
 
-- peter
 
 
[collapse]
From: Jeff Ledbetter [mailto:bounce-jeffledbetter@community.progress.com]
Sent: Wednesday, 05 March, 2014 12:02
To: TU.OE.Development@community.progress.com
Subject: RE: CATCH Brain-freeze
 
Reply by Jeff Ledbetter

Hi.

"When CATCH was initially introduced I had hoped that the CATCH would be a more ultimate CATCH (for example at the entry point of an AppServer call), but each block needs to be catching and throwing."

That has not been my experience when using the ROUTINE-LEVEL ON ERROR UNDO, THROW. For one of our products, we have a single appserver entry point that catches all App and System errors.

There are some exceptions where we want to catch a specific error to handle it right away before it is thrown back up the stack but the ROUTINE-LEVEL definition seems to help with all the rest. I'm not sure if that is a best-practice, but it has worked for us.

Stop receiving emails on this subject.

Flag this post as spam/abuse.

[/collapse]

Posted by Stefan Drissen on 05-Mar-2014 17:35

Thank you!!!

ROUTINE-LEVEL ON ERROR UNDO, THROW seems to be the one line I was missing! (or the session startup parameters for the compiler - will need to check availability on 10.2B / 11.2.1)

But... in my top level CATCH - how do I cleanup any mess left behind? In the temp-table example, how do I know I need to undo a temp-table record?  

What happens if I the tt error occurs in a persistent super procedure on the AppServer?

Posted by Thomas Mercer-Hursh on 05-Mar-2014 17:43

You put a general purpose CATCH in the top level procedure that launches everything.  It you use ROUTINE-LEVEL or BLOCK-LEVEL, that keeps throwing the error up the call chain until caught by that CATCH.

Posted by Stefan Drissen on 06-Mar-2014 01:41

Yes, that part is clear. But what about cleaning up any mess?

The top level procedure has no, and wants no, idea of the work that has been done at lower levels. How does it know what it needs to clean up? In the case of dynamic objects these could be dumped into (named) widget pools or deleted by walking the dynamic object handles.

But what about a persistent super that has errored out with a duplicate (no-undo) static temp-table record? Any subsequent hit to that super will persistently keep erroring since it has nowhere to put the duplicate.

Maybe persistent supers need more care and need to catch and cleanup their own messes?

Posted by Marian Edu on 06-Mar-2014 02:10

Everyone should clean up his own mess, `finally` can (and should) be used even with no catch block :)

I for one don't see much use of that one catch-all block, using the new error handling still mean we have to handle those errors somehow and it's most of the time at a lower level than the main start-up routine.

Posted by Mike Fechner on 06-Mar-2014 02:16

[quote user="medu"]

Everyone should clean up his own mess, `finally` can (and should) be used even with no catch block :)

[/quote]

Fully agree. FINALLY was the best new feature of 10.1C (who needed the dynamic TTY browse widget at all?)

[quote user="medu"]

I for one don't see much use of that one catch-all block, using the new error handling still mean we have to handle those errors somehow and it's most of the time at a lower level than the main start-up routine.

[/quote]

I would say it depends - if an error can be handled by performing an alternative action (e.g. retuning an alternative value from a method, searching a different record, etc.).

However, when all you can do is inform the user that something went wrong, I'm also propagating that there should be a CATCH-all block (if you want to call it that way) at the end of the hand full of entry points to the AppServer and at the end of every (GUI) event handler.

Posted by Stefan Drissen on 06-Mar-2014 02:44

Yes, everyone /should/ clean up their own mess. And yes, all exceptions /should/ be handled. But they are not - so I would /like/ to be able to catch it and clean it.

In the old days of fat client / server a client restart was sufficient to clean up all. An AppServer however is not - just throwing something out there - if an unhandled error is caught on the AppServer then QUIT? But this will probably not get the actual error thrown back to the client?

This thread is closed