-
Notifications
You must be signed in to change notification settings - Fork 24
Using the Threading Adapter Package
- Mach-II and Multi-Threading
- Supported CFML Servers
- Implementation of Threading Adapters
- Obtaining a Threading Adapter
- Using a Threading Adapter
Multi-threading is useful is specific circumstances and Mach-II utilizes it for a couple features introduced in version 1.6. However, threading implementations have slight variations between the various CFML servers. This is problematic when trying to implement features that can be multi-threaded and requires the ability to be deployed on a wide array of CFML servers include server versions that do not offer multi-threading capabilities.
Mach-II uses the threading adapter package so certain features can be run in multiple thread. This allows us to run arbitrary pieces of code (methods in CFC) in manner that gracefully degraded to serial execution when deployed on CFML servers that do not support threads.
Features added in Mach-II 1.6 that leverage the threading adapter package:
- Publish / Subscriber listener invoking (
<message>
command) - Caching strategies - TimeSpan and LRU policies (used by
reap
methods)
On versions of Mach-II before 1.9 M2, the Adobe ColdFusion 8 is the only provided adapter that works. We added concrete adapters for Railo and OpenBD in Mach-II 1.9 M2.
All threading adapters implement the same interface and all extend MachII.util.threading.ThreadingAdapter
which is abstract. The implementations vary widely between CFML servers and the adapters provide a unified interface that abstracts the differences between the threading implementations.
Available Methods:
Method Name | Syntax | Description | Returns |
---|---|---|---|
run |
threadingAdapter.run( callback, method, parameters)
|
Runs the method on the "callback" (CFC) with the passed parameters (arguments). | a thread name to use for joins later |
join |
threadingAdapeter.join( threadIds, timeout)
|
Joins a list, array or struct keys of threadIds together and waits until timeout is reached. | an cfcatch struct if an exception occurred in any of the threads |
allowThreading | threadAdapter.allowThreading() |
Checks if threading is allowed. | boolean |
Support for sleep
and terminate
actions have been suggested for inclusion on (trac-wiki) #93 "enhancement: Add Sleep and Terminate support to ThreadingAdapters (closed: Moved to GitHub)").
The easiest way to obtain a threading adapter is instantiate a utility class MachII.util.Utils
and call createThreadingAdapter()
. This will return a threading adapter for CFML engine in which you application is deployed.
Returns a threading adapter:
<cfset threadingAdapter =
CreateObject("component", "MachII.util.Utils").init().creatThreadingAdapter() />
You must check whether or not threading is available by calling the allowThreading
method in the threading adapter.
<cfcomponent>
<!---
PROPERTIES
--->
<cfset variables.threadingAdapter =
CreateObject("component", "MachII.util.Utils").init().createThreadingAdapter() />
<!---
INITIALIZATION / CONFIGURATION
--->
<cffunction name="init" access="public" returntype="SomethingCool" output="false">
<cfreturn this />
</cffunction>
<!---
PUBLIC METHODS
--->
<cffunction name="readUrls" access="public" returntype="struct" output="false">
<cfargument name="urls" type="struct" required="true"
hint="An struct of urls to read."/>
<cfset var results = StructNew() />
<cfset var threadIds = "" />
<cfset var threadResults = StructNew() />
<cfset var parameters = StructNew() />
<cfset var key = "" />
<!--- Check if multiple threads should be be used --->
<cfif threadingAdapter.allowThreading()>
<!--- "Run" returns a thread id which we'll need to join the thread together --->
<cfloop collection="#arguments.urls" item="key'>
<cfset parameters.url = arguments.urls[key] />
<cfset threadIds = ListAppend(threadIds, variables.threadingAdapter.run(this, "readUrl" parameters) />
</cfloop>
<!--- Join the threads and grab the results to return --->
<cfset threadResults = variables.threadingAdapter.join(threadIds) />
<!--- Check for errors --->
<cfif ArrayLen(threadResults.errors)>
<cfthrow message="An error occurred during reading the urls" />
</cfif>
<cfloop collection="#threadResults#" item="key">
<cfset results[key] = threadResults[key].resultData />
</cfloop>
<cfelse>
<!--- No threading available so run in serial --->
<cfloop collection="#arguments.urls" item="key'>
<cfset results[key] = readUrl(arguments.urls[key]) />
</cfloop>
</cfif>
<cfreturn results />
</cffunction>
<cffunction name="readUrl" access="public" returntype="string" output="false">
<cfargument name="url type="string" required="true" />
<cfset var result = "" />
<cfhttp url="#arguments.url#" result="result" />
<cfreturn result />
</cffunction>
</cfcomponent>
The join
method returns a struct of data that you can use to get returned data and check if any threads encountered any errors or exceptions. Exceptions that occur in a thread do not stop the execute of the parent page that executed the thread, so it is the responsibility of the parent thread to check on the status of a thread and determine what exception handling should be performed.
A threadResults
will always have a key named threadResults.errors
that contains an array of "terminated" threads in which encountered an un-handled exception during the course of execution. Checking the length of the array in a logic block via ArrayLen(threadResults.errors)
is sufficient to check whether any threads encounter an exception. A length of 0
indicates that all threads were joined successfully.
In addition the errors
array, the thread results will also contain a key (named after the thread name) for each thread that was joined with the data that was returned by that callback. Each thread key has a result
key which is boolean that indicates whether or not the callback returned data and a key named resultData
which would contain the returned data. For example, if you joined three threads with the names of a
, b
and c
which all were successfully completed, your thread results would look like:
threadResults.errors = [zero length array]
threadResults.a.result = true
threadResults.a.resultData = [some complex struct]
threadResults.b.result = true
threadResults.b.resultData = [a CFC instance]
threadResults.c.result = false
threadResults.c.resultData = [zero length string]
As you can see above, threads a
and b
both returned resultData
from the invocation of their methods. The boolean flag of result
is true for these threads and the data is available for use under the resultData
key. Thread c
returned void
and thus did not return any data in which the result
key is indicating with false. Any thread that does not return any result data will have a zero length string in the resultData
key. You should check the result
boolean flag to see if the callback returned any data and not assume that a zero length string indicates that no data was returned because a zero length string could be a valid result from the callback method.
If a thread encountered an exception during the processing of the thread, the thread id would be listed an an array element in the errors
array. Going back to our previous example, if threads b
and c
encountered an exception; your thread results would look like:
threadResults.errors = {c,b}
threadResults.a.result = true
threadResults.a.resultData = [some complex struct]
threadResults.b.error = [original cfcatch]
threadResults.c.error = [original cfcatch]
As you can see above, both of the thread ids that encountered an exception (b
and c
) are elements in the error
array. Instead of a result
and resultData
keys for the threads, you have an error
key which contains the original cfcatch that occurred in the thread. It is your responsibility to handle that exception which you could throw an error, do additional logic or just ignore the exception.