Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with:

Spawning Nested ColdFusion Threads With Synchronous Fallbacks

By Ben Nadel on
Tags: ColdFusion

Super quick post on ColdFusion threads since I have so little time these days. A couple of weeks ago, I blogged about separating your threading logic from your business logic. In addition the points outlined in that post, I've found that this approach has another excellent and unexpected benefit - it makes it easy to spawn threads even if you are unsure as to the threading nature of the calling context.

In ColdFusion, you can easily spawn asynchronous threads using the CFThread tag. You cannot, however, spawn one thread from inside of another (ColdFusion consciously added that limit to prevent people from shooting themselves in the foot). This makes it somewhat problematic when you want to encapsulate threading logic.

What I have found, though, is that if you separate your synchronous methods from your asynchronous methods, it positions you perfectly to spawn asynchronous code with a synchronous fallback. By that, I mean that your encapsulated code can try, by default, to run logic asynchronously; however, if accidentally-nested CFThreads raises an error, your encapsulated code can then catch that error and fallback to running the logic synchronously (inside the existing CFThread context).

To demonstrate this, I put together a Logging component that logs messages to a text file. By default, the logging method will try to execute the logging asynchronously; however, if it fails to do so, it will fallback to a synchronous execution.

  • <cfscript>
  • component
  • output = false
  • hint = "I log message to the given text file."
  • {
  •  
  • // I initialize the logger to log to the given file.
  • public any function init( required string aFilePath ) {
  •  
  • filepath = aFilePath;
  •  
  • return( this );
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I log the given message.
  • public void function logMessage( required string newMessage ) {
  •  
  • // ColdFUsion has a limit in that threads cannot spawn other
  • // threads. As such, if we try to run this code asynchronously,
  • // it's possible that it will fail, if the calling context is
  • // already inside of a thread. As such, we can TRY to call the
  • // code asynchronously; and, if it fails due to a nested-thread
  • // error (the only possible point of failure), we can fall back
  • // to executing the logging synchronously.
  • try {
  •  
  • logMessageAsync( newMessage );
  •  
  • } catch ( any error ) {
  •  
  • logMessageSync( "... Asynchronous logging failed." );
  • logMessageSync( newMessage );
  •  
  • }
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I execute the logging ASYNCHRONOUSLY.
  • private void function logMessageAsync( required string newMessage ) {
  •  
  • thread
  • name = "Logger.logMessageAsync.#createUUID()#"
  • action = "run"
  • newmessage = newMessage
  • {
  •  
  • logMessageSync( newMessage );
  •  
  • }
  •  
  • }
  •  
  •  
  • // I execute the logging SYNCHRONOUSLY.
  • private void function logMessageSync( required string newMessage ) {
  •  
  • lock
  • name = filepath
  • type = "exclusive"
  • timeout = 5
  • {
  •  
  • var logFile = fileOpen( filepath, "append", "utf-8" );
  •  
  • fileWrite( logFile, ( newMessage & chr( 10 ) ) );
  •  
  • fileClose( logFile );
  •  
  • } // END: Lock.
  •  
  • }
  •  
  • }
  • </cfscript>

The nice thing about this approach is that the Try-Catch is not mysterious. At first glance, you may worry that this setup could cause duplicated logging, depending on what the error is; but, it can't. Since the asynchronous method does nothing more than spawn a thread, the threading mechanism is the only possible source of error; any error inside of the spawned CFThread will fail to bubble up to the Try-Catch block.

To see this in action, here's a quick test script that logs messages inside and outside of ColdFusion CFThread tags:

  • <cfscript>
  •  
  • // Create our logger.
  • logger = new Logger( expandPath( "./log.txt" ) );
  •  
  •  
  • // Now, let's try to log serval message from both the currently
  • // executing page as well as from asynchronous threads.
  •  
  • logger.logMessage( "In parent page 1." );
  •  
  • thread
  • name = "async1"
  • action = "run"
  • {
  •  
  • logger.logMessage ( "In thread 1." );
  •  
  • }
  •  
  • thread
  • name = "async2"
  • action = "run"
  • {
  •  
  • logger.logMessage ( "In thread 2." );
  •  
  • }
  •  
  • logger.logMessage( "In parent page 2." );
  •  
  • </cfscript>

When we run the above code, we get the following log output:

In parent page 1.
... Asynchronous logging failed.
... Asynchronous logging failed.
In parent page 2.
In thread 1.
In thread 2.

As you can see, all four messages were logged successfully; however, the two messages logged inside of the top-level CFThread tag caused the encapsulated asynchronous logging to fail (due to nested threading).

Often times, when you deal with I/O bound tasks like writing to the file system or communicating with a 3rd-party API, you don't necessarily need to wait for an action to complete. In such cases, running said actions inside of a CFThread makes a lot of sense. However, when you are unsure as to whether spawning a thread is "safe", separating the synchronous methods from the asynchronous methods can make for an optimistic plan with safe fallbacks.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Thanks for this Ben - I was just wondering how to handle this the other day. As it turns out I've had to pass in to the function whether or not it should run in a threaded manner, but next time I will use your method. :-)

Reply to this Comment

@Mark,

Ahhh, super awesome. I had no idea! Probably more light-weight than handling an exception. Thanks!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.