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:
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 ???
/* ---> WAS THIS A RETURN ERROR OR AN UNDO, THROW??? */
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
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.
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.
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.
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.
>>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.
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.
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".
> 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.
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.
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.
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.
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.
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!
PROCEDURE p PRIVATE:
//UNDO, THROW NEW Progress.Lang.AppError().
CATCH e AS Progress.Lang.AppError:
MESSAGE "Internal procedure level catch" VIEW-AS ALERT-BOX.
UNDO, THROW e.
CATCH e AS Progress.Lang.AppError:
MESSAGE "Global procedure level catch" VIEW-AS ALERT-BOX.
UNDO, THROW e.
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! :-)
Yes please! When?
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.