Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Shawn Oden
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Shawn Oden

What If ColdFusion's CFThread Tag Had An Interval Attribute?

By
Published in Comments (2)

In Javascript, there are two functions - setTimeout() and setInterval() - that allow you to perform actions in an asynchronous fashion. While setTimeout() executes only once, setInterval() will execute a given function repeatedly at the given interval. On the ColdFusion side, we have CFThread, which allows us to perform asynchronous tasks; but, unlike Javascript, the only way to create repetitive asynchronous behavior in ColdFUsion is to use something like a scheduled task. Scheduled tasks are good; but, there's something about them that I've never really liked. As such, I wondered if we could bridge the gap by giving the CFThread tag an Interval attribute?

If CFThread truly had an Interval attribute, the body of the CFThread tag would provide the behavior which would be run at the end of every interval; since this is just a proof of concept, however, we have to invoke a little hackery to make things work. In the following code, I am using a beautiful idea that Elliott Sprehn outlined in his CFUNITED 2010 talk - "I Bet You Didn't Know You Could Do That With ColdFusion." Specifically, I am referring to defining a ColdFusion user defined function (UDF) as the "body" of a ColdFusion custom tag:

<cf_mytag iterator="myBody">
	<cffunction name="myBody">

		<!--- Tag body here. --->

	</cffunction>
</cf_mytag>

As you can see here, where we would normally define the custom tag body, we are, instead, defining a user defined function. Then, we are passing that UDF name (or reference if you prefer) to the custom tag such that it may manually invoke the UDF as the custom tag body (either once or in an iterative mannor). Using a UDF-as-body approach does create some complications as far as binding and execution context go; but, as you'll see in my proof of concept, these hurdles can be overcome with a little bit of hackery (different than the "Arguments" approach Elliott used in his presentation, although ironically, based on something else that Elliott taught me).

Now that you understand the use of UDFs as custom tag bodies, let's take a look at my CFThread-Interval proof of concept:

<!---
	Set the counter that will be incremented by the interval callback
	method.
--->
<cfset counter = 1 />


<!---
	Start an asychronous thread and run it at intervals of 500
	millisconds. Since I am not *truly* defining a CFThread here,
	I need provide a function callback in leu of the CFThread tag
	body.... yo, this is just a proof of concept! In reality, the
	CFThread body would be THE function that executed at intervals.
--->
<cf_thread
	name="interval"
	action="run"
	interval="250"
	callback="intervalCallback">
	<!--- --->
	<cffunction name="intervalCallback">

		<!--- With each interval, increment the counter. --->
		<cfset counter++ />

	</cffunction>
	<!--- --->
</cf_thread>


<!---
	Sleep the current thread - that should give our interval
	enough time to execute several interval durations (and update
	the counter variable).
--->
<cfthread
	action="sleep"
	duration="2500"
	/>


<!---
	Kill our interval thread. We don't want this beast running
	indefinitely on the machine.
--->
<cf_thread
	name="interval"
	action="terminate"
	/>


<!--- Output the current counter variable. --->
<cfoutput>

	Counter: #counter#

</cfoutput>

To mimic the CFThread tag, I've created a thread.cfm (cf_thread) ColdFusion custom tag. As I explained above, this custom tag uses a user defined function, "intervalCallback," in leu of an authentic CFThread tag body. While this concept might seem foreign, the rest of this demo should be easier to understand: we're creating a couter in the main page which our spawned thread will then increment at 250ms intervals. After we launch our asynchronous thread, I am sleeping the primary thread for a few seconds in order to allow enough time for the "interval" thread to execute a few times.

When we run the above demo, we get the following output:

Counter: 10

As you can see, in the 2.5 seconds that the primary page went to sleep, our asynchronous thread, running at 250ms intervals, was able to execute 9 times, incrementing the counter once per execution.

Ok, so how does this cf_thread custom tag work? Typically, when you execute a user defined function (UDF), such as the one we are using for the custom tag body, the UDF executes in the context of the calling page, not the defining page. As such, we have to dip down into the Java layer to manually execute the tag-body UDF in the context of the parent page as defined by the custom tag's Caller scope. Once we can do that, the asynchronous behavior itself is simply defined as a CFThread that runs for an indefinite amount of time, sleeping for a duration defined by the custom tag's Interval attribute:

Thread.cfm (cf_thread)

<!---
	NOTE: This cf_thread.cfm custom tag is not meant to be a full
	replica of the CFThread functionality; I am only trying to create
	functionality as a proof of concept that is related to running
	threads on an interval.
--->


<!--- Param the tag attributes. --->

<cfparam
	name="attributes.name"
	type="variablename"
	/>

<cfparam
	name="attributes.action"
	type="string"
	default="run"
	/>

<!---
	We only need the callback name if the action is run - we will
	assume that the interval is non-zero, but we won't be validating
	for that in this proof of concept.
--->
<cfif (attributes.action eq "run")>

	<cfparam
		name="attributes.interval"
		type="numeric"
		/>

	<cfparam
		name="attributes.callback"
		type="variablename"
		/>

</cfif>


<!--- ------------------------------------------------- --->
<!--- ------------------------------------------------- --->


<!--- Check to see if the action is terminate. --->
<cfif (attributes.action eq "terminate")>

	<!--- Kill the given thread. --->
	<cfthread
		name="#attributes.name#"
		action="terminate"
		/>

	<!---
		Exist out of this tag since it shouldn't do anything
		else after terminating a thread.
	--->
	<cfexit method="exitTag" />

</cfif>


<!--- ------------------------------------------------- --->
<!--- ------------------------------------------------- --->


<!---
	If we've made it this far, the only other action that we are
	currently supporting in RUN with an interval. In order to get
	this proof of concept to work, we are going to have to execute
	the interval callback in the context of the CALLER scope. Since
	UDFs outside of ColdFusion components bind to their execution
	context, we have to jump through some black-magic hoops to get
	this to execute properly.

	NOTE: This is in heavy thanks to Elliott Sprehn who's Kung Fu
	is truly amazing.
--->


<!---
	Get the meta data for the caller page. This will give us the
	CLASS used for the caller scope; from this, we can use
	reflection to access the given field (page context) from
	a given instance of that class (caller).
--->
<cfset callerMetaData = getMetaData( caller ) />

<!---
	Get the class field that would hold a reference to the
	pageContext. By default, we cannot access this field so we'll
	have to toggle is access rights.
--->
<cfset contextField = callerMetaData.getDeclaredField( "pageContext" ) />

<!---
	This is a private field so we have to explicitly change its
	access (I don't fully understand things at this level - this
	is just what Elliott had... and, if you don't do it, it tells
	you that the field is private and throws an error).
--->
<cfset contextField.setAccessible( true ) />

<!---
	Now that we have the Field object that represents the
	PageContext property, use reflection to get that property from
	the CALLER instance.
--->
<cfset callerPageContext = contextField.get( caller ) />

<!---
	This method will simply echo whatever arguments have
	been passed to it. This is just an easy way to define an
	argument collection to be used when invoking the passed-in
	Greeting method.

	NOTE: To be used with NAMED attributes. Positional attributes
	have be executed using an Object[] array... (I think).
--->
<cffunction
	name="getArgumentCollection"
	access="public"
	output="false"
	hint="I echo the arguments collection.">

	<cfreturn arguments />
</cffunction>



<!---
	At this point, we should have everything we need in order to
	execute the interval callback function in the context of the
	caller scope. Now, we have to start the CFThread that actually
	runs the interval function. You'll notice that we are NOT going
	to pass in any of the function stuff. Passing data to CFThread
	via its tag attributes performes deep-copy on the values; we
	need those refrences to be maintained.
--->
<cfthread
	name="#attributes.name#"
	action="run">

	<!---
		Since this thread is meant to run at an interval, we need
		to enter an indefinite loop - remember, this is just a proof
		of concept.
	--->
	<cfloop condition="true">

		<!---
			Sleep the tag for the interval. Notice that we are
			accessing this via the custom tag's Variables scope, not
			the attributes. If we tried to use the Attributes scope,
			it would have been conflicting with the CFThread's local
			attributes collection.
		--->
		<cfthread
			action="sleep"
			duration="#variables.attributes.interval#"
			/>

		<!---
			Invoke the callback function in the caller context.

			NOTE: To invoke the method, we are using the underlying,
			undocumented Java methods for invoking ColdFusion methods.
		--->
		<cfset caller[ variables.attributes.callback ].invoke(
			caller.variables,
			javaCast( "string", variables.attributes.callback ),
			callerPageContext.getPage(),
			getArgumentCollection()
			) />

	</cfloop>

</cfthread>


<!---
	NOTE: Since ColdFusion moves all UDF definitions to the top
	of a file when it compiles (for lack of a better metatphor),
	we don't need to worry about the END MODE of execution for this
	tag - the callback UDF, while defined inside the custom tag
	body, is actually available before the custom tag executes.
--->
<cfexit method="exitTag" />

The bulk of this custom tag is concerned with getting the Caller scope's PageContext in order to invoke our UDF in the appropriate execution context. If you look past that, however, you'll see that there is little more to this custom tag than an encapsulated CFThread tag that uses a Sleep directive to pause the asynchronous thread for each interval. I assume that this ties up a valuable thread on the server; but, like I said, this is just a proof of concept.

When it comes to passing data into a ColdFusion CFThread, you have to be very careful; data passed to a new thread is passed by deep-copy. Since we need to maintain all of the referential integrity between the caller context and the custom tag body, you'll notice that I am not passing any data into the custom tag. Rather, I am relying on the implicit sharing of the custom tag's Variables scope with the CFThread execution context.

I know that you might be looking at this and thinking that using Scheduled Tasks is just going to be easier; but, I think there is something really nice about creating repetitive behavior within the context of the relevant code. Imagine that we had a ColdFusion component instance that needed to do something internal to the CFC on a regular basis (ex. cleaning cache, watching target directory, etc.); with a CFThread/Interval situation, we could simply launch a "timer" within the component and keep all of the logic completely encapsulated. If we had to go the scheduled task route, however, not only would we have to provide an unrelated landing page for the scheduled task, we'd also have to grant external access to a private piece of behavior.

CFThread is a super powerful feature of ColdFusion and I think building in some Interval behavior would make it all the more awesome. Of course, it might be totally misguided of me to try and pull features relevant in Javascript up into a server-side technology?

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

Reader Comments

113 Comments

Note that JavaScript setInterval and setTimeout do not execute code async. They defer execution of the code, but the code is nevertheless executed within the ui thread (or, in a WebWorker, in the WebWorker thread).

15,848 Comments

@Justice,

Ah, very interesting. I had never actually thought about what thread is used once the execution of the callback actually takes place. Since you bring up WebWorkers, however, I believe they are truly asynchronous, correct?

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