Skip to content

Using the Threading Adapter Package

thofrey edited this page Apr 1, 2014 · 4 revisions

Table of Contents

  1. Mach-II and Multi-Threading
  2. Supported CFML Servers
  3. Implementation of Threading Adapters
  4. Obtaining a Threading Adapter
  5. Using a Threading Adapter

Mach-II and Multi-Threading

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)

Supported CFML Servers

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.

Implementation of Threading Adapters

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)").

Obtaining a Threading Adapter

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() />

Using a Threading Adapter

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>

Struct Returned by Join() Method

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.

Back to FAQs

Clone this wiki locally