In my first year or so of developing ColdFusion applications, I didn't really understand how to use CFLock. That caused me a lot of headaches, and I have learned to use it properly since then. In the last year or so I have had several calls and emails from people who's sites aren't performing well under load where I was able to identify their problems were due to improper use of CFLock. It is important to use CFLock to protect the integrity of data that is shared across requests in your application. However, you need to make sure you do it right. Improper use of CFLock may not cause any noticeable problem in your development environment, but once your application starts to experience a significant amount of load, poor locking implementations can cause your application to grind to a halt.
A few examples of data that could be corrupted if not properly locked are:
- Shared scope variables (variables in the application, server, and session* scopes
- Verity collections
- Files that are manipulated via CFFile or the ColdFusion File functions
(*In versions 4.5 and 5 of ColdFusion there is a "Single Threaded Sessions" setting in ColdFusion administrator where you can force each browser session to only use one thread. Enabling this feature will negate the need for locking session scoped variables, although it may cause slowness if your application utilizes frames)
Another valuable use for CFLock is to ensure that sensitive processes in your application do not run concurrently. For example if you are building a shopping cart and at the end of the checkout process you use an API call to authorize a payment then save the payment result to your database. You may want to put a named lock to make sure that if a use accidentally clicks twice on the "Pay Now" button, they do no accidentally get double charged.
Basically, anytime you are in a situation where you are developing some code that could cause problems if two requests tried to do the same thing at the same time, you want to slap a CFLock around it.
The CFLock tag has 5 attributes:
- timeout - Required - Number of seconds for the request to wait to obtain a lock. If the lock is obtained within the specified time, the code in the body of the CFLock tag is processed. If the lock is not obtained within the specified time, the CFLock tag will react based on how you set the throwOnTimeout attribute value. If you set timeout="0", then the request will wait to obtain a lock until the request times out. If the "Timeout Requests after (seconds)" setting on the ColdFusion Administrator Settings page is not enabled, and you set timeout="0", the request will never timeout and will wait indefinitely to obtain the lock.
- name - Optional - The name of the lock. This attribute is used when you are locking anything other than one of the shared variable scopes. The name cannot be an empty string. Named locks permit synchronizing access to resources such as verity collections or files that your application reads and writes to. Lock names are global to a ColdFusion server, so they are shared among applications and user sessions, but not clustered servers. The Name and Scope attributes cannot be used together in the same CFLock.
- scope - Optional - The variable scope to be locked. This attribute is used when you need to lock the one of the following variable scopes:
- throwOnTimeout - Optional - Specifies whether or not to throw an error if the lock cannot be obtained within the timeout period. If you enter "Yes", an error will occur. If you enter "No" the request will skip the code within the body of the CFLock tag and continue processing past that point
- type - Optional - Specifies whether you want the lock to be "ReadOnly", or "Exclusive".
A "ReadOnly" CFLock will allow multiple requests with the same name/scope to process their code at the same time, as long as all the requests are using "ReadOnly" locks. You should use "ReadOnly" locks every where you are reading from (but not adding, updating or deleting) shared variable scopes, and everywhere you are using named locks around your CFSearch tags or around code that reads files which could be manipulated elsewhere in your code.
An "Exclusive" CFLock will wait until all currently running "ReadOnly" locks are released, then it will obtain a lock that will not allow any other CFLocks with the same name/scope to process until it has completed it's task and released the lock. You should use "Exclusive" locks to block access to the resource while you are adding, updating, deleting variables in the shared scope, and when you are performing CFIndex or CFCollection operations to your verity index, or when you are manipulating data in a file that could be read in other parts of your application.
Be very careful not to use too many locks in your application. As I alluded to earlier, using too many locks can really slow down an application under load. If you are going to be reading several application variables throughout a particular process, then you should write one CFLock to read all the variables you will need from the application scope at the beginning of the process, then save the data to the request scope or the local variables scope and end the lock. This way you don't have to lock the application scope 15 times throughout the process every time an application variable is needed, you can just get the data from the request or local variables scope instead.
One thing to keep in mind is if you are going to use CFLock to protect a data resource, you need to make sure you use CFLock every time you use that resource in your code. It doesn't do you much good to put CFLocks around your application scope variables in some places, but not in others.
You will also want to make sure that if there is any possible chance that an error could occur with the code that is in the body of the CFLock tag you are writing, that you use CFTry and CFCatch to handle the errors so that the lock always gets released. When an error occurs in code that is within a lock, the lock never releases, so none of the subsequent requests will be able to obtain a lock with the same name, and they will throw timeout errors until the ColdFusion Application Service on that machine is restarted.
Another thing to note is that the names of named CFLocks can be dynamically generated. For instance if are you building an auction site and you want to ensure that when users are bidding on an item that only on bid process runs at a time per item, and you have a set of queries and processes that get run when a person places a bid. You can use an exclusive named CFLock, dynamically generating the name from the auction id so that it only locks the bidding process for that particular auction instead of all auctions, like this:
Here is where the code would go to add the bid, calculate the new high bidder,
check to see if the bidder who placed the bid is the high bidder or not, send an
email notification to another bidder if he just got outbid, etc, etc, etc.
<!--- here is where you handle errors so the lock always get's released --->
A good locking strategy is a vital part of the health of an application. Make sure you do it right so your blood pressure will go down and won't need to eat so many Rolaids.