Understanding ReadOnly And Exclusive Named Locks In Lucee CFML 5.3.5.92
The other weekend, as I was reading through the Learn Modern ColdFusion <CFML> in 100 Minutes book by Ortus Solutions, something about the way in which they described the CFLock
tag really clicked for me. Historically, my understanding of the different types of locking has been fairly poor. As such, I tend to just lean on exclusive
name locks all the time. But, those Ortus chaps really broke down my mental barrier; and I think I get it! So, I wanted to see if I could create a ColdFusion locking demo that would help me convince myself that I finally understand the interplay between ReadOnly
and Exclusive
named locks in Lucee CFML 5.3.5.92.
When you lock (often called "synchronize") access to shared resources in ColdFusion, you have the option to tell the CFML runtime that the lock is either Exclusive
or ReadOnly
. ReadOnly
locks, as the name implies, are for code that reads but does not modify the shared resource. Exclusive
locks, on the other hand, are for when you need to modify the shared resource.
On their own, each type of lock makes abstract sense. But, I've always been a bit fuzzy on what actually happens when the two types of locks start to compete for a shared resource. Here are some facts:
Two
ReadOnly
locks can access the same code at the same time.A
ReadOnly
lock and anExclusive
lock cannot access the same code at the same time.If a
ReadOnly
lock is active, anExclusive
lock will block and wait for theReadOnly
lock to be released before theExclusive
assumes exclusive control over the code.If an
Exclusive
lock is active, aReadOnly
lock will block and wait for theExclusive
lock to be released before theReadOnly
assumes read-only control over the code.
CAUTION: Just because two
ReadOnly
locks can access the same shared resource at the same time, it doesn't necessarily mean that the given shared resource is inherently "thread safe". For example, having two threads trying to iterate over a shared Array can lead to deadlocks. As such, you still have to exercise proper thread hygiene even when you have synchronization semantics in place.
To see these ColdFusion CFLock
rules play out in action, I've created two CFML pages: one that reads a counter; and, one that increments a counter. The one that reads the counter uses a ReadOnly
lock; and, the one that increments the counter uses an Exclusive
lock.
Both of these CFML pages can run at two different speeds: Fast and Slow. The Slow speed sleeps for 3-seconds before refreshing the page and trying to reacquire the lock.
Here's the ReadOnly
CFML page:
<cfscript>
// Set defaults for URL parameters.
param name = "url.speed" type = "string" default = "fast";
// Let's flush some content before we try to enter the lock so that the browser can
// reset the page content (helps demonstrate when the lock is blocking).
echo( "<p> Read using: #url.speed# </p>" );
flush interval = 1;
lock
name = "CounterLock"
type = "readonly"
timeout = 60
throwOnTimeout = true
{
// For slow speed access, sleep inside the LOCK for a few seconds.
if ( url.speed == "slow" ) {
sleep( 3 * 1000 );
}
// Echo state to browser.
echo( application.counter );
echo( " @ " );
echo( now().timeFormat( "mm:ss.l" ) );
}
</cfscript>
<script type="text/javascript">
// Refresh the browser window (iframe).
setTimeout(
() => {
window.location.reload();
},
100
);
</script>
As you can see, this ColdFusion code is attempting to acquire ReadOnly
access to a lock with name CounterLock
. Then, within the lock, it reads the value of application.counter
.
Now, on the "write" side, we have this ColdFusion page:
<cfscript>
// Set defaults for URL parameters.
param name = "url.speed" type = "string" default = "fast";
// Let's flush some content before we try to enter the lock so that the browser can
// reset the page content (helps demonstrate when the lock is blocking).
echo( "<p> Write using: #url.speed# </p>" );
flush interval = 1;
lock
name = "CounterLock"
type = "exclusive"
timeout = 60
throwOnTimeout = true
{
// For slow speed access, sleep inside the LOCK for a few seconds.
if ( url.speed == "slow" ) {
sleep( 3 * 1000 );
}
// INCREMENT and echo state to browser.
echo( ++application.counter );
echo( " @ " );
echo( now().timeFormat( "mm:ss.l" ) );
}
</cfscript>
<script type="text/javascript">
// Refresh the browser window (iframe).
setTimeout(
() => {
window.location.reload();
},
100
);
</script>
This ColdFusion code is basically the same; only, it's trying to acquire an Exclusive
lock named CounterLock
, from within which it will pre-increment the application.counter
value.
And now that we have two ColdFusion pages trying to compete for the same shared-access, let's pit them against each other. To do this, I've created a page that renders three iframe
elements: 2 ReadOnly
pages, and 1 Exclusive
page. We can turn each of these pages on and off; and, adjust the processing speed:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
Exploring Read / Write Named Locks in Lucee CFML 5.3.5.92
</title>
<link rel="stylesheet" type="text/css" href="./demo.css" />
</head>
<body>
<div class="panels">
<div class="panel">
<div class="actions">
<a href="./read.cfm?speed=fast" target="read1">Read Fast</a>
<a href="./read.cfm?speed=slow" target="read1">Slow</a>
<a href="about:blank" target="read1">x</a>
</div>
<iframe name="read1" src="about:blank" class="iframe"></iframe>
</div>
<div class="panel">
<div class="actions">
<a href="./read.cfm?speed=fast" target="read2">Read Fast</a>
<a href="./read.cfm?speed=slow" target="read2">Slow</a>
<a href="about:blank" target="read2">x</a>
</div>
<iframe name="read2" src="about:blank" class="iframe"></iframe>
</div>
<div class="panel">
<div class="actions">
<a href="./write.cfm?speed=fast" target="write1">Write Fast</a>
<a href="./write.cfm?speed=slow" target="write1">Slow</a>
<a href="about:blank" target="write1">x</a>
</div>
<iframe name="write1" src="about:blank" class="iframe"></iframe>
</div>
</div>
</body>
</html>
Because locking is all about time-based contention, understanding how all of this plays-out will be easier if you watch the video. That said, I've tried to put together some animated GIFs to illustrate the interplay.
ReadOnly
Locks: Fast + Slow
Two If we run the demo page with just the two ReadOnly
locks, we can see that the "fast" one continues to execute quickly while the "slow" one sleeps within the lock:
As you can see, the fast ReadOnly
lock doesn't care that another ReadOnly
lock is hanging - it continues to access and re-access the named lock without any blocking.
ReadOnly
And One Exclusive
Lock: Fast + Fast
One If we run the demo page with one ReadOnly
lock and one Exclusive
lock, both running "fast", they will be competing for access; however, they will both be running fast enough such that the competition is mostly unnoticeable:
As you can see, both the ReadOnly
lock and the Exclusive
lock are refreshing at a healthy clip.
ReadOnly
And One Exclusive
Lock: Slow Read + Fast Write
One Now, let's take a look at what happens when the two named locks really do have to compete for access. In this demo, we're going to write fast but read slow:
This is where it starts to get interesting! As you can see, when we introduce a slow ReadOnly
lock, it prevents the Exclusive
lock from entering the shared code. The Exclusive
lock has to block and wait for the ReadOnly
lock to be released.
ReadOnly
And One Exclusive
Lock: Fast Read + Slow Write
One Still using a ReadOnly
and an Exclusive
lock again, this time we're going to reverse the speed: the read will be fast, but the write will be slow:
As you can see, when the slow Exclusive
lock is introduced, the fast ReadOnly
lock has to block and wait for the Exclusive
lock to be released before the ReadOnly
lock can acquire access rights.
Again, this is probably easier to follow in the video; but, hopefully these GIFs have helped to illustrate the interplay between ReadOnly
and Exclusive
locks. It seems silly to me that it took me so long to build a more concrete mental model for locking in ColdFusion; but, it's better late than never.
Want to use code from this post? Check out the license.
Reader Comments
This is a really useful exploration of a topic that I believe is badly understood.
I never found out after many years of research whether SESSION variables need to be locked, so I have always played safe and locked SESSION variables, because I once read an article about an edge case that can cause race conditions when trying to read/write SESSION variables from within different frames [frameset]. Now, I know very few people use framesets anymore, but, ironically, I am working on a legacy application, at the moment, that uses a frameset.
@Charles,
Locking is pretty confusing. Especially because, in ColdFusion / Lucee the
Struct
object is documented (somewhere) thread-safe / synchronized, but theArray
is not.StructNew
Documentation says:But, even with that, you still occasionally find a
java.util.ConcurrentModificationException
error being thrown somewhere. And, of course, just because a Struct is thread-safe, it doesn't mean that you won't run into race-conditions when read/writing to values stored in a scope -- it just means things won't "blow up" :DSo, all to say, I agree -- even understanding when to lock
Session
access is confusing. I am happy that, at the very least, I have a better sense now of howReadOnly
andExclusive
interact. This is probably something I should have learned years ago.