Forcing your Application.onError event to run to completion in ColdFusion

Categories: HTML/ColdFusion

A common practice when building applications in ColdFusion is to utilize the onError event in the Application.cfc in order to track and log errors that occur in your application, so that you can track down the problems and resolve them. However, there's one type of error that can often escape your onError event handler—and that's requests that are timing out.

First, just some background information on how ColdFusion handles "request timeouts". If the server (or current page) is designed to timeout after 30 seconds, ColdFusion will not simply stop executing when the length of the running request gets to 30 seconds. Instead there are specific operations in ColdFusion1 that check the current running time to see if the request should be halted. That's why if you have a SQL query that takes 45 seconds to run, the page doesn't simple stop after 30 seconds. Instead the query will finish executing and your code won't halt execution until it tries to execute logic that would check the current execution runtime.

NOTE:
This also why when ColdFusion reports the line that took to long to run, it often isn't pointing to the actual line of code that was the real culprit, but a tag like <cfoutput>—which is just displaying the information.

Now that you hopefully have a better understanding of when request timeouts are thrown, let's examine why the Application.onError might not run.

The problem isn't that the Application.onError event doesn't get fired—it does. The problem is that because your page has already been running longer than the allotted time, as soon as ColdFusion encounters one of the operations that checks to see if the page should timeout, it will throw a second error—which effectively breaks your onError event.

The way you can get around this problem is by tracking the current execution timing in your Application and then having your Application.onError immediately adjust the page's request timeout setting as it's first line of logic. Since the <cfsetting /> tag does not check against the current execution time, this allow you to add a buffer to your onError request so that the event can run to completion.

Here's a sample snippet of an Application.cfc which will allow the Application.onError event to run for another 10 seconds—regardless of how long the current template has been running:

<cfcomponent output="false">
  <!---// track the starting execution time //--->
  <cfset executionStartTime = getTickCount() />

  <!---// onError //--->
  <cffunction name="onError" returnType="void" output="true">
    <cfargument name="exception" type="any" required="true" />
    <cfargument name="eventName" type="string" required="true" />

    <!---// declare local variables //--->

    <!---//
      take the current time the request has been running and add 
      10 seconds, to attempt to run the onError handler succesfully 
    //--->
    <cfsetting requesttimeout="#(((getTickCount()-executionStartTime)/1000)+10)#" />

    <!---// insert error code handling here //--->
  </cffunction>
</cfcomponent>

I've been using this trick to help make sure any requests that are timing out are fully logged, so I can evaluate the issue and look for ways to fix the root problem.

1 Unfortunately I do not have a list of which operations in ColdFusion do an integrity check on the request lifecycle. I do know that <cfloop>, <cfoutput>, <cfquery> and most complex cf-based tags do check the current running time against the page's request timeout setting.

Comments

Phillip Senn's Gravatar Clever.
Carl Von Stetten's Gravatar Sweeeeeett! Adding that to my apps right now. Thank you. :-)
Loic Mahieu's Gravatar It's also possible to determine the current request timeout by using the internal coldfusion java class:
createObject("java", "coldfusion.runtime.RequestMonitor").getRequestTimeout()

But your solution is really nicer than mine in the fact that I'm using non-documented feature and above all, an adobe-only component.
Dan G. Switzer, II's Gravatar @Loic Mahieu:

I thought about using createObject("java", "coldfusion.runtime.RequestMonitor").getRequestTimeout(), but I was worried:

1) That creating a java object might eventually cause a lookup to the current execution time, thus in effect breaking my code.
2) Not all servers allow creation of Java objects.

Those were the 2 reasons I decided just to track the execution time myself.
Nolan Erck's Gravatar Just out of curiosity, why not put "<cfset executionStartTime = getTickCount() />" inside a call to onRequestStart()? Is there any reason to leave it as a "stand alone" line of code like that? Or just personal preference?
Dan G. Switzer, II's Gravatar @Nolan:

The main reason is to make sure the code always runs, because the onRequestStart isn't guaranteed to run. If your timeout would come *before* the onRequestStart (such as during the onApplicationStart) then the onError event would actually throw an error because the variable does not exist.

Add Comment

Leave this field empty


If you subscribe, any new posts to this thread will be sent to your email address.