Ask Ben: Limiting The Amount Of Time A Block Of Code Can Run
I have this application that uses a black-boxed piece of functionality. 98% of the time, this black-boxed functionality runs very fast as expected. Sometimes though, for reasons I cannot yet debug, this function takes so long that the page times out. Is there a way to make sure that just this block of code only executes for X number of seconds?
CAUTION: Please disregard the solution below! Christoph Schmitz pointed out that this is a serious misunderstanding of how CFLock timeout works... I thought it was how long it could RUN... it is how long it will WAIT. Sorry for misleading anyone. I thought I was helping other people, but it turns out, this time, I get to learn something :)
Please see a better answer here: http://www.bennadel.com/index.cfm?dax=blog:618.view
For starters, we cannot force a piece of code to execute faster than it is going to on its own. If we could do that, I would make sure all my pages executed in 16ms. What we can do, though, is "misuse" the CFLock tag to make sure that your piece of code is only allowed to execute for X number of seconds before it throws an error. We can leverage the TimeOut attribute of the CFLock tag to allow the code a maximum amount of time in which it can run. If the CFLock times out and throws an error, we can then catch it and try to recover from the too-long-running task:
<!--- Try to execute the following block of code. --->
<cftry>
<!---
By putting a named lock on this block of code using
a CreateUUID() value, we can make sure it never
becomes single-threaded as no two CreateUUID()s are
the same. Furthermore, we can limit the amount of
time this code ran run by using the TIMEOUT attribute
of the CFLock tag.
--->
<cflock
name="#CreateUUID()#"
type="READONLY"
timeout="5"
throwontimeout="true">
<!---
This is our black-boxed piece of code. Not
sure how it works, but we know that 2% of the
time it runs way longer than it should and
crashes the page.
--->
<p>
This will only be allowed to execute for
a maximum of 5 seconds.
</p>
</cflock>
<!---
Catch an errors that get thrown from our
CFLock (if it times out).
--->
<cfcatch>
<!---
Our black-boxed piece of code has run too
long. Use this opprotunity to set default
values so that you might be able to recover
from this timeout.
--->
</cfcatch>
</cftry>
There are a few things to notice about the CFLock tag:
We are using a named lock, not a scope lock. By using a named lock in conjunction with a CreateUUID() call, we can ensure that this block of code never becomes single threaded. This is good because I assume this page is not single threaded to begin with (so let's not force it to become so).
We are using a timeout of 5 seconds. This gives the black-boxed code a maximum of 5 seconds to run. You can, of course, adjust this to be what you think it should be; however, this value should be smaller than the request time out of the page itself (otherwise, the page may crash anyway).
We are having the CFLock tag throw an error if the time out is exceeded. This is the default value for the CFLock tag, but I put it in to drive the point home. This is key because the whole thing is wrapped in a CFTry / CFCatch block - the thrown error will give us a chance to recover from the long-running code. After all, the whole point of this is to NOT have the page crap out.
I hope this works for you. I would not exactly recommend this technique, though, as locking does come with processing overhead and I am not sure how this page is being used (high traffic?). Furthermore, I believe I read somewhere that repeated calls to CreateUUID() actually start to slow down your machine, but I cannot state that as fact, just hearsay.
Want to use code from this post? Check out the license.
Reader Comments
Ben,
no offense man, but, you got cflock wrong somehow...
The timeout attribute of cflock specifies the time a request will wait until it aquires a lock, it does NOT specify the time a request within the lock may run. You can have a cflock-timeout of 1 second and still have the request run 100s of seconds.
So if you want the cflock to throw an exception, you need to make the lock exclusive and make the lock name static (make the code block single threaded) and then start a second request. Then this second request will timeout after 5 seconds.
IMHO the easiest way to timeout a request is the <cfsetting requesttimeout="x" /> tag.
Chris
@Chris,
Oh man... you are totally right! Thanks for clearing that up for me. That is a huge misunderstanding in my mind. I knew it felt very wrong to use CFLock in this fashion - another reason now, it simply doesn't work that way.
Thanks for showing me the light :)
@Christoph,
Does this look any better:
www.bennadel.com/index.cfm?dax=blog:618.view