Question about cfthread, cffunction and threadsafety

Hi guys. I tried finding the answer to the following question myself, but I couldn’t find a clear, definitive answer.

If I define a cffunction and its code only uses the arguments passed into it (arguments-scope variables), and some working variables it creates using the “var” keyword (local-scope variables) and those working variables are made solely by transforming/formatting the argument values… is that cffunction threadsafe?

Meaning can I launch a bunch of simultaneous cfthreads that all call that cffunction, and have no data clashes or race conditions between different simultaneous invocations of the cffunction by different simutaneously-running cfthreads?

I’m 95% sure this IS indeed threadsafe, because each and every invocation of the cffunction should have its very own private version of the “arguments” scope variables and the “local” scope variables created for it, separate from those of any/all other running invocations of the cffunction, and the “local” scope (var keyword) working variables values’ will be constructed using only the arguments that were passed in… so overall, assuming the arguments and return values are passed by value not reference (meaning they’re all either primitive types or complex types wrapped in Duplicate()), then this should be a “shared nothing” situation, which is inherently threadsafe.

Can someone confirm? Thanks!

OS: Linux
Java Version: 11.0.11
Tomcat Version: 8.0.36
Lucee Version: 6.1.0.243

1 Like

Well, since nobody answered my question, I went ahead and wrote a test program in a single .cfm file to try to “almost-prove” it statistically.

It launches 100 simultaneous cfthreads, each of which makes 100 calls to the same cffunction which is defined at the end of the .cfm file, well outside the cfthread block.

The cffunction assigns randomly-chosen numbers to its internal local-scope variables (defined with “<cfset var name…”) and then uses random cfsleep delays between assignment and then checking to see if the value has wrongly changed (due to interference from other simutaneously-running invocations/threads of itself). Any wrongly-changed value gets written to a log file.

100 simultaneous threads each calling the same function 100 times on a quad-core machine results in lots of arbitrary orderings and timings, and then I ran the program over and over about a dozen times to ensure an overall massive amount of random variability in interthread timings.

Not a single wrongly-changed value was observed.

Thus while I can’t conclusively say the arrangement I describe in my original post above is threadsafe, I can say it with a certainty of (ungodly huge number)-to-1 against chance. Which is so close to proof that it basically is proof.

Hope that helps someone!

3 Likes

For what it’s worth, by default, everything is passed by reference. However, the cfargument tag has a “passby” attribute that allows you to control whether the argument is passed by value or by reference.

First of all, great test! Inspiring.

My answer to your original question is: yes, I expect the different function calls to be thread-safe. The reason is that each function is called, implicitly or explicitly, on an instance object. The instance object belongs to the caller. No two distinct callers share the same instance object.

This is the first I’ve heard about the passby attribute - thank you. How long has that been there?

Unknown to me, and not noted in the docs. Perhaps a developer would know?

You can easily enforce pass-by-value by simply wrapping something in the Duplicate() function. This makes a deep copy. I do this when necessary when calling a function with an argument, and when returning a value from a function.

Documented in Lucee: a passBy attribute for cfargument.