Locking code with cflock



The cflock tag controls simultaneous access to ColdFusion code. The cflock tag lets you do the following:

  • Protect sections of code that access and manipulate shared data in the Session, Application, and Server scopes, and in the Request and Variables scopes for applications that use ColdFusion threads.

  • Ensure that file updates do not fail because files are open for writing by other applications or ColdFusion tags.

  • Ensure that applications do not try to simultaneously access ColdFusion extension tags written using the CFX API that are not thread-safe. This is important for CFX tags that use shared (global) data structures without protecting them from simultaneous access (not thread-safe). However, Java CFX tags can also access shared resources that could become inconsistent if the CFX tag access is not locked.

  • Ensure that applications do not try to simultaneously access databases that are not thread-safe. (This is not necessary for most database systems.)

ColdFusion is a multi-threaded 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.

Although ColdFusion 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, ColdFusion 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 ColdFusion 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 can prevent race conditions.

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 could 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 many 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 can 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 stores 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 can end up with a mixture of information from the two orders, such as sage green size 34 shorts.

By placing 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 like 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 need not 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, lock the code that sets the variable, but do not have to lock code on other pages that reads the variable’s value. If you set the variable in the corresponding start method in Application.cfc (for example, onApplicationStart for Application scope variables), you do not have to lock the code that sets the variable.

However, although leaving code that uses write-once data unlocked can improve application performance, it also has risks. Ensure that the variables are written only once. For example, ensure 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:

Exclusive locks (the default lock type)
Allow only one request to process the locked code. No other requests can run code inside the tag while a request has an exclusive lock.

Enclose all code that creates or modifies session, application, or server variables in exclusive cflock tags.

Read-only locks
Allow multiple requests to execute concurrently if no exclusive locks with the same scope or name are executing. No requests can run code inside the tag while a request has an exclusive lock.

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, be careful not to set any session, application, or server variables inside a read-only cflock tag body.

Note: You cannot upgrade or downgrade a lock from one type to another. In other words, do not nest an exclusive lock in a read-only lock of the same name or scope; the exclusive lock will always time out. Also, do not nest a read-only lock inside an exclusive lock with the same name or scope; doing so has no effect.

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

Request

All code sections with this attribute that run in the same request share a single lock. You use this scope only if your application uses the cfthread tag to create multiple threads in a single request. Locking the Request scope also locks access to Variables scope data. For more information on locking the Request scope, see Locking thread data and resource access.

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

  • When code is running in a 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.

  • When code in a cflock tag block with the type readOnly is running, code in other cflock tag blocks with the same scope attribute and the readOnlytype 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.

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

The cflockname 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

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 cflockthrowOnTimeout 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 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, consider the following issues:

  • If the code block is larger, ColdFusion spends more time inside the block, which can increase the number of times an application waits for the lock to released.

  • Each lock requires processor time. The more locks you have, the more processor time is spent on locking code.

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.

To avoid a deadlock, lock code sections in a well-specified order, and name the locks consistently. In particular, to lock access to the Server, Application, and Session scopes, 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 in the Application.cfc onRequestStart method or 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 in the Application.cfc onRequestEnd method 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 that 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 that 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:

<!--- Initialize 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>