Time-Boxing A CFThread And Then Terminating It If It Takes Too Long To Complete In ColdFusion
Every now and then, I have some processing that I need to perform in ColdFusion; but, I want to time-box the processing such that I can terminate it if it runs for too long. As of late, I've been using the CFThread tag to carry out this scenario. I'll spawn an asynchronous "worker" CFThread; then, block and wait a fixed-amount of time for the worker thread to complete. And, if it doesn't complete in the given time, I ask ColdFusion to terminate the worker thread.
I touched on the topic of terminating asynchronous CFThreads a few years ago. But, in that first post, there was no concept of time-boxing the thread - only terminating it. In this post, I'm going to use the "join" action with a "timeout" to block and wait before inspecting the asynchronous thread for its execution status.
When you provide a "timeout" to the CFThread "join" action:
<cfscript>
thread
name = "target-thread-id"
action = "join"
timeout = 3000
;
</cfscript>
... it blocks the processing of the parent request until the target thread either completes; or, the timeout has been passed. And, if the timeout is passed, the parent request processing picks up and continues processing the request. Because of this behavior, we can use this "timeout" value to time-box the CFThread execution. And then, we can use the CFThread scope to inspect the state of the target thread.
To see this in action, let's spawn a CFThread that we know will take too long to join. Then, we'll look at the CFThread state and terminate it if its still running:
<cfscript>
workerThreadID = "my-worker-thread";
// Start a worker thread that is going to perform some processing that we want to
// time-box. And then, kill if it takes too long to run.
thread
name = workerThreadID
action = "run"
{
// Imagine "real processing" happening here.
sleep( 5000 );
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Now that the worker thread is off doing its thing, we want to give it a limited
// amount of time to complete its work and return. To do this, we're going to use the
// JOIN action with a TIMEOUT. The TIMEOUT will ask the parent page to stop and wait
// for the target thread to complete. And, if it doesn't complete in the allotted
// time, the parent thread will just PROCEED with the request processing.
thread
name = workerThreadID
action = "join"
timeout = 3000 // After 3-seconds, proceed with request.
;
// At this point, the request processing is proceeding. Bet we don't know if this is
// because the worker thread completed; or, because the "join" action timed-out after
// 3-seconds. As such, we have to inspect the state of the worker thread.
workerThread = cfthread[ workerThreadID ];
// Check to see if the worker thread is still pending.
if (
( workerThread.status == "NOT_STARTED" ) ||
( workerThread.status == "RUNNING" )
) {
// The worker thread has not completed in time; ask ColdFusion to terminate it!
thread
name = workerThreadID
action = "terminate"
;
writeOutput( "Terminating worker thread - taking too long to complete." );
} else {
writeOutput( "Worker thread completed in time." );
}
</cfscript>
As you can see, we have a worker CFThread that will sleep for 5-seconds. But, our "join" action is going to time-box it to 3-seconds. Then, after the 3-seconds is completed, we check to see if the worker thread is still executing (which know it will be); and, ask ColdFusion to terminate it.
Now, when we run this code, we get the following ColdFusion output:
Terminating worker thread - taking too long to complete.
With this approach, if the worker thread completes before the timeout has passed, the status of the thread will be "COMPLETED" (or possibly "TERMINATED") at the time we go to inspect it. As such, we'll skip the "terminate" request and just continue on with the parent page processing.
It's not often that I need to time-box the execution of a CFThread tag in ColdFusion. But, when I do, I have found this approach to be straightforward and reliable.
Want to use code from this post? Check out the license.
Reader Comments
Hi, Ben. Interesting stuff as always. I do have a concern for you and readers. I'm both this post and the other, you show doing a sleep.
Just beware that while such a sleep can be terminated, many common operations that could hang up a request (or thread) will NOT terminate on request like that. For instance, a long-running query, or cfhttp call, or file i/o, and the like.
Or at least I can confirm that as a request they can't, and we should confirm if somehow a thread termination would kill a thread doing them. I'm not at my computer to adequately test.
For those interested, I have more on the matter (regarding requests) in this old post of mine (which remains true today):
https://www.carehart.org/blog/client/index.cfm/2010/10/15/Lies_damned_lies_and_CF_timeouts
If you or any of us can get a chance to confirm things, it would be useful to add here.
@Charlie,
Absolutely great point. I wish I could find the snippet of document -- and I may totally be making it up -- but I swear I just read somewhere that there is a note that
terminate
will ask the JVM to terminate the thread, but does promise that it will actually take place. I could swear I read that somewhere; but, it could have been a dream.I should be able to test with a long running query (one that just does a SLEEP on the MySQL server). I'll get back to you.
@Charlie,
That said, I do definitely remember when I was using Fusion Reactor (I miss those days), the request to terminate a given request didn't always work :D