CATCH behavior question - REPEAT versus FOR EACH block?

Posted by temays on 27-Jul-2017 08:34

I'm seeing a little weirdness using catch in a repeat block versus a for each (see the code below.) If in an error is encountered in a repeat/get-next table navigation block (the example uses a query but repeat/find next reacts the same way), the catch fires but  immediately exits the repeat block, regardless of any undo, retry options. However when navigating the table via a for each block and forcing the same error, I see the catch statement firing, and then staying within the iterating block.

Is this expected behavior?   I would hope to catch and process the error,  programmatically determine whether or not to continue table navigation, regardless of how i was moving through the data.

/*Uses sports - for sports2000 change cust-num to custnum*/

&SCOPED-DEFINE Test_type REPEAT /* Set to REPEAT or anything else to use for each*/

&IF "{&Test_type}" EQ "REPEAT"
&THEN
define variable hQuery as handle no-undo.
define variable cnt as int no-undo.

create query hQuery.
hQuery:set-buffers(buffer sports.customer:handle).
hQuery:query-prepare("FOR EACH sports.Customer NO-LOCK").
hQuery:query-open().

rep-blk:
repeat on error undo, retry:
hQuery:get-next().
cnt = cnt + 1.
message "Repeat/get-next Cnt = " cnt " cust-num = " cust-num view-as alert-box.

IF cnt = 2 then assign sports.customer.cust-num = 1. /*Force an error(NO-LOCK update)*/

CATCH myAppError As Progress.Lang.AppError:
MESSAGE "in catch block - Lang.AppError " VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
CATCH mySysError AS Progress.Lang.SysError:
MESSAGE "in catch block - Lang.SysError " mySysError:GetMessage(1) VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
CATCH myProError AS Progress.Lang.ProError:
MESSAGE "in catch block - Lang.ProError" mySysError:GetMessage(1) VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
CATCH myError AS Progress.Lang.Error:
MESSAGE "In catch block - Lang.Error " myError:GetMessage(1) VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.

END.
message "Outside of repeat" view-as alert-box.


&ELSE
define variable cnt as int no-undo.

rep-blk:
FOR EACH sports.Customer NO-LOCK:
cnt = cnt + 1.
message "For each - Cnt = " cnt " cust-num = " cust-num view-as alert-box.
IF cnt = 2 then assign sports.customer.cust-num = 1. /*Force an error(NO-LOCK update)*/
CATCH myAppError As Progress.Lang.AppError:
MESSAGE "in catch block - Lang.AppError " VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
CATCH mySysError AS Progress.Lang.SysError:
MESSAGE "in catch block - Lang.SysError " mySysError:GetMessage(1) VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
CATCH myProError AS Progress.Lang.ProError:
MESSAGE "in catch block - Lang.ProError" mySysError:GetMessage(1) VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
CATCH myError AS Progress.Lang.Error:
MESSAGE "In catch block - Lang.Error " myError:GetMessage(1) VIEW-AS ALERT-BOX.
undo, next rep-blk.
END CATCH.
END.
message "Outside of for each " view-as alert-box.
&ENDIF

Posted by temays on 27-Jul-2017 09:03

Thanks for the suggestion, but DO WHILE TRUE exhibits the same behavior.   My original suspicion was with default "infinite loop protection" and that for sure appears to be the case.    Looks like the undo, retry is getting converted to an undo, leave and normally this is not override-able.

However, it appears I found a solution.   The docs state the use of a RETRY function within a block, turns off default infinite loop protection.  That's handy.  So adding IF NOT RETRY THEN DO: END.   within the block allows me to control the iterations following a successful CATCH.   

Posted by Laura Stern on 27-Jul-2017 09:21

This is due to our infinite loop protection.  Since there is no user interaction in the REPEAT block, we change the NEXT to LEAVE- even though you explicitly said to do NEXT. Questionable behavior, I would say, but expected.  So if you add an UPDATE statement inside the REPEAT block (just for experimental purposes), you'll see that it does repeat the block.

In the FOR EACH block, it allows you to go NEXT since you are moving to the next record and that in itself is a change, so we don't need to fear that nothing in the block will change.  We are not smart enough to see that inside the REPEAT block you are doing NEXT on a QUERY, which would have the same effect as iterating through the FOR EACH.

Right now, I cannot think of a way to get around this.  You can't even add PROCESS EVENTS because that (for reasons that I cannot explain) is NOT treated as a user interaction.  You should log a bug.  Maybe we can add a startup parameter that will turn off infinite loop protection.

Posted by temays on 27-Jul-2017 10:03

Thanks for the suggestion but DO WHILE TRUE exhibits the same behavior.  

My original suspicion was the infinite loop protection scheme was converting the undo, retry into an undo, leave and this does appear to be the case. Normally this is not override-able but in the docs found: Using the RETRY function in a block turns off the default error processing, which result in no infinite loop protection for the block.    

So...Adding IF NOT RETRY THEN DO: END.  allowed me to control block after the catch statement.  I would not suggest this method as a general solution, as turning off infinite loop protection, even within a single block of code, should be done with extreme care.  

Thanks,

Terry Mays,

All Replies

Posted by Lieven De Foor on 27-Jul-2017 08:46

Could you try using a DO WHILE TRUE instead of REPEAT?

What are the results there?

REPEAT has a lot of overhead you probably don't need here and DO WHILE TRUE is generally faster (try it for yourself with a simple action inside the loop).

Posted by temays on 27-Jul-2017 09:03

Thanks for the suggestion, but DO WHILE TRUE exhibits the same behavior.   My original suspicion was with default "infinite loop protection" and that for sure appears to be the case.    Looks like the undo, retry is getting converted to an undo, leave and normally this is not override-able.

However, it appears I found a solution.   The docs state the use of a RETRY function within a block, turns off default infinite loop protection.  That's handy.  So adding IF NOT RETRY THEN DO: END.   within the block allows me to control the iterations following a successful CATCH.   

Posted by Laura Stern on 27-Jul-2017 09:21

This is due to our infinite loop protection.  Since there is no user interaction in the REPEAT block, we change the NEXT to LEAVE- even though you explicitly said to do NEXT. Questionable behavior, I would say, but expected.  So if you add an UPDATE statement inside the REPEAT block (just for experimental purposes), you'll see that it does repeat the block.

In the FOR EACH block, it allows you to go NEXT since you are moving to the next record and that in itself is a change, so we don't need to fear that nothing in the block will change.  We are not smart enough to see that inside the REPEAT block you are doing NEXT on a QUERY, which would have the same effect as iterating through the FOR EACH.

Right now, I cannot think of a way to get around this.  You can't even add PROCESS EVENTS because that (for reasons that I cannot explain) is NOT treated as a user interaction.  You should log a bug.  Maybe we can add a startup parameter that will turn off infinite loop protection.

Posted by Laura Stern on 27-Jul-2017 09:26

I just posted the answer and it seems to have disappeared.  At the risk of having it show up twice, I will post it here again:

This is due to our infinite loop protection.  Since there is no user interaction in the REPEAT block, we change the NEXT to LEAVE- even though you explicitly said to do NEXT. Questionable behavior, I would say, but expected.  So if you add an UPDATE statement inside the REPEAT block (just for experimental purposes), you'll see that it does repeat the block.

In the FOR EACH block, it allows you to go NEXT since you are moving to the next record and that in itself is a change, so we don't need to fear that nothing in the block will change.  We are not smart enough to see that inside the REPEAT block you are doing NEXT on a QUERY, which would have the same effect as iterating through the FOR EACH.

Right now, I cannot think of a way to get around this.  You can't even add PROCESS EVENTS because that (for reasons that I cannot explain) is NOT treated as a user interaction.  You should log a bug.  Maybe we can add a startup parameter that will turn off infinite loop protection.

Posted by Lieven De Foor on 27-Jul-2017 09:38

Replacing REPEAT with DO WHILE TRUE could be a workaround? Unless the infinite loop detection detects that this is in fact also an infinite loop...

While these things (i.e. infinite loop protection) probably sounded good for a 4GL, I think such "optimizations" should not be part of the language/runtime, but up to the developer...

Posted by temays on 27-Jul-2017 10:03

Thanks for the suggestion but DO WHILE TRUE exhibits the same behavior.  

My original suspicion was the infinite loop protection scheme was converting the undo, retry into an undo, leave and this does appear to be the case. Normally this is not override-able but in the docs found: Using the RETRY function in a block turns off the default error processing, which result in no infinite loop protection for the block.    

So...Adding IF NOT RETRY THEN DO: END.  allowed me to control block after the catch statement.  I would not suggest this method as a general solution, as turning off infinite loop protection, even within a single block of code, should be done with extreme care.  

Thanks,

Terry Mays,

Posted by temays on 27-Jul-2017 10:10

I also posted twice and it has not shown up, so hopefully this is not a repeat.  For sure infinite loop protection is the reason for this behavior.   I found in the docs that use of the RETRY function within a block turns off infinite loop protection.  So adding a IF NOT RETRY THEN DO: END.  allowed me to control the iterative properties after the catch.  

While this should NOT be a general solution, meaning infinite loop protection  is a GOOD thing, in this case it allowed me to override the default handing and programmatically take over.  

Posted by Laura Stern on 27-Jul-2017 10:14

Ah yes... IF RETRY!  I forgot that. Thank you!

Posted by temays on 27-Jul-2017 10:27

Not sure what's going on today with communities, I replied to you and it didn't show up.  DO WHILE TRUE exhibits the same behavior.    Thanks for the suggestion.  

I was using a repeat block to get default transaction and record scope benefits, which of course could also be done with do while, but I did test it and the behavior was the same.

This thread is closed