Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: David Bainbridge
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: David Bainbridge ( @redwhitepine )

Performing Disaster Recovery After A ColdFusion Page Timeout Exception

By on
Tags:

Previously, I blogged about how CFSetting updates the page timeout setting and does not actually start a new timeout. I had written about this in terms of disaster recovery. I can't find the conversation at this moment, but someone had raised a good point that in order to add time to the request timeout you would need to know how long the page had already been running (or you might set a value in seconds that has already passed).

I had suggested that you could use GetTickCount() in the Application.cfm/cfc file to get the start time of the page, then use that to get the run time of the page when it times out. This is lame because it requires modifying the Application.cfm/cfc file, which just doesn't feel right. Luckily, I just found out how to get the processing time of the page without modifying any other templates.

Using that technique, we can create a slightly more elegant disaster recovery model for ColdFusion request timeout exceptions:

<cffunction
	name="KillTime"
	access="public"
	returntype="void"
	output="false"
	hint="I kill time for the given miliseconds.">

	<!--- Define arguments. --->
	<cfargument name="MS" type="numeric" required="true" />

	<!--- Get start and end tick out values. --->
	<cfset var intStart = GetTickCount() />
	<cfset var intEnd = (intStart + ARGUMENTS.MS ) />

	<!--- Loop until this time is killed. --->
	<cfloop condition="(GetTickCount() LT intEnd)">

		<!--- Just try to kill some processing time. --->
		<cfset intStart = (intStart * Pi()) />

	</cfloop>

	<!--- Return out. --->
	<cfreturn />
</cffunction>



<!---
	Set the current time out to be 2 seconds. We are
	setting this intentionally low because we want the
	page request to timeout.
--->
<cfsetting requesttimeout="2" />

<!--- Try to kill some time. --->
<cftry>

	<!---
		Here, we are killing time - 4 seconds to be
		approximate. This will exceed the request time
		out set above (2 seconds) and will throw an error.
	--->
	<cfset KillTime( 4000 ) />


	<!---
		The KillTime() method call has timed out (just
		as we expected it to).
	--->
	<cfcatch>

		<!---
			Since we just caught an page timeout error, let's
			set a new request time out that is high enough to
			definitely let the page continue running. We HAVE
			to do this because the line of code after this
			requires too much processing time to run in the
			15-30 miliseconds that we have left to work with.
		--->
		<cfsetting requesttimeout="10" />


		<!---
			Now that we have some cushion time, let's use
			the page and fusion context objects to get the
			date/time stamp at which this page started
			processing. Then, get the number of seconds
			that that start date is smaller than the current
			date/time stamp value.
		--->
		<cfset intRunTimeInSeconds = DateDiff(
			"s",
			GetPageContext().GetFusionContext().GetStartTime(),
			Now()
			) />


		<!---
			Now that we know how long the page has been running,
			let's create a new timeout setting with that time
			plus whatever additional time we feel is necessary
			to perform our disaster recovery. In this case, we
			are going to add an additional 5 seconds. This will
			override the "Safe" CFsetting we ran above.
		--->
		<cfsetting
			requesttimeout="#(intRunTimeInSeconds + 5)#"
			/>


		<!--------------------------------------->
		<!--- Perform disaster recovery here. --->
		<!--------------------------------------->

	</cfcatch>

</cftry>


<!--- Get the new run time in seconds for the page. --->
<cfset intRunTimeInSeconds = DateDiff(
	"s",
	GetPageContext().GetFusionContext().GetStartTime(),
	Now()
	) />


<!--- Output that run time. --->
<p>
	The page has been processing for:
	#intRunTimeInSeconds# Seconds.
</p>

Running the above code, we get:

The page has been processing for: 2 Seconds.

As you can see, even though we were caught in the middle of a ColdFusion request timeout, we were able to recover from it quite nicely.

The KillTime() method is used just to force a page timeout after a period of time. Once that happens, notice that we are immediately setting a static CFSetting request timeout setting. I don't like that we have to do this, but unfortunately, the DateDiff() that we are performing is too slow to execute fully in what little "wiggle" room we have (around 16 ms before the page actually dies). The idea of that first CFSetting tag is to have a timeout that we definitely will NOT exceed. I made it 10 seconds, but heck, you could make it 600 seconds. It won't matter since we then quickly override it with something more appropriate.

Once we know that we are safe to run, we can then easily figure out how long the page has been running and set a new timeout that is equal to the existing page run time plus whatever time we feel we are going to need to do CFLog or CFMail or what ever you need to do.

Want to use code from this post? Check out the license.

Reader Comments

28 Comments

This is a very good idea, but doesn't onError usually run after a timeout? So couldn't you get yourself an email that way, too? What are the other instances where this could be of help?

I suppose that you could use this method to redirect the user to another page when certain operations time out, but I'd be interested in hearing about specific applications for this.

15,674 Comments

@Tom,

The OnError() application even is policed by the same CFSettings as that the rest of the page processing. Therefore, if a CFQuery tag times out, OnError() will indeed fire, but if you try to do anything complex in the OnError(), it will fail.

This code above applies to all timeout exceptions as far as I know.

57 Comments

Why do I even bother googling problems like this? I should know just to come to your site. :D
This looks great. I am looping over a list and pulling data from another site and for whatever reason it sometimes hangs up. I was just catching the error and continuing the loop. But I'm thinking now is to limit the timeout so it doesn't hang for such a long time on those problem items and just moves on to the next one faster.

57 Comments

And what should happen now? A major client with a HUGE application that was originally "written" in 4.5 and I'm rewriting in 8 started throwing ugly timeout errors. It has worked fine all these years. I found that the original "coder" *cough cough* used cfsetting liberally throughout the application based on how long HE thought the query or whatever should take. So in one place he set the timeout to 3 seconds. That wasn't bad but he used Fusebox to do this in so there are layers of pages and on each call he put a different timeout so I had to drill down changing them all.
But what really irked me is that he just let it show the ugly error page. Now with what you showed here, they will fail nicely. Actually what I did was set it so it will reset the timeout and retry the query at the longer timeout. It does this until it reaches the absolute max timeout at which time it says "AW S**T" and stops trying and displays an error.

15,674 Comments

@Don,

He set the request timeout to 3 seconds sometimes?? Ha ha ha, that just seems silly. Talk about getting anal with your timeout settings.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel