Wednesday, November 18, 2009

CF: Controlling Time Based Runtime Exceptions

Ultimately time controls everything. This is true in larger scheme of things but as well in writing and running ColdFusion code. Fortunately, many CF tags have built in time controls in forms of exceptions that can be thrown when a pre-specified time has been exceeded. Most tags implement this through a timeout attribute.

You, then, use a CFTRY/CFCATCH combination to handle timeout occurrences, e.g. a web page failed to respond, or FTP didn't connect, etc.

However, this system breaks down if your intent is control multiple tags in combination, e.g. a combination of CFHTTP and CFFTP may take up to 30 seconds though individually CFHTTP or CFFTP may consume a variable amount of time.

Similarly, you are running a multitude of small steps in a loop, e.g. loading 1000 records, or displaying 500 lines. Though each line will only take a fraction of a second to process the number of lines in aggregate will blow you out of the water.

Normally in these cases we would wait on an overall page time out if we had one, then produce a generic catch all message saying your page has timed out and good luck trying this again.

This may suffice generically speaking, but may prove to be frustrating for users and visitors of your apps and sites.

What if you could instead be more pro-active. What if you could predict how long your processing is going to take, even on shared servers with varying loads?
What if you could give users options to either proceed, adjust your number of iterations, or simply stop early because you will never finish the task at hand in the time given.

Ideally your code would be very compact:


<!--- check prediction --->
<cfif objTC.WillTimeout()>
<!--- we will timeout lets do something about it --->
...



Conceptually speaking this could have been done for a long time in CF, but practically I have seen few implementations.

Let's look at what it would take to make this prediction using a scenario in which we have a repetitive core process that we need to time. Thus, it would be all about the ability to measure time for each loop. Thus we would
a) need to start a timer, e.g. GetTickCount()
b) end the timer, e.g. another GetTickCount()
c) measure the difference between start and end time
d) use the remaining iterations to come up with how much more time we need

Simple, right? Indeed it is. But the downfall is that it doesn't work.
The system breaks down because of the high variability in practice between each iteration of the loop. Using the built in CF function GetTickCount() we could establish a start and end time. But, when we measure the execution time between the two tick counts the results in this realm change quite a bit.

So, will we have to implement something complex using Java JETM instead to get this working? You probably could, but it isn't necessary if we add a dose of statistics to the whole puzzle. Here is a modified approach:
a) establish statistical validity threshold in number of iterations (e.g. how many time do I need to have gone through the loop to have a valid sample)
b) still need to start a timer, e.g. GetTickCount()
c) end the timer, e.g. another GetTickCount()
d) measure the difference between start and end time
e) update your statistics
f) if validity threshold has been reached make prediction


This does seem to work a lot better but requires many more lines of code. Rather than put all of these lines of code into each template, I have abstracted the needed calls into a component.

I would initialize the component (objTC) with the number of iterations (number of times I would need to go through the loop) and how many times I will need to have completed the loop to get a valid time prediction. In my example this would be 1000000 iterations with 150 completions. Or, something like this:


<cfset objTC = CreateObject("component","TimeControl").init(1000000,150)>



Then within the loop I would start and end the main timer like so:


<!--- start measuring --->
<cfset objTC.start()>


End Measuring:


<!--- end measuring --->
<cfset objTC.end()>


Now that the component takes care of all the nasty statistics stuff I can keep my main code simple and have some functions to use to either provide feedback to user, or just check on the status of things.
E.g. I could provide feedback like so:

<cfif objTC.HasValidAverage()>
<!--- send the expected time to user
when this process will complete --->

<cfoutput>
Expected completion at #objTC.getFinishTime()#
or (#objTC.getFinishSeconds()#seconds)
<br/>
</cfoutput>

<cfflush>


This is just a sample on how to handle it. I have put several more functions in the examlpe to play with. It may work well for some of your scenarios or not all. In any case, I do believe an improved feedback to user and more predictive code behavior based on execution time will reflect well on you as programmer. So feel free to play with samples and see whether this can get you started.

The full sample code can be downloaded here.


Cheers,
-B

No comments: