ColdFusion 9.0 Resources |
Locking code with cflockContents [Hide]The cflock tag controls simultaneous access to ColdFusion code. The cflock tag lets you do the following:
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 scenariosThe 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 variableIf 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:
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. Ensuring consistency of multiple variablesOften 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 variablesYou 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 tagThe 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 typesThe cflock tag offers two modes of locking, specified by the type attribute:
Lock scopes and namesThe 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 attributeWhen 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:
If multiple code sections share a lock, the following rules apply:
Controlling locking access to files and CFX tags with the name attributeThe 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-outsInclude 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:
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 granularityWhen 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:
Nesting locks and avoiding deadlocksInconsistent 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. 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:
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 scopeYou 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. Locking application variables efficientlyThe 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:
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> |