|
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 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:
Retrieves the current value of Application.totalTicketsSold
from temporary storage.
Increments this value.
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:
Order 1 reads the total tickets sold as 160.
Order 2 reads the total tickets sold as 160.
Order 1 adds an order of 5 tickets to 160 to get 165.
Order 2 adds an order of 3 tickets to 160 to get 163.
Order 1 saves the value 165 to Application.totalTicketsSold
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:
Order 1 reaches the lock tag, which gets an Application
scope lock.
Order 1 reads the total tickets sold as 160.
Order 2 reaches the lock tag. Because there is an active
Application scope lock, ColdFusion waits for the lock to free.
Order 1 adds an order of 5 tickets to 160 to get 165.
Order 1 saves the value 165 to Application.totalTicketsSold.
Order 1 exits the lock tag. The Application scope lock is
now free.
Order 2 gets the Application scope lock and can begin processing.
Order 2 reads the total tickets sold as 165.
Order 2 adds an order of 3 tickets to 165 to get 168.
Order 2 saves the value 168 to Application.totalTicketsSold.
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 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:
- 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 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:
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 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:
Perform a time-consuming activity outside a cflock tag
Assign the result to a Variables scope variable
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 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:
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 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:
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:
Lock the Session scope. In the cflock tag,
specify scope="Session".
Lock the Application scope. In the cflock tag,
specify scope="Application".
Lock the Server scope. In the cflock tag,
specify scope="Server".
Unlock the Server scope.
Unlock the Application scope.
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 scopeYou can avoid locking some shared-scope variables multiple
times during a request by doing the following:
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.
Use the Request scope variables on your ColdFusion pages
for the duration of the request.
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 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:
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.
Outside the cflock bock, test the value
of the local variable
If it the local variable indicates that the application variables
are not initialized, get an exclusive Application scope lock.
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.
If the variables are still not set, set them and set the
Application scope flag to true.
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>
|
|
|