Deeply Nested CFThread Tags Are Permitted In Lucee 5.3.2.77
When Adobe ColdFusion introduced the CFThread
tag for asynchronous processing, they decided to prevent developers from nested it. That is, they explicitly prevent one CFThread
tag from spawning a child CFThread
tag. The rationale (as best I can remember) being that a developer could accidentally spawn an excessive number of threads and exhaust the server's thread pool. Whether or not this rationale is, well, rational, I am excited to see that this limitation does not appear to apply to Lucee. In fact, I can deeply nest CFThread
tags in Lucee 5.3.2.77.
To see this in action, I'm going to spawn a series of nested CFThread
tags. And then, block and wait for each nested CFThread
tag to complete so that I can aggregate a series of nested results:
<cfscript>
thread name = "a" {
// NOTE: Adobe ColdFusion 2018 throws error: "Child threads are not supported."
thread name = "b" {
thread name = "c" {
thread name = "d" {
thread name = "e" {
thread.result = "from-e";
}
threadJoin( "e" );
thread.result = "from-d (#cfthread.e.result#)";
}
threadJoin( "d" );
thread.result = "from-c (#cfthread.d.result#)";
}
threadJoin( "c" );
thread.result = "from-b (#cfthread.c.result#)";
}
threadJoin( "b" );
thread.result = "from-a (#cfthread.b.result#)";
}
threadJoin();
echo( "Result: #cfthread.a.result#" );
</cfscript>
As you can see, each CFThread
tag both spawns a child CFThread
and then assigns a .result
property based on the given context and the results of the child CFThread
.
Now, if we were to run this CFML code in Adobe ColdFusion 2018 (syntax differences aside), we'd get the following error:
Thread B cannot be created. Child threads are not supported.
However, when we run this ColdFusion code in Lucee CFML 5.3.2.77, we get the following output:
Result: from-a (from-b (from-c (from-d (from-e))))
As you can see, Lucee has absolutely no problem spawning and joining deeply-nested CFThread
tags!
This is actually a really exciting feature of the Lucee language because it is more forgiving of some architectural choices. I know for a fact that I have processes in my ColdFusion application that I would love to put inside a CFThread
tag; but, haven't been able to in the past because some really deep method call somewhere also uses a CFThread
tag and breaks in Adobe ColdFusion. With Lucee, however, I can now make more processes asynchronous without having to worry about deeply-nested CFThread
tags. This also means that adding a CFThread
tag deeply within a Lucee application, will never accidentally break a consuming context! Outstanding work!
Want to use code from this post? Check out the license.
Reader Comments
One thing you might also like is the implementation of the each() functions for structs, arrays and queries. When you call the each function, you pass on a closure to handle with the individual element. If you now want to work on those elements in parallel, you just add two arguments to the each() function. First one will say whether or not to execute the closure in parallel and the second one, how many simultaneous threads you want to start. Here's an example:
As you may see in the second result, the order has changed. This is just because the threads finish at different times.
Hope you like it :)
@Gert, Hi Gert/Ben.
RE: each()
What happens if you leave out the 'thread number' parameter? Does it attempt to run all of them in parallel? And, if you have 8 items in the array and you add 4 as the second argument, does the process add 2 items to each thread?
@Charles,
the default is 10. But you can put in there any number.
If you have 8 elements in the array and you pass 4 as the third argument, there will be 4 threads running in parallel until all 8 are handled off.
So for example say you cfhttp a website and one of them takes 30s to answer, the others only 3s, then three threads will handle the 7 array elements while one is blocked with the 30s cfhttp. Once ALL have finished, the code after the .each() will be executed. Just like a thread action="join".
One other thing. Lucee only needs the thread name if you need to join named threads. Otherwise you can open threads as you see fit and then just use thread action="join" with no other attribute.
@Gert,
The parallel thread stuff has been really interesting to play with. I actually have an experiment in production in which 50% of requests to a particular workflow are using Struct-based parallel iteration around database access.
The concept is really exciting; but, I am discovering that the problem is a lot more nuanced. I'm tracking the Request Duration of the page in New Relic; and, what I'm seeing is that the overall duration of the request is more-or-less the same whether or not I am using parallel iteration or blocking calls to the database.
I suspect that the small cost of having to spawn and join threads is outweighing the on-average small code of the database calls?
Or perhaps there is one large bottleneck on the page, and it will always be the limiting factor. For example, I am seeing that generating S3-signed-urls takes like 40% of the processing time. So, even when running that in parallel, I still have to block and wait for it to finish.
Still playing around this stuff and trying to understand where the sweet spot is.
Are
cfthread
, parallel-iteration, andrunAsync()
all using the same mechanics under the hood (more or less)? Or are they suited for different types of processing?@Gert,
Thanks for this.