Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Exception Handling Dilemma in VFP


:P
On this page:

It was brought to my attention a couple of days ago that by switching the Web Connection error management mechanism to a new TRY/CATCH based handler instead of the traditional Error method handler, that there's some loss of functionality.

 

In Web Connection 4.0 all Process class errors are handled with Error methods on the Process class which basically capture all non-handled errors. The issue with error methods is that you can't easily disable them so in order to have a debug environment where errors are not handled and a runtime environment where they are I had to use a bracketed code approach like this:

 

*** This blocks out the Error method in debug mode so you can

*** get interactive debugging while running inside of VFP.

 

#IF !DEBUGMODE   

 

*********************************************************************

FUNCTION Error(nError, cMethod, nLine)

**************************************

 

LOCAL lcLogString, llOldLogging, lcOldError

 

*** Have we blown the call stack? If so - get out

IF PROGLEVEL() > 125

   RETURN TO PROCESSHIT

ENDIF

 

nError=IIF(VARTYPE(nError)="N",nError,0)

cMethod=IIF(VARTYPE(cMethod)="C",cMethod,"")

nLine=IIF(VARTYPE(nLine)="N",nLine,0)

 

*** Make sure we don't bomb out here

lcOldError=ON("ERROR")

ON ERROR *

 

*** Shut down this request with an error page - SAFE MESSAGE (doesn't rely on any objects)

THIS.ErrorMsg("An error occurred...",;

   "This request cannot be served at the moment due to technical difficulties.<P>"+;

   "Error Number: "+STR(nError)+"<BR>"+CRLF+;

   "Error: "+MESSAGE()+ "<P>"+CRLF+;

   "Running Method: "+cMethod+"<BR>"+CRLF+;

   "Current Code: "+ MESSAGE(1)+"<BR>"+CRLF+;

   "Current Code Line: "+STR(nLine) + "<p>"+;

   "Exception Handled by: "+THIS.CLASS+".Error()")

 

*** Force the file to close and be retrievable by wc.dll/exe

*** NOTE: HTML object does not release here though due to other

***       Object references like Response.

IF TYPE("THIS.oResponse")="O" AND !ISNULL(THIS.oResponse)

   THIS.oResponse.DESTROY()

ENDIF

 

*  wait window MESSAGE()+CRLF+MESSAGE(1)+CRLF+"Method: "+cMethod nowait

 

IF TYPE("THIS.oServer")="O" AND !ISNULL(THIS.oServer)

   *** Try to log the error - Force to log!!!

   * llOldLogging=THIS.oServer.GetLogToFile()

   llOldLogging = THIS.oServer.lLogToFile

   lcLogString="Processing Error - "+THIS.oServer.oRequest.GetCurrentUrl()+CRLF+CRLF+;

      "<PRE>"+CRLF+;

      "      Error: "+STR(nError)+CRLF+;

      "    Message: "+MESSAGE()+CRLF+;

      "       Code: "+MESSAGE(2)+CRLF+;

      "    Program: "+cMethod+CRLF+;

      "    Line No: "+STR(nLine)+CRLF+;

      "     Client: " + THIS.oRequest.GetIpAddress() + CRLF +;

      "Post Buffer: " + THIS.oRequest.cFormVars + CRLF +;

      "</PRE>"+CRLF+;

      "Exception Handled by: "+THIS.CLASS+".Error()"

 

   THIS.oServer.LogRequest(lcLogString,"Local",0,.T.)

 

   THIS.oServer.SetLogging(llOldLogging)

 

   THIS.SendErrorEmail( "Web Connection Error - " + THIS.oServer.oRequest.GetCurrentUrl(), ;

                              lcLogString)

ENDIF

 

ON ERROR &lcOldError

 

*** Bail out and return to Process Method!

RETURN TO ROUTEREQUEST
ENDFUNC

* EOF wwProcess::Error

#ENDIF

 

Now this actually worked fine, but it's always been a funky scheme. The main sticking point is that there's a compiler switch requirement to enable and disable the debug mode switch. So it requires a recompile to make the change between the two.

 

The other more subtle issue is that it relies on RETURN TO to return back to a specified calling method, which is not always reliable. In fact, if anywhere in the call chain an EVAL() execution causes an error the entire error mechanism breaks because VFP 8 and 9 does not support RETURN TO on EVALUATE() calls. When that's the case RETURN TO does a simple return and you end up executing code FOLLOWING the error.

 

I've never been fond of Error methods in this scenario because there's no deterministic way to return somewhere, so when VFP 8 came out I was glad to see TRY/CATCH and the ability to have more deterministic error handling that allows you to return to a very specific place in your code.

 

So, with Web Connection 5.0 the Error method and DEBUGMODE approach (which by the way was applied to other classes as well) was replaced with a TRY/CATCH handler around the core processing engine. Here the implementation doesn't have any special error methods but instead it calls into:

 

*********************************************************************

FUNCTION Process()

************************

LOCAL loException

PRIVATE Response, REQUEST, Server, Session, Process

 

Process = THIS

Server = THIS.oServer

Request = THIS.oRequest

Response = this.oResponse

Session = this.oSession

Config = this.oConfig

 

 

*** Hook method

IF !THIS.OnProcessInit()

   RETURN

ENDIF

 

 

IF Server.lDebugMode

   this.RouteRequest()

ELSE

   TRY

      this.RouteRequest()

   CATCH TO loException

      THIS.OnError(loException)

   ENDTRY

ENDIF

 

THIS.OnProcessComplete()

 

RETURN .T.

ENDFUNC

* EOF wwProcess::Process

 

The TRY/CATCH captures any errors and then simply calls an overridable hook method that is used to handle the error. A default handler is provided or the developer can override OnError to do whatever is needed. From a design perspective this is much cleaner and doesn't have the potential bug issue using RETURN TO.

 

But, there's some loss of functionality here unfortunately. TRY/CATCH is nice, but its use ends up with some limitations. It:

 

  • Doesn't give you detailed error information
  • You're no longer part of the CallStack – you're back up at the calling level

 

CATCH allows you receive an Exception object, but unfortunately that object doesn't give a lot of information and the information available in it is not quite as complete as what you get in the error method. The main reason for this is that when a CATCH operation occurs it unwinds the call stack which puts you back of the initial calling method. This means you can't get detailed error information from the call stack level where the error actually occurred. Any PRIVATE or LOCAL variables will be gone, and ASTACKINFO() will only return to you the current call stack which is not the callstack of where the error actually occurred.

 

This also means your error information is limited to what the Exception object provides and it's not as complete as what LINENO(), PROCEDURE and SYS(16) provide. The result is that in many situations you can't get the LineNo and executing program name the way you can in an Error method, especially at runtime with no debug info in place.

Alternatives? Not really…

So, now I'm trying to figure out some ways around this limitation – unfortunately I don't have a good way of doing that. My first thought was to allow using Error method in addition the TRY/CATCH handler. A couple of thoughts came to my mind:

 

  • Overriding Try/Catch with an Error Method
  • Using an Error method in addition to Try/Catch and use THROW to create a custom exception

 

The first option would allow the developer to create an Error method and do something like this:

 

FUNCTION Error(lnerror,lcMethod,lnLine)

 

*** Do your own error handling here

 

RETURN TO RouteRequest

 

Unfortunately this doesn't work: VFP doesn't allow RETURN TO or RETRY from within a TRY/CATCH block. Denied.

 

The second option would be a similar approach but rather than returning capture the error information and then rethrow a custom exception that does contain additional information:

 

FUNCTION Error(lnerror,lcMethod,lnLine)

 

this.StandardPage("Error Occurred","Test")

 

LOCAL loException as Exception

loException = CREATEOBJECT("Exception")

loException.LineNo = lnLine

loException.ErrorNo = lnError

loException.Message = MESsAGE()

 

THROW loException

 

ENDFUNC

 

Unfortunately that also doesn't work the error thrown in the Error method is thrown back up to the next error handler. The only thing that can handle an exception in an Error method is ON ERROR. So that also doesn't work.

 

Next thought: Take the exception and attach it to a property so any extra error info is available. Then simply return from the Error method. Unfortunately that also doesn't work in that you cannot short circuit the error processing – any code following the original error continues to run.

 

So in the end it's clear that Error() methods that need to pop up the error chain do not co-exist nicely with TRY/CATCH handlers. I don't see a way that I can make this work using an approach where both are used.

 

In the end my solution is a workaround by adding an lUseErrorMethodErrorHandling flag to the process class and then based on that skipping the TRY/CATCH call:

 

IF Server.lDebugMode OR ;

   this.lUseErrorMethodErrorHandling

   this.RouteRequest()

ELSE

   TRY

      this.RouteRequest()

   CATCH TO loException

      THIS.OnError(loException)

   ENDTRY

ENDIF

 

When set error handling works like in the old version of Web Connection, except that the developer is responsible for doing his own error handling. To simulate similar behavior and message formatting as the TRY/CATCH handler you can run code like this:

 

FUNCTION Error(lnerror,lcMethod,lnLine)

 

LOCAL loException as Exception

loException = CREATEOBJECT("Exception")

loException.LineNo = lnLine

loException.LineContents = MESSAGE(1)

loException.ErrorNo = lnError

loException.Message = MESSAGE()

loException.Procedure = SYS(16)

 

this.OnError(loException)

 

RETURN TO RouteRequest

ENDFUNC

 

It works, but it's not what I would consider the cleanest implementation. It would have been really cool if VFP would have allowed some option or hook that can be managed when the Exception object is created. Instead of passing a Reference to an Exception you'd pass a type - VFP could instantiate the type and you could override say the Init() of that type to collect any relevant information from the callstack at that point. Or have the reference have a method that gets called on initialization. Or at the very least if the TRY/CATCH supported the ability to THROW out of the error method up the chain.

 

Alas, this work around does the trick...

 


The Voices of Reason


 

Justin Graf
February 20, 2006

# re: Exception Handling Dilemma in VFP

I have had the same problem. my solution is similar to yours. My solution was create a public class that has a collection class SaError, and create custom Error class to record more information like class name, time, user and the call stack.
When an error occurs in a try catch block record it to the collection. If the try catch block can't fix the error throw the error again.

When the error is thrown it will go to next try catch block in the call stack. if none are found then it will use Foxpro's old error managment and give worthless information because its an unhandle exception.

Hence the reason of recording the error objects to a collection class. If an error keeps going up the call stack and reach the on error thats when i dump the error collections contents to a text file or display the error messages.

The same thing happens when you throw an error in Visual Studio with a Try Catch Block. an UnHandle Exception error occurs with worthless information, because a new error has occured.

Ana María Bisbé York
March 07, 2006

# re: Exception Handling Dilemma in VFP

Thanks Rick !!

The Spanish version of this article is available at (La versión en Español de este artículo está disponible en:)
<http://www.portalfox.com/modules.php?op=modload&name=Sections&file=index&req=viewarticle&artid=116>


Regards / Saludos,

Ana
www.amby.net
www.PortalFox.com

# DotNetSlackers: Exception Handling Dilemma in VFP


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024