What If ColdFusion's CFThread Tag Had An Interval Attribute?
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
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).
@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?