Is it safe to use named locks (e.g. name of the key) to synchronize read/write access to specific keys of scopes & structs? I've been doing it for a while and never ran into concurrency issues, but I'm writing a server-level cache and I want to make sure name-based locking is safe with the server
scope. I'm afraid the underlying implementation may not be thread-safe and that concurrent access on different keys could cause issues.

- 42,889
- 6
- 74
- 90
-
What exactly are you "afraid" of? Named locks only synchronize access for the same named locks while a scope lock locks the whole scope/struct, regardless which thread attempts to access it. If you need a (serverwide) cache, you might be better off using `cfcache` (Ehcache) with the `region` attribute. – Alex Aug 28 '19 at 17:19
-
@Alex Well all our code using the `server` scope uses name locks and keys never overlap from one name lock to another, still I wonder if setting 2 unrelated properties in a CF scope/struct can cause concurrency issues (e.g. internal calculation of the entries size, etc.). Thanks for pointing out cfcache, I'll have a look at it, but the same locking concerns also occur in many different scenarios. – plalx Aug 28 '19 at 18:31
-
@plalx - The underlying implementation of structures isn't thread safe. Since named locks don't lock the whole object or method, like `synchronized`, I'd imagine whatever thread safety issues apply to HashMaps apply here as well. – SOS Aug 28 '19 at 21:40
-
@Alex - Where in the docs does it say structures are thread safe? Last I checked, structures used HashMap internally. The caching docs mention ConcurrentHashMap, but I haven't found anything about structures. – SOS Aug 29 '19 at 00:08
1 Answers
Locks in ColdFusion via cflock
are just semaphores. They control which threads can access the code at the same time (concurrently). These locks do not impact Java's intrinsic locks or synchronized methods/statements. So cflock
doesn't provide thread-safety per se.
User Ageax showed that CF structs do not use ConcurrentHashMap (see comments), so you have to explicitly use them: createObject("java", "java.util.concurrent.ConcurrentHashMap").init()
Note that ConcurrentHashMap is type and case sensitive (while the regular struct is not).
The good news
Structs in ColdFusion are thread-safe by nature. Here is an example that compares the unsafe Java HashMap and the safe ColdFusion Struct:
First run with:
<cfset s = createObject("java", "java.util.HashMap").init()>
Second run with:
<cfset s = structNew()>
<cfset s.put("A", 1)>
<cfset s.put("B", 2)>
<cfthread name="interrupter">
<cfset s.put("C", 3)>
</cfthread>
<cfoutput>
<cfloop collection="#s#" item="key">
#s[key]#,
<cfset sleep(1000)>
</cfloop>
#structKeyList(s)#
</cfoutput>
The HashMap will throw ConcurrentModificationException
, because the map was accessed by the main thread while being modified by the "interrupter" thread.
The Struct however, will not throw an exception. It will simply return 1,2,A,B,C
, because the iterator blocks access, i.e. causes the write operation by the "interrupter" thread to be postponed. After the iterator is done (end of loop), it releases the lock and the struct will be modified. This is why structKeyList()
will immediately return the freshly written key-value-pair "C": 3
as well.
You can read more about the implementation details of concurrent map access in the official Java docs for java.util.concurrent.ConcurrentHashMap. But keep in mind that ColdFusion probably uses a derived version of ConcurrentHashMap.

- 7,743
- 1
- 18
- 38
-
Alex, usually you're right on the money, but .. I have to disagree on this one :-). That's not sufficient evidence to prove structures use a ConcurrentHashMap internally. Create a thread that adds keys within a loop, and iterates through the structure, and it'll throw a java.util.ConcurrentModificationException with enough iterations. – SOS Aug 29 '19 at 01:21
-
@Ageax No it doesn't throw an exception. It does neither in CF10 nor in CF2018. And I'm pretty sure YOU KNOW how to find out what Adobe is using internally. This is Java, not machine code, if you know what I mean. – Alex Aug 29 '19 at 11:10
-
Yes, my tests did throw an exception in CF2016. I haven't had time to check other versions, so perhaps they changed something? The exception, and a .. "standard" .. test, shows CF2016 uses HashMap for some things, ie. `writeDump( structNew().values() );`. If I get time later, I'll try it on CF10 and CF2018 and post back with the results. – SOS Aug 29 '19 at 13:20
-
Here's an example that shows both CF10 and 2016 throw an exception, but not 2018. For struct.values() CF10/2016 uses java.util.HashMap$Values https://cffiddle.org/app/file?filepath=9804c931-2906-4778-97f8-049d39a81fea/d658b839-b008-4874-850f-df4e7a53aaf2/34a40926-fe81-4b4d-8630-ae94a9b4fe78.cfm – SOS Aug 29 '19 at 15:44
-
@Alex Currenlty running in CF11 and as far as I could see they extended `AbstractMap` and were not using `ConcurrentHashMap`. Iterating while modifying indeed throws a `ConcurrentModificationException`. This behavior seems to have changed in 2018, where I guess they based their implementation on `ConcurrentHashMap` (couldn't confirm, don't have 2018 yet). Hopefully we aren't iterating values and name-based locks seems fine in that scenario. Can't use `ConcurrentHashMap` because CF closures aren't casted to `Function` when calling `map.computeIfAbsent`. I'd need to create a java-cf adapter. – plalx Aug 29 '19 at 21:33
-
@plalx - Iterating is easier to reproduce, but it's not the only potential issue. [Resizing can cause issues too](https://stackoverflow.com/questions/2688629/is-a-hashmap-thread-safe-for-different-keys). – SOS Aug 29 '19 at 22:22
-
@Ageax Yeah I guess I'll have to use a server lock to write the root key in the server scope which then will contain a ConcurrentHashmap and I'll use key-based named locks to emulate a computeIfAbsent behavior for now or explore if I can use cfcache instead. – plalx Aug 30 '19 at 04:42
-
@Ageax I've tried (in CF11) a combination of concurrent (100k in each tread) add/remove/retrieve (not iterating) with scopes & structs (with key-based lock) and in both scenarios (all threads operating on same keys & all threads operating on a different key set) I haven't encountered any data integrity issues and each key was set once in a computeIfAbsent operation. I guess that we can conclude that as long as you don't iterate over they keys using named locks per key are safe for single-key read/writes? – plalx Aug 30 '19 at 13:45
-
Just tried with 1 million items in each thread and no integrity issues either: no errors during concurrent access and the shared struct contains the expected set of keys once all threads return. – plalx Aug 30 '19 at 13:55
-
-
@Ageax I tested with the server scope and structs. I use named locks to implement an atomic compare and swap like computeIfAbsent. – plalx Aug 31 '19 at 21:14
-
@plalx - Just because you didn't see the issue in a single round of testing (I didn't either, btw) I still wouldn't conclude using a structure/HashMap is thread safe. The issue of resizing clashes has been reported many times, such as in the link above or [this one](https://stackoverflow.com/questions/13695832/explain-the-timing-causing-hashmap-put-to-execute-an-infinite-loop). Using named locks wouldn't prevent that scenario. Unfortunately, unlike iterating, it's much harder to reproduce on demand. – SOS Aug 31 '19 at 22:00
-
As an aside, regarding "*CF closures aren't casted to Function when calling map.computeIfAbsent*", you can implement java interfaces with a CFC. However, if Ehcache already uses ConcurrentHashMap, that sounds like the simpler, more stable route to take. – SOS Aug 31 '19 at 22:08
-
@Ageax CF11 caching API is completely unsafe to implement a compare and swap (at least the default provider). I've even seen single thread inconsistencies where I couldn't read back what was just wrote to the cache when writing to new regions and many occurences where both threads would write the same value even with named locks per cache ID. I did not know I could implement java interfaces, I'll have a look, but it doesn't help b/c I need a shared memory scope to store the data and it seems none is threadsafe in CF. – plalx Sep 01 '19 at 02:05
-
What I did for now is use named locks for the server scope, but then store a CFC that wraps over a ConcurrentHashMap to reduce writes to the server scope. I guess I'd have to implement my own global cache in Java to be 100% safe. Do you know if CF 2018 offers stronger concurrency guarantees? Couldn't observe issues with structs even when iterating but they still seem to use a HashMap? Surprising how hard it is to find documentation, gotta decompile or use reflection to check the inner workings but haven't had the chance yet. – plalx Sep 01 '19 at 02:20
-
@plalx - Yes, if you wanted to implement the interface, you'd have to use it with a ConcurrentHashMap. I'm not sure about CF2018. Frustratingly there's scant information about the thread safety of cache or structures. Reflection showed structures do use a HashMap. Though I wasn't able to produce an exception while iterating either. It's possible they implemented some new handling - BUT - without Adobe's guarantee that it's thread safe - I wouldn't rely on that. – SOS Sep 02 '19 at 15:43