Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Using A Closure To "Terminate" CFThread Tags Across Page Requests In Lucee CFML 5.3.6.61

By Ben Nadel on
Tags: ColdFusion

While the CFThread tag has a "terminate" action; and Lucee has a threadTerminate() built-in function (BIF); these two approaches only work within a single page-request - any attempt to terminate a CFThread reference spawned from another page will result in a ColdFusion error. Over the weekend, however, as I was experimenting with task threads in Lucee CFML, I came up with an approach to cross-page thread termination that I thought was kind of interesting: using a ColdFusion Closure to communicate the intent to terminate in Lucee CFML 5.3.6.61.

To be clear, this approach doesn't "terminate" a CFThread tag in the same way that the native threadTerminate() function does. Meaning, it doesn't "interrupt" the thread. Instead, it merely provides a way to tell the CFThread tag that it should stop; and then, it's up to the CFThread logic to take that suggestion into account when executing its own internal control-flow.

Years ago, I looked at using Closures to create a CFThread tunnel. But, in that post, I was using the Closure to expose external data to the CFThread. In this post, I'm doing the opposite - I'm going to use the Closure to expose (so to speak) internal thread data to the outside world.

Specifically, I'm using a Closure to allow the outside world to change a thread-local variable, isRunning. By way of the thread scope, I'm defining a .forceQuit() method which will flip the isRunning Boolean to false. Then, my internal CFThread control-flow will see this change and break out of its own execution:

<cfscript>

	// Keep track of the spawned threads so that we can "force quit" them from another
	// page reference.
	if ( isNull( application.threadRefs ) ) {

		application.threadRefs = [];

	}

	// The execution of the CFThread tag is limited by the request-timeout of the server
	// and application. As such, we have to bump up the request-timeout to make sure that
	// our threads aren't terminated prematurely.
	setting
		requestTimeout = 120
	;

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	uniqueName = "Test Thread #createUniqueId()#";

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

		// I determine if the internal workings of the thread should continue running.
		var isRunning = true;

		// The THREAD scope acts as a "tunnel" by which the CFThread tag can expose data
		// to the outside world. In this case, we're going to expose a CLOSURE that sets
		// and THREAD-LOCAL VARIABLE to FALSE indicating that the CFThread should stop
		// executing its internal loop.
		thread.forceQuit = () => {

			systemOutput( "[ #uniqueName# ]: About to force quit thread." );
			isRunning = false;

		};

		// Keep "doing stuff" until we are told to stop.
		for ( var i = 1 ; isRunning && i <= 120 ; i++ ) {

			systemOutput( "[ #uniqueName# ]: Running iteration #i#.", true, false );
			sleep( 1000 );

		}

		systemOutput( "[ #uniqueName# ]: CFThread exiting.", true, false );

	} // END: Thread.

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Keep a reference to the thread so we can terminate it from another page.
	application.threadRefs.append( cfthread.testThread );
	
</cfscript>

As you can see, inside the CFThread tag, I have a for loop that will continue executing as long as the isRunning flag is set to true. This CFThread tag doesn't really have any additional logic; but, you can assume that the "loop" would be processing some sort of long-running asynchronous task.

Once the CFThread tag is spawned, I store a reference to it in the application scope, which allows me to reference that CFThread instance across pages. Which means, in my "stop" page, I can loop over these references and call the exposed .forceQuit() method:

<cfscript>

	if ( isNull( application.threadRefs ) || ! application.threadRefs.len() ) {

		echo( "No threads to kill." );

	}

	// Let's Loop over all of the cache CFThread instances and try to force-quit them.
	for ( threadRef in application.threadRefs ) {

		// NOTE: This IS NOT terminating the thread - it is calling a Thread-local
		// function that has logic that will, in turn, allow the thread to complete.
		threadRef.forceQuit();

	}

	application.threadRefs = [];
	
</cfscript>

Now, one point of CAUTION: the .forceQuit() function isn't actually defined and exposed on the thread scope until the CFThread tag has started executing. Which means, if the CFThread is queued-up for execution, this method reference will not yet exist. Of course, in this demo, I'm waiting until the threads are running before I try to stop them.

That said, if we start a CFThread, wait a few seconds, and then call the stop page, we get the following terminal output:

[ Test Thread 4b ]: Running iteration 1.
[ Test Thread 4b ]: Running iteration 2.
[ Test Thread 4b ]: Running iteration 3.
[ Test Thread 4b ]: Running iteration 4.
[ Test Thread 4b ]: About to force quit thread.
[ Test Thread 4b ]: CFThread exiting.

As you can see, we were able to start the CFThread tag on one page; and then, terminate the CFThread tag from an entirely different page.

It's not a perfect solution; but, it seems like it at least provides a viable way to influence CFThread tags across different pages within a Lucee CFML application. And, since CFThread tag attributes are passed by-reference in Lucee, we could probably come up with an even more robust way to accomplish.



Reader Comments

What has two thumbs and hopes you leave a comment? This Guy! (Ben Nadel).

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.