Using A Closure To Encapsulate CFThread Execution And Error Handling In ColdFusion

Cyberdime
Published: November 17, 2022

In ColdFusion, I’m a huge fan of using Closures to create a clean separation of concerns between the business logic and the low-level mechanics required to execute a given algorithm. I’ve used closures for things like managing temp directories, pulling resources out of a connection pool, and implementing distributed locks. And, when it comes to executing CFThread tags, I almost always split my asynchronous code from my business logic. However, it wasn’t until the other day that it occurred to me that I could probably use Closures to simplify the execution of asynchronous CFThread tags in ColdFusion.

The basic idea behind using a ColdFusion Closure to create a separation of concerns is that you pass the closure out of scope and into another function. This other function then handles the low-level, non-business-logic code and invokes the passed-in closure as part of the execution. The pattern looks something like this (pseudo-code):

<cfscript>
	runMyClosure(
		() => {
			// The business logic here inside my closure.
		}
	);
	// ---
	// The low-level mechanics are handled here in this other method.
	// ---
	private void function runMyClosure( required function operator ) {
		// Low-level mechanics here.
		// Low-level mechanics here.
		operator(); // Execute the passed-in operator.
		// Low-level mechanics here.
		// Low-level mechanics here.
	}
</cfscript>

Normally, when using this technique, I’m executing synchronous code, like file I/O; and, the closure is really just there to separate the logic from the mechanics. A decade ago, when ColdFusion 10 came out, I did a lot of experimenting with passing closures into a CFThread tag; however, until now, it never occurred to me that I might use closures to completely encapsulate the CFThraed tag logic itself.

To explore this idea, I’ve created a mock ColdFusion component for creating users. It has one public method, createUser(), that needs to perform some asynchronous logging as part of its workflow. This logging is going to be defined inside a ColdFusion closure which will be invoked inside an asynchronous CFThread tag. However, the CFThread mechanics will be encapsulated inside their own method, runSafelyInThread().

component
	output = false
	hint = "I provide service methods for users."
	{
	/**
	* I create a user record with the given credentials.
	*/
	public numeric function createUser(
		required string email,
		required string password
		) {
		var user = {
			id: randRange( 1, 999999 ),
			email: email,
			password: password,
			createdAt: now()
		};
		// The given closure will be executed inside a CFThread body. Any errors that
		// occur during the spawning / execution will be safely caught and logged.
		runSafelyInThread(
			() => {
				cfdump( var = "User [#user.id#] created.", output = "console" );
			}
		);
		return( user.id );
	}
	// ---
	// PRIVATE METHODS.
	// ---
	/**
	* I encapsulate the asynchronous spawning and execution of the given operator.
	* 
	* When it comes to running code asynchronously inside a CFThread tag, there are two
	* things that can go wrong: first, the JVM might be out of threads (very rare) and
	* can't actually spawn a new thread for you to run. And second, once the thread has
	* been spawned and is running, the code executing inside the CFThread body might throw
	* an error that is lost into the ether. To encapsulate the error handling, all the
	* business logic will be contained inside the passed-in Operator; and, all of the low-
	* level mechanics of safely running a thread will be handled inside this method.
	* --
	* NOTE: There is also the possibility of "sudden thread death"; but, I don't believe
	* we can handle that sort of error here.
	*/
	private void function runSafelyInThread( required function operator ) {
		try {
			thread
				name = "UserService.runSafelyInThread.#createUuid()#"
				action = "run"
				operator = operator
				{
				try {
					operator();
				} catch ( any error ) {
					logError( error, "Operator error inside runSafelyInThread operator." );
				}
			}
		// There's a tiny chance that the JVM is out of threads due to high load. Catch
		// those errors so that the parent request doesn't blow up.
		} catch ( any error ) {
			logError( error, "CFThread could not be spawned in runSafelyInThread." );
		}
	}

	/**
	* I log the given error and message to the console.
	*/
	private void function logError(
		required any error,
		required string message
		) {
		cfdump( var = message, output = "console" );
		cfdump( var = error, output = "console" );
	}
}

As you can see, the runSafelyInThread() method takes care of error handling, both inside and outside of the CFThread tag. And, this method handles the execution of the passed-in ColdFusion closure once the the asynchronous thread has been successfully spawned. This allows the passed-in closure to worry-not about the threading and focus solely on the logging.

Notice also that the closure is referencing the user object with is part of the local scope of the lexical context!

To try this out, I created a simple test page that creates a single user:

<cfscript>
	id = new UserService()
		.createUser( "skroob@spaceballs.movie", "12345" )
	;
	writeOutput( "Woot woot, #id# created." );
</cfscript>

If we run this ColdFusion code – in the latest Adobe ColdFusion or Lucee CFML – and we look at the logs, we get the following output:

[INFO] string User [990685] created.

This is the logging that was generated by our ColdFusion closure that was passed into the CFThread tag.

Not only did the ColdFusion closure maintain the bindings to its lexical scope after it was passed into the CFThread tag, we were able to create a clean separation of concerns between the business logic (the logging) and the low-level mechanics (the thread spawning). It’s kind of astonishing how easy ColdFusion makes it to run code asynchronously.

when it blocks to how it handles (or rather doesn’t handle) errors. It’s very possible that in the years since I’ve looked at the initial implementation of runAsync(), all of these bumps have been smoothed out. However, for the time being, I find the CFThread tag so much easier to reason about.

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

Source: www.bennadel.com