The cflock
tag controls simultaneous access to ColdFusion code. The cflock
tag lets you do the following:
ColdFusion Server is a multithreaded web application server that can process multiple page requests at a time. As a result, the server can attempt to access the same information or resources simultaneously, as the result of two or more requests.
While the ColdFusion Server is thread-safe and does not try to modify a variable simultaneously, it does not ensure the correct order of access to information. If multiple pages, or multiple invocations of a page, attempt to write data simultaneously, or read and write it at the same time, the resulting data can be inconsistent, as shown in the following "Sample locking scenarios" section.
Similarly, the ColdFusion Server cannot automatically ensure that two sections of code do not attempt to access external resources such as files, databases, or CFX tags that cannot properly handle simultaneous requests. Nor can the ColdFusion Server ensure that the order of access to these shared resources is consistent and results in valid data.
By locking code that accesses such resources so that only one thread can access the resource at a time, you ensure data integrity.
The following examples present scenarios in which you need to lock ColdFusion code. These scenarios show only two of the circumstances where locking is vital.
If you have an application-wide value, such as a counter of the total number of tickets sold, you might have code such as the following on a login page:
<cfset Application.totalTicketsSold = Application.totalTicketsSold + ticketOrder>
When ColdFusion executes this code, it performs the following operations:
Suppose that ColdFusion processes two ticket orders at approximately the same time, and that the value of Application.totalTicketsSold is initially 160. The following sequence might happen:
The application now has an inaccurate count of the tickets sold, and is in danger of selling more tickets than the auditorium can hold.
To prevent this from happening, lock the code that increments the counter, as follows:
<cflock scope="Application" timeout="10" type="Exclusive">
<cfset Application.totalTicketsSold = Application.totalTicketsSold + ticketOrder> </cflock>
The cflock
tag ensures that while ColdFusion performs the processing in the tag body, no other threads can access the Application scope. As a result, the second transaction is not processed until the first one completes. The processing sequence looks something like the following:
The resulting Application.totalTickesSold value is now correct.
Often an application sets multiple shared scope variables at one time, such as a number of values submitted by a user on a form. If the user submits the form, clicks the back button, and then resubmits the form with different data, the application might end up with a mixture of data from the two submissions, in much the same manner as shown in the previous section.
For example, an application might store information about order items in a Session scope shopping cart. If the user submits an item selection page with data specifying sage green size 36 shorts, and then resubmits the item specifying sea blue size 34 shorts, the application might end up with a mixture of information from the two orders, such as sage green size 34 shorts.
By putting the code that sets all of the related session variables in a single cflock
tag, you ensure that all the variables get set together. In other words, setting all of the variables becomes an atomic, or single, operation. It is similar to a database transaction, where everything in the transaction happens, or nothing happens. In this example, the order details for the first order all get set, and then they are replaced with the details from the second order.
For more examples of using locking in applications, see "Examples of cflock".
You do not need to use cflock when you read a variable or call a user-defined function name in the Session, Application, or Server scope if it is set in only one place in the application, and is only read (or called, for a UDF) everywhere else. Such data is called write-once. If you set an Application or Session scope variable in Application.cfm and never set it on any other pages, you must lock the code that sets the variable, but do not have to lock code on other pages that reads the variable's value.
However, although leaving code that uses write-once data unlocked can improve application performance, it also has risks. You must make sure that the variables are truly written only once. For example, you must make sure that the variable is not rewritten if the user refreshes the browser or clicks a back button. Also, it can be difficult to ensure that you, or future developers, do not later set the variable in more than one place in the application.
The cflock
tag ensures that concurrently executing requests do not run the same section of code simultaneously and thus manipulate shared data structures, files, or CFX tags inconsistently. It is important to remember that cflock
protects code sections that access or set data, not the variables themselves.
You protect access to code by surrounding it in a cflock
tag; for example:
<cflock scope="Application" timeout="10" type="Exclusive">
<cfif not IsDefined("Application.number")> <cfset Application.number = 1> </cfif> </cflock>
The cflock
tag offers two modes of locking, specified by the type
attribute:
Enclose all code that creates or modifies session,application, or server variables in exclusive cflock
tags.
Enclose code that only reads or tests session, application, or server variables in read-only cflock
tags. You specify a read-only lock by setting the type="readOnly"
attribute in the cflock
tag, for example:
<cflock scope="Application" timeout="10" type="readOnly"> <cfif IsDefined("Application.dailyMessage")> <cfoutput>#Application.dailyMessage#<br></cfoutput> </cfif> </cflock>
Although ColdFusion does not prevent you from setting shared variables inside read-only lock tag, doing so loses the advantages of locking. As a result, you must be careful not to set any session, application, or server variables inside a read-only cflock
tag body.
The cflock
tag prevents simultaneous access to sections of code, not to variables. If you have two sections of code that access the same variable, they must be synchronized to prevent them from running simultaneously. You do this by identifying the locks with the same scope
or name
attributes.
Note: ColdFusion does not require you to identify exclusive locks. If you omit the identifier, the lock is anonymous and you cannot synchronize the code in the cflock
tag block with any other code. Anonymous locks do not cause errors when they protect a resource that is used in a single code block, but they are bad programming practice. You must always identify read-only locks.
When the code that you are locking accesses session, application, or server variables, synchronize access by using the cflock
scope
attribute.
You can set the attribute to any of the following values:
If multiple code sections share a lock, the following rules apply:
cflock
tag block with the type
attribute set to Exclusive
, code in cflock
tag blocks with the same scope
attribute is not allowed to run. They wait until the code with the exclusive lock completes.
cflock
tag block with the type readOnly
is running, code in other cflock
tag blocks with the same scope
attribute and the readOnly
type
attribute can run, but any blocks with the same scope
attribute and an Exclusive
type cannot run and must wait until all code with the read-only lock completes. However, if a read-only lock is active and code with an exclusive lock with the same scope or name is waiting to execute, read-only requests using the same scope or name that are made after the exclusive request is queued must wait until code with the exclusive lock executes and completes.
The cflock
name
attribute provides a second way to identify locks. Use this attribute when you use locks to protect code that manges file access or calls non-thread-safe CFX code.
When you use the name
attribute, specify the same name for each section of code that accesses a specific file or a specific CFX tag.
You must include a timeout
attribute in your cflock
tag. The timeout
attribute specifies the maximum time, in seconds, to wait to obtain the lock if it is not available. By default, if the lock does not become available within the time-out period, ColdFusion generates a Lock type exception error, which you can handle using cftry
and cfcatch
tags.
If you set the cflock
throwOnTimeout
attribute to No, processing continues after the time-out at the line after the </cflock>
end tag. Code in the cflock
tag body does not run if the time-out occurs before ColdFusion can acquire the lock. Therefore, never use the throwOnTimeout
attribute for CFML that must run.
Normally, it does not take more than a few seconds to obtain a lock. Very large time-outs can block request threads for long periods of time and radically decrease throughput. Always use the smallest time-out value that does not result in a significant number of time-outs.
To prevent unnecessary time-outs, lock the minimum amount of code possible. Whenever possible, lock only code that sets or reads variables, not business logic or database queries. One useful technique is to do the following:
cflock
tag
cflock
block.For example, if you want to assign the results of a query to a session variable, first get the query results using a Variables scope variable in unlocked code. Then, assign the query results to a session variable inside a locked code section. The following code shows this technique:
<cfquery name="Variables.qUser" datasource="#request.dsn#">
SELECT FirstName, LastName FROM Users WHERE UserID = #request.UserID# </cfquery> <cflock scope="Session" timeout="5" type="exclusive"> <cfset Session.qUser = Variables.qUser> </cflock>
When you design your locking strategy, consider whether you should have multiple locks containing small amounts of code or few locks with larger blocks of code. There is no simple rule for making such a decision, and you might do performance testing with different options to help make your decision. However, you must consider the following issues:
Inconsistent nesting of cflock
tags and inconsistent naming of locks can cause deadlocks (blocked code). If you are nesting locks, you must consistently nest cflock
tags in the same order and use consistent lock scopes (or names).
A deadlock is a state in which no request can execute the locked section of the page. All requests to the protected section of the page are blocked until there is a time-out. The following table shows one scenario that would cause a deadlock:
Neither user's request can proceed, because it is waiting for the other to complete. The two are deadlocked.
Once a deadlock occurs, neither of the users can do anything to break the deadlock, because the execution of their requests is blocked until the deadlock is resolved by a lock time-out.
You can also cause deadlocks if you nest locks of different types. An example of this is nesting an exclusive lock inside a read-only lock of the same scope or same name.
In order to avoid a deadlock, lock code sections in a well-specified order, and name the locks consistently. In particular, if you need to lock access to the Server, Application, and Session scopes, you must do so in the following order.
cflock
tag, specify scope="Session"
.
cflock
tag, specify scope="Application"
.
cflock
tag, specify scope="Server"
.
Note: You can skip any pair of lock and unlock steps in the preceding list if you do not need to lock a particular scope. For example, you can omit steps 3 and 4 if you do not need to lock the Server scope.
You can avoid locking some shared-scope variables multiple times during a request by doing the following:
With this technique the "last request wins." For example, if two requests run simultaneously, and both requests change the values of data that was copied from the shared scope, the data from the last request to finish is saved in the shared scope, and the data from the previous request is not saved.
The need to lock application variables can reduce server performance, because all requests that use Application scope variables must wait on a single lock. This issue is a problem even for write-once read-many variables, because you still must ensure the variable exists, and possibly set the value before you can read it.
You can minimize this problem by using a technique such as the following to test for the existence of application variables and set them if they do not exist.
cflock
bock, test the value of the local variable
The following code shows this technique:
<!--- Initilialize local flag to false --->
<cfset app_is_initialized = False> <!--- Get a readonly lock ---> <cflock scope="application" type="readonly"> <!--- read init flag and store it in local variable ---> <cfset app_is_initialized = IsDefined("APPLICATION.initialized")> </cflock> <!--- Check the local flag ---> <cfif not app_is_initialized > <!--- Not initialized yet, get exclusive lock to write scope ---> <cflock scope="application" type="exclusive"> <!--- Check nonlocal flag since multiple requests could get to the exclusive lock ---> <cfif not IsDefined("APPLICATION.initialized") > <!--- Do initializations ---> <cfset APPLICATION.varible1 = someValue > ... <!--- Set the Application scope initialization flag ---> <cfset APPLICATION.initialized = "yes"> </cfif> </cflock> </cfif>