Orange is my favorite color

rctimeoutIn ColdFusion, each page request has a timeout that is set in the ColdFusion Administrator. This is a global default for all requests. It’s independent of the timeout attribute on tags like CFHTTP and covers the entire request lifecycle. You can update it using the cfsetting tag at any point for a given request:

<cfsetting requesttimeout="300" />

I had an issue with cfpayment where processing transactions in a loop was timing out. I had even written some fancy code designed to gracefully abort before timing out but it wasn’t working. I had set the timeout to 1800 seconds and I could see from transaction logs that it was ending right around 5 minutes, or 300 seconds, which is a common timeout setting in our payment processing code. I went sniffing… and this is what I found. My controller was indeed setting the timeout to 1800 seconds. Here is a simplified version of my abort-before-it-times-out code:

<cfset timeout = 1800 />
<cfset thread = CreateObject("java", "java.lang.Thread") />
<cfset timer = getTickCount() />

<cfsetting requesttimeout="#timeout#" />

<cfloop from="1" to="15" index="ii">
	< !-- I was calling cfpayment.charge() here -->
	<cfset thread.sleep(2000) />

	<cfif (getTickCount() - timer)/1000 GT timeout * 0.9>
		<cfoutput>breaking from loop!<br /></cfoutput>
		<cfbreak />

So in my case, I had far more than 15 payments to run in a batch and the timeout was 1800 seconds. But inside of cfpayment, in the base component process() method which handles the CFHTTP code, I had this:

<cfsetting requesttimeout="300" />

Crap. No matter what I set the timeout to be, as soon as I charged a card, it was reset down to just 300 seconds and so my batch processing would time out. Thanks to a clever but woefully named post by Barney Boisvert, getting the request timeout of the current context is super simple:

<cffunction name="getCurrentRequestTimeout" output="false" access="private" returntype="numeric">
	<cfset var rcMonitor = createObject("java", "coldfusion.runtime.RequestMonitor") />
	<cfreturn rcMonitor.getRequestTimeout() />

I have updated the cfpayment core now to have the emulate the existing behavior, which is give a CFHTTP attempt 300 seconds to process a payment against a gateway, but, if we have a longer timeout already, we won’t modify it. Now the code in base.cfc looks like this:

<cfsetting requesttimeout="#max(getCurrentRequestTimeout(), getTimeout()) + 10#" />
<cfhttp timeout="#getTimeout()#" ... >

The clever part here is that we’re taking the longer timeout, current request context or configured timeout, and adding 10 seconds to it and then using the specified timeout for the CFHTTP request. In my loop scenario, it will retain the longer 1800 second timeout while using a 300 second timeout for each individual payment attempt. My controller code will also keep an eye on the overall progress and gracefully stop processing if we find less than 10% of the timeout is left. In the more common case of processing one transaction where the request timeout and CFHTTP timeout are the same, the extra 10 seconds in the RequestTimeout will afford enough time to process the results of the timed out CFHTTP (via try/catch, cferror, onError, etc).

This is a strategy I’ve used for a long time in order to be able to catch and process timeouts (send an email, etc). Ben Nadel had a post about graceful timeout recovery in 2007 which covers this in detail and it’s an invaluable tool for potentially long running network requests that MUST have error recovery such as payment processing. If you don’t use this technique, I recommend reading his post.

The good news is, if you’re using cfpayment, you already have it. :)

1 Comment

  1. zac spitzer said:

    on July 22, 2009 at 6:53 pm

    awesome, often wondered about that one! cheers

{ RSS feed for comments on this post}