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.