Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Raphael Schürholz
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Raphael Schürholz

RequestTimeout Setting Affects CFThread Execution In Lucee CFML 5.3.6.61

By
Published in Comments (11)

UPDATE (2023-01-07): I had a bug in the way I was originally calling the CFSetting tag. I went back and corrected it and updated some of the descriptions.

Yesterday, when I was exploring task threads in Lucee CFML, I noticed that my long-running CFThread tags were suddenly dying at around 30-seconds of execution time. And, after I poked around in the Lucee Administrator, I noticed that the default request timeout for the server was set to 30-seconds. I didn't realize this, but apparently the CFSetting tag's requestTimeout property affects both top-level page requests as well as daemon CFThread tags. To confirm this, I wanted to set up a simple test in Lucee CFML 5.3.6.61.

To test this, I set up a page that programmatically sets a short requestTimeout and then spawns a CFThread tag that uses a sleep() command to make sure that its execution time takes longer that the define timeout:

<cfscript>

	// Setting a request-timeout of 3-seconds.
	setting
		requestTimeout = 3
	;

	thread
		type = "daemon"
		name = "testThread"
		{

		systemOutput( "CFThread started, about to sleep().", true, false );
		sleep( 5000 );
		systemOutput( "CFThread completed successfully.", true, false );

	}

	// Store a reference to the thread we just created so that we can view the status
	// of it in another script.
	application.testThread = cfthread.testThread;

	echo( "CFThread initiated." );

</cfscript>

As you can see, I'm setting the CFSetting timeout to be 3-seconds. Then, within the CFThread tag, I'm sleeping for 5-seconds (which is longer than the 3-second timeout). For debugging, I'm storing the thread reference in the application scope so that I can dump() it out on another page:

<cfscript>

	if ( application.keyExists( "testThread" ) ) {

		// Dump CFThread data (without the Error, which is output next).
		dump(
			label = "Test Thread",
			var = application.testThread,
			hide = "Error"
		);

		// If CFThread ended in error, output the error.
		// --
		// NOTE: I'm hiding some keys just to make the output easier to read.
		if ( application.testThread.keyExists( "Error" ) ) {

			dump(
				label = "Test Thread Error",
			 	var = application.testThread.Error,
			 	hide = "StackTrace,TagContext"
			 );

		}

	}

</cfscript>

Now, if we run the page that spawns the CFThread tag and then check the debugging page a few seconds later, we see this following ColdFusion error:

A CFThread tag that was terminated due to CFSetting RequestTimeout in Lucee CFML.

As you can see, the CFThread tag ended with status TERMINATED. And, the ColdFusion error appended to the CFThread tag indicates a "request timeout".

Now, let's update the ColdFusion page to set the timeout to longer than 5-seconds to see if the CFThread tag completes without termination:

<cfscript>

	// Setting a request-timeout of 10-seconds. This is LONGER than the asynchronous
	// thread is going to SLEEP.
	setting
		requestTimeout = 10
	;

	thread
		type = "daemon"
		name = "testThread"
		{

		systemOutput( "CFThread started, about to sleep().", true, false );
		sleep( 5000 );
		systemOutput( "CFThread completed successfully.", true, false );

	}

	// Store a reference to the thread we just created so that we can view the status
	// of it in another script.
	application.testThread = cfthread.testThread;

	echo( "CFThread initiated." );

</cfscript>

If we run this ColdFusion page and then check the debugging page a few seconds later, we see that the CFThread tag shows a status of COMPLETED. As such, we can conclude that the CFThread tag execution life-span was directly influenced by the requestTimeout setting on the CFSetting tag.

So, it turns out this was a pretty large blind-spot in my understanding of how the CFThread tag executes in a ColdFusion application. I always just assumed that the CFThread tags would execute indefinitely in the background (assuming no bugs, of course). But, it turns out that the request timeout settings affect both the top-level and the child threads within the Lucee CFML application.

Want to use code from this post? Check out the license.

Reader Comments

15,848 Comments

@Elliott,

Hey man -- long time no talk :D Good to hear from you! I'm actually trying to use this approach right now in my previous exploration on using Task threads. Initial findings are a bit confusing - originally, my threads were seemingly "completed" at about 30-seconds. But now, if I set the request-timeout to 1-min in the Task thread, my daemon threads all seem to be "completing" in 6-12-seconds.... which is the wrong direction 🤔

I'll keep tinkering.

15,848 Comments

Ahhhh, d'uh. My initial attempt to use the CFSetting tag was trying to use the createTimeSpan() function for the requestTimeout. It's been a long time since I've even used this tag. But, it doesn't work that way :D You have to provide a number of seconds, not a time span. When I switch over to something like:

setting
	requestTimeout = ( 60 * 5 )
;

... in the Task thread, then the Daemon threads live much longer.

15,848 Comments

@All,

This morning, I wanted a quick sanity-check to make sure that a long-running CFThread tag - that exceeds the duration of the parent page-request - can still call "back into" the timed-out page request:

www.bennadel.com/blog/3903-calling-into-a-timed-out-parent-page-context-from-a-cfthread-tag-in-lucee-cfml-5-3-6-61.htm

"Page Context" is a funky concept in ColdFusion, so I am sure I am missing some edge-cases here; but, at least for my typical use-case, which is invoking methods on the variables scope (normally as part of a ColdFusion Component context), this seem to "just work". Noice!

15,848 Comments

@All,

I recently start to consider "nested threads" or "tail recursion threads" in Lucee CFML. Which got me to revisit this CFSetting issue. And, I realized that you can sort of get around the request timeout by breaking your asynchronous processing into smaller threads:

www.bennadel.com/blog/3939-recursive-nested-cfthreads-can-get-around-cfsetting-requesttimeout-in-lucee-cfml-5-3-7-47.htm

Meaning, you can exceed the request timeout value as long as no individual CFThread exceeds the timeout. But, in aggregate, multiple CFThread tags can run for a long time.

3 Comments

Great article on a crucial subject. The ability to time out a thread has huge implications. For one thing, it can mean the difference between a rogue thread being stopped in its tracks or being free to hang about and hog memory for as long as it wants.

However, there are questions I should like to be able to answer. How is the thread timed out? What is the underlying mechanism?

I have searched the web, but am unable to find any documentation about the feature in Lucee 5.3.6.61 whereby RequestTimeout affects CFThread execution.

15,848 Comments

@BKBK,

Honestly, I have no idea what the underlying mechanism is. I assume it's just a native part of the thread-pool in Java, which ColdFusion is built on top of. But, I only dip down in to the Java layer occasionally to take advantage of some existing library - I don't really know much about the broader Java programming techniques.

3 Comments

@Ben,
I have been looking some more into your discovery that, from Lucee 5.3.6.61 onwards, the following code will effectively stop the thread after the timeout period of 15 seconds:

thread name="testThread" {
    setting reqtestTimeout=15;
    // Do some thread stuff/
}

I still haven't found any docs on the underlying mechanism. Nevertheless, I have been able to demonstrate that the feature is present in recent Lucee versions, but not in recent Adobe ColdFusion versions.

The demonstration: run the following code on recent Lucee and Adobe ColdFusion versions

<cfscript>

void function runThreadDurationTest() {

     thread action="run" name="testThread"  {

     /* Does  this stop the thread after 15 seconds? */
     setting requestTimeout="15";

     /* Give thread something to do that exceeds the 15-second requestTimeout */
     for (i=1; 1 <= 20; i++) {

        sleep(1000);

     }

}    


/* Task to check thread status. The task runs for at least 30s. 
    That is, well beyond the 15-second requestTimeout in the thread.
    If the thread is still running after 30 seconds, it is terminated. */

  for (i=1; 1 <= 30; i++) {

         if (testThread.status is "running") {

                writedump(var=testThread, format="html",output="#expandPath('threadTestDump.html')#");

         }           

         sleep(1000);           

         if (i==30) {

              thread action="terminate" name="testThread" {}

              break;
         }

    }

}

writeoutput("Is done");

runThreadDurationTest();

</cfscript>

Result after running the code several times:

  • On Lucee, the number of dumps ranged from 11 to 18. The total duration of the requests was between 12 and 18 seconds. This shows that the RequestTimeout setting has the effect of stopping the thread.

  • On Adobe ColdFusion 2018 and 2021, the number of dumps ranged from 29 to 30. The thread's status in each dump was "running". The total duration of the requests was just over 30 seconds. This shows that the RequestTimeout setting does not have the effect of stopping the thread.

15,848 Comments

@BKBK,

Yeah, long-running threads are an interesting beast. This is still something for which I am trying to find a good approach. It's that lack of control / clarity that makes it so challenging. And, then there always the looming threat of "Sudden Thread Death" or "Thread Interruption".

I think for a long time I was fighting the idea of using ColdFusion Scheduled Tasks as a means to manage long running threads. But, the nice thing about a scheduled task is that it's an isolated request. As such, you can set the Request Timeout to something really high or low in order to exert control over the threads that run inside of it.

I think being able to programmatically define the scheduled tasks has helped me embrace them more.

Of course, not all things makes sense for scheduled tasks. Some tasks are just short-lived processes, and for that I still love the <cfthread> tag in a normal context.

3 Comments

@Ben

My initial attempt to use the CFSetting tag
was trying to use the createTimeSpan() function
for the requestTimeout. It's been a long time since
I've even used this tag. But, it doesn't work that
way :D You have to provide a number of seconds,
not a time span

Indeed. The value of RequestTimeout is in seconds. Whereas, the return value of CreateTimeSpan() is number-of-days. That is, CreateTimeSpan(0,0,0,30) = 30/86400 = 0.0003472.

So, setting requestTimeout = createTimeSpan(0,0,0,30) is equivalent to setting a timeout of 0.0003472 seconds!

15,848 Comments

I finally went back and fixed the code / writing in this post (removed the createTimeSpan() call in the CFSetting tag that was incorrect).

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel