PASOE still makes mysterious distinction between RETURN ERRO

Posted by dbeavon on 14-Jan-2019 17:47

I've noticed that PASOE is more happy with the statement, RETURN ERROR CH-Error than it is when THROW'ing the equivalent AppError.

When THROW'ng an AppError you receive this message in the PASOE agent log.


[19/01/14@12:27:39.714-0500] P-007928 T-008080 1 AS-4 -- Cannot throw or return error/stop object to remote client because object is not serializable or client does not support it. (14438)

... But  in all other respects, we seem to get the same behavior when using the RETURN ERROR statement.

I recall that the same was true with "classic" appserver.   There is additional information in the KB that confirms a difference (in classic appserver) between when an AppError is raised via "THROW" or "RETURN ERROR". See:

https://knowledgebase.progress.com/articles/Article/000045832

Does anyone know how/why PASOE makes a distinction between those two variations?  Based on my experience with structured error handling (SEH) in ABL, the two types of statements are accomplishing the same thing.  Also, given that PASOE is able to make a distinction, would there be any way for an ABL developer to make a distinction between an AppError that was raised by "RETURN ERROR" vs "UNDO, THROW".  In the code below where the comment is, should I be able to find out how the error was raised, just like PASOE is somehow able to ???

DO TRANSACTION:
      
   RUN SomeFailingProgram.p.
   
   CATCH v_AppError 
      AS Progress.Lang.AppError:
         
         /* --->  WAS THIS A RETURN ERROR OR AN UNDO, THROW??? */
         
   END CATCH.
   
END.

Any help would be appreciated.  It is not clear to me how PASOE treats errors that are being relayed back to the client application (a .Net openclient).

Thanks in advance, David

Posted by Laura Stern on 15-Jan-2019 15:45

Not mysterious.  These are different statements doing 2 different things.  RETURN ERROR <string> raises an error and sets RETURN-VALUE.  Whereas when an AppError is thrown it raises error and there is an error object available to be caught.  The error object can have a ReturnValue set in it, or not, an error message set, or not, and possibly can have other properties if it is a custom AppError.

Having said, that, when RETURN ERROR happens, internally, the AVM usually makes an AppError automatically, setting the ReturnValue property with the string so that if there is a CATCH statement above, this can be caught like any other thrown error.  And no, you can't tell if this came from the application itself or was manufactured by the AVM.  Don't see a reason for the app to know that.

But when RETURN ERROR happens at the top-level procedure of an AppServer and the client is a .NET client, there is no point in making an error object because there's nothing we can do with it.  As I had said in a previous post, the OpenClient product was not upgraded to be able to handle error objects, so it still uses the old mechanism (though in 12.0, Java OpenClients have been modified to get error information from a thrown error object).  So RETURN ERROR <string> works the way it always did.  But if an error is thrown, we say that we cannot do it because the mechanism for doing that is not implemented.  Pretty straight forward.

I believe I had suggested that you log a bug: The AVM should log the actual error message, if there is one, when you get error 14438.  I.e., If we can't throw the error to the client, we should at least get the error information in the log file.  So please do that if you haven't already.

Posted by Laura Stern on 16-Jan-2019 13:54

OK.  I forgot it worked this way.  But what is happening with the AppError case is that if the ReturnValue property is set in an AppError, when the object is thrown, the AVM sets RETURN-VALUE from that property value.  So when this is thrown out the top of an AppServer, to an OpenClient, as I said, even though you get 14438, we still return an error and we always send over the value in RETURN-VALUE, regardless of how/when it got in there.  That is why it seems to be the seem behavior.  But what's going on internally is really very different.  We are giving you the 14438 message because normally, if you throw an error object, you'd expect the whole object (or at least all of the relevant information in it, notably the error message) to be sent back to the client, and this is not happening.

All Replies

Posted by Laura Stern on 15-Jan-2019 15:45

Not mysterious.  These are different statements doing 2 different things.  RETURN ERROR <string> raises an error and sets RETURN-VALUE.  Whereas when an AppError is thrown it raises error and there is an error object available to be caught.  The error object can have a ReturnValue set in it, or not, an error message set, or not, and possibly can have other properties if it is a custom AppError.

Having said, that, when RETURN ERROR happens, internally, the AVM usually makes an AppError automatically, setting the ReturnValue property with the string so that if there is a CATCH statement above, this can be caught like any other thrown error.  And no, you can't tell if this came from the application itself or was manufactured by the AVM.  Don't see a reason for the app to know that.

But when RETURN ERROR happens at the top-level procedure of an AppServer and the client is a .NET client, there is no point in making an error object because there's nothing we can do with it.  As I had said in a previous post, the OpenClient product was not upgraded to be able to handle error objects, so it still uses the old mechanism (though in 12.0, Java OpenClients have been modified to get error information from a thrown error object).  So RETURN ERROR <string> works the way it always did.  But if an error is thrown, we say that we cannot do it because the mechanism for doing that is not implemented.  Pretty straight forward.

I believe I had suggested that you log a bug: The AVM should log the actual error message, if there is one, when you get error 14438.  I.e., If we can't throw the error to the client, we should at least get the error information in the log file.  So please do that if you haven't already.

Posted by dbeavon on 15-Jan-2019 16:32

Thanks for the feedback.  

>> when RETURN ERROR happens at the top-level procedure of an AppServer and the client is a .NET client, there is no point in making an error object because there's nothing we can do with it.

I think this is the part that introduces the behavior differences  (insofar as whether the agent logs get a "14428" message or not).

One thing I wanted to re-iterate is that, setting aside the "14428" message for THROW AppError,  in all other respects, we get the same behavior for THROW AppError as we do when using the RETURN ERROR statement.  This is an important thing to stress, because we'd prefer it if EITHER of these error-raising mechanisms would "escape" from the pasoe session and relay back to the client WITHOUT any additional noise in the agent log.  The openclient is able to receive the "ReturnValue" in both cases, and that is a good thing.  The error will become the full responsibility of the openclient.  These errors are the way that we communicate regular business-logic failures from appserver back to the openclient.

It is all OTHER types of errors (not AppError or RETURN ERROR) that cannot be successfully transmitted to the openclient.  They are NOT easily "handled" from an openclient perspective, because the openclient receives no related information.  And the responsibility for troubleshooting and fixing them is normally on the PASOE software developer (usually we just try to convert them into an equivalent RETURN ERROR.)  Those are the ones that are most frustrating because the only information we have about them is the "14428" message in the agent log.  When that unhelpful message is printed by PASOE, we lose any other details that might be helpful to find the underlying root cause.

So to summarize, I would want the agent logs to stay quiet for the "handle-able" AppError's and RETURN ERROR's.  But for all other types of errors I would want the agent log to at least provide the underlying messages and stack trace.  Better yet, make it the responsibility of the developer to decide if/when to print details about errors "escaping" from PASOE.  Most developers would remain quiet where AppError and RETURN ERROR are concerned, because those errors are things that happen as a matter of course.

Posted by Laura Stern on 15-Jan-2019 22:53

Now I'm confused.  You're saying that the openclient gets back a ReturnValue even when you get error 14438 (not 14428)?  That makes no sense to me.  We do return an error.  But I can't conceive of how you'd get a valid ReturnValue.

I misspoke in an earlier post.  If you do RETURN ERROR, we should not be logging any error to the AppServer log.  We don't log ReturnValues, only error messages. So it will be "quiet" in that case, as you wish.

But as far as I know there is no difference in how we handle a thrown AppError and any other kind of thrown error object if both are being thrown out of the AppServer.  Based on what you said earlier, I suspect that you don't have the 14438 problem with AppErrors because you catch them and turn it into a RETURN ERROR yourself.  So you are never attempting to throw one back to the client.

Posted by dbeavon on 16-Jan-2019 00:32

>>You're saying that the openclient gets back a ReturnValue even when you get error 14438.

Yes.  I wanted to make sure you saw that.  The .Net openclient receives the ReturnValue.  This is despite the noise in the agent logs (14438 messages).  But remember that the error has to be thrown as an AppError class.

You can see why it is a bit obnoxious that all of our external entry-point procedures need to have the CATCH AppError block that does nothing more than translate to a corresponding RETURN ERROR.  Even without the redundant CATCH, I would be getting the same behavior ... aside from the agent log message (14438). 

So it's somewhat mysterious after all?  When I first reported the difference (between errors is raised via THROW'ing AppError or "RETURN ERROR") a Progress KB was created.  That was back in 2014.

https://knowledgebase.progress.com/articles/Article/000045832

In summary,  the error message is available to the .Net openclient (in the ReturnValue) if the error was either sent by THROW'ing an AppError, OR it was sent back via RETURN ERROR..  The openclient is able to take full ownership of error-handling because all the necessary information (a character string) is sent over the proxy.  Based on my testing, the only drawback of THROW'ing an AppError is additional the noise in the agent log (ie. 14438 messages).

As I said these business-logic failures happen as a matter of course (eg for regular data entry issues or validation).  They should not cause so much noise in the agent logs, if that can be avoided.

But for other types of errors (ones that are not based on AppError), there is no way for the openclient to get the related information.  So those are the ones that still warrant a 14438 message (along with additional details about the message which are currently missing ).

>> ... you catch them and turn it into a RETURN ERROR yourself.  So you are never attempting to throw one back to the client.

Yes we do this and it is very repetitive.  The only purpose seems to be in order to avoid the 14438 messages in the agent logs.

Posted by Laura Stern on 16-Jan-2019 13:54

OK.  I forgot it worked this way.  But what is happening with the AppError case is that if the ReturnValue property is set in an AppError, when the object is thrown, the AVM sets RETURN-VALUE from that property value.  So when this is thrown out the top of an AppServer, to an OpenClient, as I said, even though you get 14438, we still return an error and we always send over the value in RETURN-VALUE, regardless of how/when it got in there.  That is why it seems to be the seem behavior.  But what's going on internally is really very different.  We are giving you the 14438 message because normally, if you throw an error object, you'd expect the whole object (or at least all of the relevant information in it, notably the error message) to be sent back to the client, and this is not happening.

Posted by dbeavon on 16-Jan-2019 14:32

The confusing thing is that, when we first encountered it, AppError appeared to be the new "S.E.H.-equivalent" for "RETURN ERROR".  I suspect most developers assumed they would interchangeable.  In fact, AppError even has the ReturnValue property, presumably to imitate "RETURN ERROR" as closely as possible.

... And within ABL itself they seem to be interchangable.  As you pointed out, you don't provide any possible way for a CATCH block to distinguish whether an error was raised via THROW'ing AppError or via RETURN ERROR.

... But despite all the similarity between the two, when it comes to the agent log one makes "noise" as the error is being transmitted to the .Net openclient and one does not.  

 

>> normally, if you throw an error object, you'd expect the whole object (or at least all of the relevant information in it, notably the error message) to be sent back to the client, and this is not happening.

I suspect most developers to put the "relevant information" in the ReturnValue of the AppError class (via the AppError constructor which takes a single character string).  For most practical purposes, the whole object *IS* being sent back to the client.  (or at least it is no less information than we get with RETURN ERROR).

Perhaps there were too many goals bundled up into the original design of the AppError class, and some of them actually conflict with the need for an S.E.H. equivalent of "RETURN ERROR".

Posted by ske on 16-Jan-2019 14:50

> But despite all the similarity between the two, when it comes to the agent

> log one makes "noise" as the error is being transmitted to the .Net openclient and one does not.  

Perhaps the best "fix" would be for the AppServer to stop logging an error when an AppError is thrown to the client in those cases where the AppError does not contain any other information than the RETURN-VALUE. That would make AppError and RETURN ERROR more fully equivalent and interchangeable.

Posted by Laura Stern on 16-Jan-2019 14:52

Sorry, but you are looking at things from a very skewed perspective.  No - an AppError is not the EQUIVALENT of RETURN ERROR.  It merely provides backward compatibility with that statement.  An AppError can have much more information in it.  Also, in the past some applications used the string of RETURN ERROR <string> to be an error message.  But some used it for other purposes.  Now with AppError, you can have a real error message, and use ReturnValue for something else, like context information, if you wish. Plus you can have your own error class that inherits from AppError and put whatever you want in there.  So no - most developers of a new application would not simply use the ReturnValue.  They will set an error message into the object, which is the most important thing, and then possibly use ReturnValue or other custom properties.  That way when the error is caught, they do not need to distinguish between an AppError or a SysError to display the error message.  They can always use GetMessage(n) to get the error message string out.  Then if it is an AppError, they may want to do additional processing.

Posted by Laura Stern on 16-Jan-2019 14:55

So no, we would not "fix" the AppServer as you suggested because we do not want AppError and RETURN ERROR to be more fully equivalent and interchangeable.  They are different and are meant to be different.

Posted by dbeavon on 16-Jan-2019 15:26

From my perspective it isn't the whole story to simply say "they are different"  Especially considering that RETURN ERROR will behave - within ABL - just like UNDO, THROW AppError.  

... and as you had mentioned before, there is not even a way for a CATCH block to distinguish whether the error was originally raised one way or the other.  How is that not equivalent !?

I think the real problem is that there are conflicting design goals for AppError.  Ideally there would have been another ("ReturnValueError") that was derived from AppError and its purpose would be much more certain than the purpose for AppError.

As things are today, AppError  seems like the only "go-to" error for a PASOE developer who might be trying to proxy a failure message back to the openclient.  And that would work fine except for the noisy agent log.  So we are stuck wrapping a CATCH around all our entry level procedures, just in order to trim the noise out of the agent log.  That is a lot of very repetitive and "clunky" programming work.

In the end, the solution might be to have a way for an ABL developer to introduce logging for themselves (possibly via some kind of event/trigger?).  IE this could be done if we need any additional logging/behavior whenever an error is "escaping" from PASOE and is bound for the openclient.  We should be able to accomplish this without repeating code in every single entry-procedure.  It seems unfortunate that a PASOE application developer is at the mercy of PASOE agent to capture & log any exceptional (un-handle-able) errors that might be escaping.  I think it would be very useful for a developer to introduce our own customized behavior.

Posted by Laura Stern on 16-Jan-2019 15:35

Well, IMO, the way to solve this is to upgrade the .NET Open Client so that error objects can be thrown back to it, as we did for Java Open Clients. We need to move forward.

Posted by Torben on 16-Jan-2019 16:10

Beside the behaviour when called from .Net Open Client.

The thing that puzzled me the most when moving from classic to structured error handling, was the difference in when catch block is executed!

RETURN ERROR, dont execute CATCH in in procedure where statement is executed, but UNDO, THROW do!

RUN p.
PROCEDURE p PRIVATE:
    RETURN ERROR.
    //UNDO, THROW NEW Progress.Lang.AppError().
    CATCH e AS Progress.Lang.AppError:
       MESSAGE "Internal procedure level catch" VIEW-AS ALERT-BOX.
       UNDO, THROW e. 
    END CATCH.
END PROCEDURE.
CATCH e AS Progress.Lang.AppError:
   MESSAGE "Global procedure level catch" VIEW-AS ALERT-BOX.
   UNDO, THROW e.
END CATCH.

Posted by Laura Stern on 16-Jan-2019 16:48

That is because RETURN ERROR returns first, then raises error.  So it is the CATCH block in the caller that will run.  Where us UNDO, THROW raises error right there on that line.  So the local CATCH block will catch it.  This is explained in the documentation.

Just FYI, in version 12, the Error handling book has been rewritten and I believe is now very clear and much more concise on how all of this works and on how the different statements involved in error handling interact.  You should read it!  :-)

Posted by bronco on 16-Jan-2019 16:57

Yes please! When?

Posted by Laura Stern on 16-Jan-2019 18:17

If you're referring to upgrading the .NET OpenClient error support, I can't answer that question.  You need to let Product Management know that it is important to you.  

Posted by Torben on 17-Jan-2019 12:53

Yes, I've learned to live with (and maybe understand) the differences between RETURN error and THROW, but I still occasionally have problems with multi block-level ON ERROR UNDO, THROW, where none of the CATCH executes. All are shortcut'ed by RETURN ERROR.

So when using structured error handling, my recommendation will be to avoid mixing with classic RETURN ERROR unless you know you want the RETURN ERROR behaviour.

Posted by dbeavon on 17-Jan-2019 13:55


>> avoid mixing with classic RETURN ERROR unless you know you want the RETURN ERROR behaviour.

We would like to have TOTALLY avoided "RETURN ERROR" as well. But that continues to be necessary in our top-most entry-procedure. We need to translate all AppError's into RETURN ERROR right before these errors are sent to the openclient.  Or the agent logs will get very noisy.

Interestingly, when I reported this difference between the two (back in 2014), the KB article was written and you will see that it includes the following note: "Resolution : Scheduled to be implemented in the 11.4 OpenEdge Release" ... see https://knowledgebase.progress.com/articles/Article/000045832

I'm not sure what that ("scheduled to be implemented") means ... but I haven't noticed any changes yet, and we are several years down the road, on a totally different appserver technology.  I'm eager to see some ambitious new enhancements for PASOE (eg. allowing the full exception detail to be marshalled from the server to the .Net openclient) but I'm not holding my breath.  In the meantime Progress might consider simply allowing AppErrors to pass quietly from server to client just like the RETURN ERROR does.  I think that is the behavior which many ABL developers would expect, given the interchangability between RETURN ERROR and THROW AppError in the ABL runtime.

It still seems a bit mysterious that the "top-most" entry-procedure decides NOT to create an AppError from the RETURN ERROR  and it behaves totally differently than other procedures.  In theory that top-most RETURN ERROR should be just as noisy in the agent logs as if we were THROW'ing AppError. 

Posted by Laura Stern on 17-Jan-2019 13:56

These statements have a very specific and well defined behavior.  You learn what each statement does and code it the way you want it to behave.  So your statement: "So when using structured error handling, my recommendation will be to avoid mixing with classic RETURN ERROR unless you know you want the RETURN ERROR behaviour." is kind of peculiar.  Yes, of course - as I just said, you learn what the statements do first, then you code so that it does what you want!  

And BTW - this is always what RETURN ERROR has done.  It returns first and then raises error.  So if you were in an DO ON ERROR block, it would not follow the ON ERROR directive of that block, it would return and then raise error and abide by whatever error handling was in the caller.  Not very mysterious.

What do you mean by:  "problems with multi block-level ON ERROR UNDO, THROW, where none of the CATCH executes"?

Posted by Torben on 17-Jan-2019 16:02

The difference in possibility to suppressing errors on block level in THROW and RETURN ERROR

BLOCK-LEVEL ON ERROR UNDO, THROW.
RUN p IN THIS-PROCEDURE.
PROCEDURE p PRIVATE:
   DO ON ERROR UNDO, THROW:
      DO ON ERROR UNDO, THROW:
         RETURN ERROR.
         // UNDO, THROW NEW Progress.Lang.AppError().
         CATCH e AS Progress.Lang.AppError:
            MESSAGE "Inner block level catch" VIEW-AS ALERT-BOX.
            UNDO, THROW e.
         END CATCH.
      END.
      CATCH e AS Progress.Lang.AppError:
         MESSAGE "Outer block level catch" VIEW-AS ALERT-BOX.
         // Don't re-throw
      END CATCH.
   END.
   CATCH e AS Progress.Lang.AppError:
      MESSAGE "Internal procedure level catch" VIEW-AS ALERT-BOX.
      UNDO, THROW e.
   END CATCH.
END PROCEDURE.
CATCH e AS Progress.Lang.AppError:
   MESSAGE "Global procedure level catch" VIEW-AS ALERT-BOX.
   UNDO, THROW e.
END CATCH.

Posted by Laura Stern on 17-Jan-2019 16:32

Sorry - was there a question in there?  I've already explained the difference between RETURN ERROR and THROW.  

Posted by Torben on 17-Jan-2019 16:41

No, no question, Just something that caused our transition from classic to structured error handling (including suppressing errors) not to work as we initially expected.

(And in our case a RETURN ERROR came from an include file, so it was not directly visible in code being modified)

A solution we sometimes used was to wrap the include inside a procedure, so RETURN ERROR was converted to AppError in the caller.

{incldue/return-error}

->

run p.

procedure p:

{incldue/return-error}

end procedure.

Posted by Laura Stern on 17-Jan-2019 17:21

I'm still confused, since as I said, RETURN ERROR hasn't changed in terms of where the error was raised.  So you just needed to know that you needed a CATCH block in the caller. Maybe you didn't realize that.  But I don't really need to understand!  Thanks.

This thread is closed