Gotchas with ROUTINE-LEVEL in ChUI procedures?

Posted by Dustin Grau on 23-Jan-2015 15:15

As the OE documentation states, use of the ROUTINE-LEVEL statement can change the default of "ON ERROR UNDO, RETRY" to "ON ERROR UNDO, THROW". I'm working with some older ChUI client code that I'd like to run from WebSpeed routines as well. My preference is to apply the ROUTINE-LEVEL statement to throw any errors, and right now I can't see any immediate reasons why this would cause problems with the character environment as a result. My question is, would there be any gotchas or reasons to not do this?

Posted by Laura Stern on 12-Feb-2015 15:11

Not that's not what I said.  The whole point of using ROUTINE-LEVEL ON ERROR UNDO, THROW is just what it says - to throw an error to the caller rather than handling it locally.  So what you just described is expected.  ERROR-STATUS:ERROR in the caller will be different depending on which routine you called (the one with or without the ROUTINE-LEVEL statement).

I was talking about that the ROUTINE-LEVEL (or BLOCK-LEVEL) statements should have no affect on statements that use NO-ERROR or blocks that have explicit error directives.  These things are explicit and we will do what the code says.  The ROUTINE-LEVEL (or BLOCK-LEVEL) statements only change the default behavior - i.e,. what happens without NO-ERROR or with no explicit ON ERROR clause.

When I said there is a bug I was referring to the first example in this thread with this statement:

  cc = string( 999, ">9" ) no-error.

But I was just playing with this.  Oh!  Now I know what's going on.  THERE IS NO BUG.  Here's what's happening:

Some of the older constructs in the language that have error conditions put up an error message but actually treat the condition as a warning, not an error.  This is mostly true of handle-based methods.  I didn't realize that the STRING built-in function is also this way.  So yes, if you execute that assignment statement above and you look at the ERROR-STATUS handle, ERROR will be No, but NUM-MESSAGES will be 1.  This is what you get for warnings.

However, we decided that this behavior is not really correct. And yet we could not change this behavior without breaking people's code.  But when we introduced structured error handling, we decided that people were now changing their code and having to re-factor all of their error handling code.  So we took advantage of this.  Now if the block is governed by structured error handling constructs, we will do the right thing - which is to treat a conditinon causing an error message as an error, not a warning!!  

So when using the ROUTINE-LEVEL statement you are using structured error handling and the STRING function will raise an error.  If you are NOT using ROUTINE-LEVEL, the STRING function as shown will generate a warning.  Even without ROUTINE-LEVEL, adding a CATCH block would have the same effect.

All Replies

Posted by Jean Richert on 27-Jan-2015 10:47

Is there someone following this forum with working experience similar to what Dustin is trying and would be willing to share? Thanks guys!

Posted by Stefan Drissen on 27-Jan-2015 15:13

One thing that we ran into today is that ROUTINE-LEVEL changes (possibly in a good way) if a NO-ERROR results in ERROR-STATUS:ERROR being set.

routine-level on error undo, throw.

def var cc as char.

cc = string( 999, ">9" ) no-error.

if error-status:error then 
   message 'oops' cc view-as alert-box.


Remove the routine-level statement and error-status:error is /not/ set, but error-status:num-messages is still 1.

Posted by Dustin Grau on 30-Jan-2015 11:03

Thanks! That's pretty much what I was finding as well. I guess I wasn't sure if there were any other situations to be on the lookout for.

Posted by cverbiest on 11-Feb-2015 07:39

The error-status:error change seems to be an undocumented side-effect

Adding routine-level changes the behaviour of your procedures and functions. If the code was written depending on the original behaviour (on error undo, return) instead of the new one (on error undo, return error) this change might not be what you want.

I prefer to have block-level on error undo, throw everywhere but it's a change that should not be made without looking at the current error handling within the code.

Example, following sample doesn't retry but it will if you comment out the routine-level statement.

for each customer on error undo, retry :
    if retry then disp "retry".
    disp custnum.
    run failproc.p.
end.

/* failproc contains */

/*routine-level on error undo, throw.*/

integer("abc").

Posted by Laura Stern on 11-Feb-2015 10:13

This is a bug! The ROUTINE-LEVEL ... statement should have no effect whatsoever on statements with NO-ERROR on it.  Please log a bug.  The ROUTINE-LEVEL or BLOCK-LEVEL directive should only affect things that are using "default" error handling - either statements without NO-ERROR or blocks with no explicit error directive.

Posted by Laura Stern on 11-Feb-2015 10:20

Again - as per my reply to above post, the error-status:error change is a bug, not an undocumented side-effect.

What you are saying about this code is correct.  Just to be explicit, without the routine-level statement, the error occurs on "integer("abc") and is "handled" inside failproc.p. The default error handling occurs, which is to display an error and kick you out of the procedure.  But then the error is over and the caller does not see any error.  Therefore, the retry won't happen.  However with routine-level..., the error on integer("abc") gets thrown up to the caller and so the for each block will handle it by doing a retry.

So yes, of course using these directives can change things drastically.

Posted by Laura Stern on 11-Feb-2015 10:21

I'm not sure why you are singleing out the "character environment".  Error handling works the same in all environments.

Posted by Dustin Grau on 12-Feb-2015 09:29

Sorry I couldn't reply sooner. Yes, I understand that error handling _should_ be the same whether it's character, GUI, or speedscript, so don't worry about the singling-out of the character bit. I too set up a test case that shows a curious effect with the error-status:error lingering beyond the scope of the original program. I have 3 programs for testing: a primary caller, and 2 error-producing tests (1 with routine-level and 1 without). When not using routine-level the error-status:error does stay isolated to just the called program. But when calling the program that contains routine-level, the calling program will not only catch any errors from the called program but will still see error-status:error as set. Are you saying this is not the expected behavior?

Here is my code. Sorry, I don't yet know how to format it as a code block by default.

Caller:

{src/web/method/wrap-cgi.i}

{&OUT} "Running 'no-routine' test...<br/>".

RUN tools/err_test-no_routine.p.

IF ERROR-STATUS:ERROR THEN

    {&OUT} "An error status still exists after 'no-routine' call: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

{&OUT} "<br/>".

{&OUT} "Running 'with-routine' test...<br/>".

RUN tools/err_test-with_routine.p.

IF ERROR-STATUS:ERROR THEN

    {&OUT} "An error status still exists after 'with-routine' call: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

CATCH err AS Progress.Lang.Error:

    {&OUT} "<hr/>".

    IF ERROR-STATUS:ERROR THEN

        {&OUT} "An error status still exists within catch block: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

    {&OUT} SUBSTITUTE("Caught Error: [&1] &2", err:GetMessageNum(1), err:GetMessage(1)).

END CATCH.

Program without routine-level:

/* No ROUTINE-LEVEL */

{src/web/method/wrap-cgi.i}

PROCEDURE causeError:

    DEFINE VARIABLE myInt AS INTEGER NO-UNDO.

    DO:

        ASSIGN myInt = INTEGER("a") NO-ERROR.

        IF ERROR-STATUS:ERROR THEN

            {&OUT} "An error status exists after 'a' assignment: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

        {&OUT} "Assigning 'b' value, check log for details of failure...<br/>".

        ASSIGN myInt = INTEGER("b").

        IF ERROR-STATUS:ERROR THEN

            {&OUT} "An error status exists after 'b' assignment: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

    END.

END PROCEDURE.

/* Main Block */

RUN causeError.

Program with routine-level:

ROUTINE-LEVEL ON ERROR UNDO, THROW.

{src/web/method/wrap-cgi.i}

PROCEDURE causeError:

    DEFINE VARIABLE myInt AS INTEGER NO-UNDO.

    DO:

        ASSIGN myInt = INTEGER("c") NO-ERROR.

        IF ERROR-STATUS:ERROR THEN

            {&OUT} "An error status exists after 'c' assignment: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

        {&OUT} "Assigning 'b' value, check log for details of failure...<br/>".

        ASSIGN myInt = INTEGER("d").

        IF ERROR-STATUS:ERROR THEN

            {&OUT} "An error status exists after 'd' assignment: " + ERROR-STATUS:GET-MESSAGE(1) + "<br/>".

    END.

END PROCEDURE.

/* Main Block */

RUN causeError.

Posted by Laura Stern on 12-Feb-2015 15:11

Not that's not what I said.  The whole point of using ROUTINE-LEVEL ON ERROR UNDO, THROW is just what it says - to throw an error to the caller rather than handling it locally.  So what you just described is expected.  ERROR-STATUS:ERROR in the caller will be different depending on which routine you called (the one with or without the ROUTINE-LEVEL statement).

I was talking about that the ROUTINE-LEVEL (or BLOCK-LEVEL) statements should have no affect on statements that use NO-ERROR or blocks that have explicit error directives.  These things are explicit and we will do what the code says.  The ROUTINE-LEVEL (or BLOCK-LEVEL) statements only change the default behavior - i.e,. what happens without NO-ERROR or with no explicit ON ERROR clause.

When I said there is a bug I was referring to the first example in this thread with this statement:

  cc = string( 999, ">9" ) no-error.

But I was just playing with this.  Oh!  Now I know what's going on.  THERE IS NO BUG.  Here's what's happening:

Some of the older constructs in the language that have error conditions put up an error message but actually treat the condition as a warning, not an error.  This is mostly true of handle-based methods.  I didn't realize that the STRING built-in function is also this way.  So yes, if you execute that assignment statement above and you look at the ERROR-STATUS handle, ERROR will be No, but NUM-MESSAGES will be 1.  This is what you get for warnings.

However, we decided that this behavior is not really correct. And yet we could not change this behavior without breaking people's code.  But when we introduced structured error handling, we decided that people were now changing their code and having to re-factor all of their error handling code.  So we took advantage of this.  Now if the block is governed by structured error handling constructs, we will do the right thing - which is to treat a conditinon causing an error message as an error, not a warning!!  

So when using the ROUTINE-LEVEL statement you are using structured error handling and the STRING function will raise an error.  If you are NOT using ROUTINE-LEVEL, the STRING function as shown will generate a warning.  Even without ROUTINE-LEVEL, adding a CATCH block would have the same effect.

Posted by Dustin Grau on 12-Feb-2015 15:18

Sorry for the confusion. I think I've just gotten wrapped around the axle on this. Thank you for the clarification though on the behavior!

Posted by Laura Stern on 12-Feb-2015 15:25

No problem.  You're welcome.

Posted by Stefan Drissen on 13-Feb-2015 04:11

Thank you very much for clearing up the promotion of the string warning to an error! It does make sense.

This thread is closed