Locking code with cflock

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.

Sample locking scenarios

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.

Reading and writing a shared variable

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:

  1. Retrieves the current value of Application.totalTicketsSold from temporary storage.
  2. Increments this value.
  3. Stores the result back in the Application scope.

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:

  1. Order 1 reads the total tickets sold as 160.
  2. Order 2 reads the total tickets sold as 160.
  3. Order 1 adds an order of 5 tickets to 160 to get 165.
  4. Order 2 adds an order of 3 tickets to 160 to get 163.
  5. Order 1 saves the value 165 to Application.totalTicketsSold
  6. Order 2 saves the value 163 to Application.totalTicketsSold

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:

  1. Order 1 reaches the lock tag, which gets an Application scope lock.
  2. Order 1 reads the total tickets sold as 160.
  3. Order 2 reaches the lock tag. Because there is an active Application scope lock, ColdFusion waits for the lock to free.
  4. Order 1 adds an order of 5 tickets to 160 to get 165.
  5. Order 1 saves the value 165 to Application.totalTicketsSold.
  6. Order 1 exits the lock tag. The Application scope lock is now free.
  7. Order 2 gets the Application scope lock and can begin processing.
  8. Order 2 reads the total tickets sold as 165.
  9. Order 2 adds an order of 3 tickets to 165 to get 168.
  10. Order 2 saves the value 168 to Application.totalTicketsSold.
  11. Order 2 exits the lock tag, which frees the Application scope lock. ColdFusion can process another order.

The resulting Application.totalTickesSold value is now correct.

Ensuring consistency of multiple variables

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".

Using the cflock tag with write-once variables

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.

Using the cflock tag

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>

Lock types

The cflock tag offers two modes of locking, specified by the type attribute:

Lock scopes and names

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.

Controlling access to data with the scope attribute

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:
Scope
Meaning
Server
All code sections with this attribute on the server share a single lock.
Application
All code sections with this attribute in the same application share a single lock.
Session
All code sections with this attribute that run in the same session of an application share a single lock.

If multiple code sections share a lock, the following rules apply:

Controlling locking access to files and CFX tags with the name attribute

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.

Controlling and minimizing lock time-outs

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:

  1. Perform a time-consuming activity outside of a cflock tag
  2. Assign the result to a Variables scope variable
  3. Assign the Variables scope variable's value to a shared scope variable inside a 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>

Considering lock granularity

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:

Nesting locks and avoiding deadlocks

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:
User 1
User 2
Locks the Session scope.
Locks the Application scope.
Tries to lock the Application scope, but the Application scope is already locked by User 2.
Tries to lock the Session scope, but the Session scope is already locked by User 1.

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.

  1. Lock the Session scope. In the cflock tag, specify scope="Session".
  2. Lock the Application scope. In the cflock tag, specify scope="Application".
  3. Lock the Server scope. In the cflock tag, specify scope="Server".
  4. Unlock the Server scope.
  5. Unlock the Application scope.
  6. Unlock the Session scope.

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.

Copying shared variables into the Request scope

You can avoid locking some shared-scope variables multiple times during a request by doing the following:

  1. Copy the shared-scope variables into the Request scope in code with an exclusive lock the Application.cfm page.
  2. Use the Request scope variables on your ColdFusion pages for the duration of the request.
  3. Copy the variables back to the shared scope in code with an exclusive lock on the OnRequestEnd.cfm page.

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.

Locking application variables efficiently

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.

  1. Use an Application scope flag variable to indicate if the variable or variables are initialized. In a read-only lock, check for the existence of the flag, and assign the result to a local variable.
  2. Outside the cflock bock, test the value of the local variable
  3. If it the local variable indicates that the application variables are not initialized, get an exclusive Application scope lock.
  4. Inside the lock, again test the Application scope flag, to make sure another page has not set the variables between step one and step four.
  5. If the variables are still not set, set them and set the Application scope flag to true.
  6. Release the exclusive lock.

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>

Comments