[Looking for Charlie's main web site?]

Understanding the "cost" of cflock, part 1

In a post today on his blog, Ben Nadel did an experiment "Looking At The Performance Overhead Of A Read-Only Lock". (He happened to do it in Lucee, but the concept applies equally to CF.)

And I wanted to offer some additional thoughts--first planning to offer them as a comment--because there's a lot behind the question and his observations. But as it got longer, I realized it was too long for a comment. Also, I didn't want people to think (in reading a comment on Ben's blog) that I was challenging Ben or questioning his understanding of the matter! Not at all. :-) Instead, I was just wanting to add more context, to help other readers, and based on my years of observing the community.

What I offer here is pretty much exactly what I wrote, but I have added headings, to help readers here:

Interesting thought experiment, Ben. If you'd been taking bets, I'd have put down good money that there'd be virtually NO impact in this scenario. The reason I say that may add some value to this post, whether one is using Lucee or ACF.

I find that many people do think (or fear) that cf locks do things which "may be expensive"--or conversely they give no attention and use them only because they vaguely recall they're "supposed to" in some situations.

What locks DO do

To clarify for some readers, a cflock does not "lock code" nor does it "lock scopes". Like you said, it can "essentially single-threads access to a given block of code". Nicely worded. :-)

A cf lock is a "gentlemen's agreement" (pardon my boomerism): when the cflock is hit, it causes cf to simply check, "is anyone else holding a contending i TYPE of this lock", of this NAME or SCOPE? That's really it.

If my TYPE is "read", and no one else holds an "exclusive" lock of the same name or scope, I can proceed, otherwise I wait. If my type is "exclusive" is and no one else holds a "read" OR "exclusive", I can proceed, otherwise I wait.

How lock timeouts get involved

And the "wait" is controlled by the TIMEOUT atrribute (in the tag) or argument (in script). That's why one is required.

Many misconstrue the TIMEOUT as having impact on "how long the thing in the lock can take", but it's not that at all. It's merely how long do I wait for another request (or thread) holding the same/contending lock to let it go.

And if that timeout is exceeded, then I'll throw an error... unless the optional THROWONTIMEOUT is set to "no". Please BEWARE using that. Many do without any idea of the implications. If you ignore the timeout, then the code in your lock will simply NOT BE RUN. Many folks find mysteriously that some variable is not set or some "code has not run", and this is why.

We have so little insight into these goings-on in a CF request/s

Sadly, cf gives us zero insight into any of this. I've long lamented that we should have at LEAST optional logging of when a lock timeout occurred, or was ignored this way, or simply how long we have WAITED for or HELD a lock.

To me, misuse/misunderstanding of locks is a cancer in much cf code, causing needless holdups or unexpected failures to run code. That's why I felt I should add this.

Some natural next questions

Of course, the next question some would ask would be "well, when should I or should I NOT use locks". And when should I use read or exclusive types? And others may ask, "I thought the SCOPE lock locked the Scope. You're saying it doesn't?" It does not. Finally, when might you use a NAME lock instead?

[Those are all fodder for a part 2 post. :-) And in the interest of time, I do want to get this out, as-is, as a part 1, thus this post. hope this part 1 helps some, while news of a part 2 may whet the whistle of others.]

Bottom line

But bottom line to Ben's original post, nope, there's no reason a lock ITSELF should "take time". It's just that check to say "does anyone hold a contending lock of the kind I want"? That alone is a trivial matter. Any delay due to the lock will be only if there IS contention FOR that lock.

So again, look for a part 2 to come. I welcome comments, whether here or on Ben's post--preferably only if such a comment there would be written without presuming people HAVE read this post.

For more content like this from Charlie Arehart: Need more help with problems?
  • If you may prefer direct help, rather than digging around here/elsewhere or via comments, he can help via his online consulting services
  • See that page for more on how he can help a) over the web, safely and securely, b) usually very quickly, c) teaching you along the way, and d) with satisfaction guaranteed
Comments
Charlie, really well said, and I appreciate the clarity of perspective. Locking -- much like Try/Catch -- was always a kind of "danger zone" topic, it seemed, when I was getting into programming. It was one of those things that was always described TO me as being slow, and perhaps a "necessarily evil". But, since I didn't really understand how locks worked when I was younger, or when I should use them, all this really did was leave me with a general sense of anxiety that using a Lock (or a Try/Catch block) was fraught with peril - abstract, hard to define peril.

It would also be great if the ColdFusion documentation could add more detail to what actually is supported in a "synchronized" Struct or Array. For example, the docs state that Structs and Arrays are always synchronized by default. But, I can recreate "concurrent modification exceptions" and "undefined values" by trying to mutate a struct/array that is currently being iterated-over by another thread. To be clear, I think those errors are _valid_ - but, without documentation that is more specific about which actions are / are not "thread safe", it leaves it as an exercise for the developer to run tests like this.
# Posted By Ben Nadel | 6/24/22 12:11 PM
Thanks for the kind regards, Ben. You're always so helpful in sharing your trepidations, which makes it easy for so many to relate to whatever challenges you may discuss. Then there's also just all the awesome info--and wonderfully formatted code!--that you always offer. You rock!

So about the matter of synchronized arrays, it's worth pointing out first that this IS something new (whether arrays may NOT be sync'ed), since CF2016. And it's indeed documented in at least the CFML Reference for arraynew (more below). Before I share it, note that there's no equivalent indication of any such option to control whether structs are implicitly sync'ed or not.

Back to arrays, of course many will not bother with an arraynew, when they can just use the [] nomenclature to create them implicitly. But what that does in creating an array is at least the default behavior of arraynew, so the below is worth noting. :-)

And to save folks going to dig through the docs page there (or for those who may not bother), I'll quote the main points below.

Note that the doc page clarifies how a new OPTIONAL argument was added, that allowed one to make such an array NOT synchronized/NOT thread safe--if they knew what they were doing. We can liken it to how folks using SQL may, for instance, use NOLOCK directives (but let's not go any further here on that!)

Here's the link to the arraynew docs, and the quote of that discussion there about this "isSynchronized" argument:

https://helpx.adobe....

--
"When false, creates an unsynchronized array. This parameter is new in the 2016 release of ColdFusion. When this parameter is true or no parameter is specified, the function returns a synchronized array.

The default value is true.

In ColdFusion 11, and previous versions, arrays were always thread-safe.

But thread safety comes at a cost of synchronizing the array object. In a synchronized array, two or more threads cannot access the array at the same time. The second thread has to wait until the first thread completes its job, resulting in significant performance deterioration.

In ColdFusion (2016 release), you can use an unsynchronized array and let multiple threads access the same array object simultaneously.

You can use unsynchronized arrays in situations where you need not worry about multiple threads accessing the same array. In other words, if you are defining an unsynchronized array in a thread safe object, like a UDF or a CF-Closure, you can use an unsynchronized array.

While a normal array is slower but thread safe, an unsynchronized array is faster by more than 90%."
--

Before leaving this comment, and for any who have read this far, note again that in my post I acknowledge that some will ask still other questions about when/why/when not to bother with locking. I'll address that more in part 2.
# Posted By Charlie Arehart | 6/24/22 12:32 PM
I don't think I've ever tried to make a non-synchronized Struct/Array, so I have no sense of when or if it changed for Structs. The Lucee docs do make some note about synchronized structs:

> Note, the type "synchronized" is no longer supported and will be ignored; all struct/scopes are "thread safe" since version 4.1.

I don't know if that was a brief, Lucee-specific feature that they rolled-back; or, if that was something they had for some ACF compatibility. But, in either case, Structs are always synced at this point.

But, even so, if you throw enough volume at a Struct, attempting to add/remove keys while also iterating over it, eventually you get a concurrent modification errors (at least in Lucee).

Looking forward to part 2 :D
# Posted By Ben Nadel | 6/24/22 12:45 PM
Thx.
# Posted By Charlie Arehart | 6/24/22 12:46 PM
Copyright ©2022 Charlie Arehart
Carehart Logo
BlogCFC was created by Raymond Camden. This blog is running version 5.005.
(Want to validate the html in this page?)

Managed Hosting Services provided by
Managed Dedicated Hosting