ColdFusion is a multithreaded application, meaning that the server can process more than one page request at a time. Generally speaking, this is a wonderful feature. Because the server can in effect do more than one thing at a time, it can tend to two or three (or 50) simultaneous visitors to your application. But as wonderful as multithreading is, it also means that you need to think carefully about situations where more than one person is accessing a particular page at the same time. Unless you take steps to prevent it, the two page requests can be reading or changing the same application variable at the very same moment. If you are using Application variables to track any type of data that changes over time, and the integrity of the data is critical to your application (such as any type of counter, total, or statistic), you must tell ColdFusion what to do when two page requests are trying to execute the same "shared data-changing" code at the same time. NOTE This isn't something that only ColdFusion programmers face. In one fashion or another, you'll run into these issues in any multithreaded programming environment. ColdFusion just makes the issue really easy to deal with. Of course, ColdFusion provides solutions to help you deal with concurrent page requests quite easily. Either of the following can be used to control what happens when several page requests are trying to read or change information in the application scope:
NOTE This discussion assumes you are developing applications for ColdFusion MX 7. Previous versions of ColdFusion required you to use locks far more frequently, even when there wasn't a race condition issue at hand. Basically, you needed to lock every line of code that used application or session variables. This is no longer the case. Beginning in ColdFusion MX, you need only lock application or session variables if you are concerned about race condition issues, as explained in the next section. NOTE If you use the onApplicationStart method, you don't need to use any <cflock> tags. All application variables created there are entirely thread-safe. What Is A Race Condition?It's time to pause for just a moment of theory. You need to understand the concept of a race condition and how such conditions can occur in your ColdFusion applications. Simply put, a race condition is any situation where two different page requests can change the same information at the very same moment in time. In many situations, race conditions can lead to undesired results. In other situations, you may not care about them at all. NOTE We seriously recommend taking a few moments to really visualize and understand this stuff, especially if you are going to be using application variables to hold values that change over time (especially values that increment numerically as your application does its work). Here's an example that should make this really easy to understand. Imagine an application variable called APPLICATION.HitCount. The purpose of this variable is to track the number of individual page views that an application has responded to since the ColdFusion server was started. Simple code like the following is used in the onRequestStart method to advance the counter by one every time a user visits a page: <cfset APPLICATION.hitCount = APPLICATION.hitCount + 1> So far, so good. The code seems to do what it's supposed to. Every time a page is viewed, the variable's value is increased by one. You can output it at any time to display the current number of hits. No problem. But what happens if two people visit a page at the same time? We know that ColdFusion doesn't process the pages one after another; it processes them at the very same time. Keeping that in mind, consider what ColdFusion has to do to execute the <cfset> tag shown above. Three basic mini-steps are required to complete it:
The big problem is that another page request may have changed the value of the variable between steps 1 and 2, or between steps 2 and 3. Just for fun, let's say the hit count variable is currently holding a value of 100. Now two users, Bob and Jane, both type in your application's URL at the same time. For whatever reason, Jane's request gets to the server a split moment after Bob's. Bob's request performs the first mini-step (getting the value of 100). Now, while Bob's request is performing the second mini-step (the addition), Jane's request is doing its first step: finding out what ColdFusion has for the current value of the application variable (uh-oh, still 100). While Bob's request performs the third mini-step (updating the counter to 101), Jane's request is still doing its second step (adding one to 100). Jane's request now finishes its third step, which sets the application variable to, you guessed it, 101. That is, when both requests are finished, the hit count has only increased by one, even though two requests have come through since hit number 100. A bit of information has been lost. Granted, for a simple hit counter like this, a teensy bit of information loss probably isn't all that important. You may not care that the hit count is off by one or two every once in a while. But what if the application variable in question was something more like APPLICATION.totalSalesToDate? If a similar kind of "mistake" occurred in something like a sales total, you might have a real problem on your hands. NOTE Again, it is important to note that this isn't a problem specific to ColdFusion. It's a simple, logical problem that would present itself in almost any real-world situation where several different "people" (here, the people are Web users) are trying to look at or change the same information at the same time. NOTE By definition, the chances of a race condition problem actually occurring in an application will increase as the number of people using the application increases. That is, these kinds of problems tend to be "stealth" problems that are difficult to catch until an application is battle-tested. The solution is to use <cflock> tags to make sure that two requests don't execute the <cfset> tag (or whatever problematic code) at the same exact moment. For example, <cflock> tags would cause Jane's request to wait for Bob's request to be finished with that <cfset> before it started working on the <cfset> itself. NOTE Does all this "two related things happening at the same moment in different parts of the world" stuff sound like something out of a Kieslowski film? Or remind you of bad song lyrics, perhaps something cut from The Police's "Synchronicity" album? Perhaps, but this kind of freak coincidence really can and will happen sooner or later. So, no, I can't promise that Irene Jacob, Julie Delpy, and Juliette Binoche will all happen to show up at your doorstep at the same time someday, any more than I can promise you tea in the Sahara. But I can assure you that some kind of unexpected results will occur someday if this kind of race condition is allowed to occur, unchecked, in your code. How's that for fatalism? <cflock> Tag SyntaxNow that you know what race conditions are and how they can lead to unpredictable results, it's time to learn how to use locking to avoid them. We'll get into the nuances shortly, but the basic idea is to place opening and closing <cflock> tags around any part of your code that changes application variables (or session variables, which are discussed in the next chapter) or any other type of information that might be shared or changed by concurrent page requests. Table 19.8 takes a closer look at the tag's syntax. Using Exclusive LocksAs Table 19.9 shows, there are two types of locks: Exclusive and ReadOnly. Let's start off simple, and talk about <cflock> tags of type="Exclusive". If you want, you can solve your race condition problems using only exclusive locks.
Exclusive locks work like this. When your template gets to an opening <cflock> tag in your code, it requests the corresponding lock from the server. There is only one available lock for each scope (Application, Session, or Server), which is why it's called "exclusive." Once this exclusive lock has been bestowed upon your template, it stays there until the closing </cflock> tag in your code, at which point the lock is released and returned to the server. While your template has the lock (that is, while the code between the <cflock> tags is running), all other templates that want an application-level lock must wait in line. ColdFusion pauses the other templates (right at their opening <cflock> tags) until your template releases the lock. The code shown in Listing 19.14 shows how to place exclusive locks in your code. This listing is similar to the previous version of the Featured Movie template (Listing 19.7). The only important difference is the pair of <cflock> tags at the top of the code. Note that the <cflock> tags surround the entire portion of the template that is capable of changing the current value of the APPLICATION.movieList variable. Listing 19.14. FeaturedMovie2.cfmUsing Exclusive Locks to Safely Update Application Data<!--- Filename: FeaturedMovie.cfm Created by: Nate Weiss (NMW) Purpose: Displays a single movie on the page, on a rotating basis Please Note Application variables must be enabled ---> <!--- Need to lock when accessing shared data ---> <cflock scope="APPLICATION" type="Exclusive" timeout="10"> <!--- List of movies to show (list starts out empty) ---> <cfparam name="APPLICATION.movieList" type="string" default=""> <!--- If this is the first time we're running this, ---> <!--- Or we have run out of movies to rotate through ---> <cfif listLen(APPLICATION.movieList) eq 0> <!--- Get all current FilmIDs from the database ---> <cfquery name="getFilmIDs" datasource="#REQUEST.dataSource#"> SELECT FilmID FROM Films ORDER BY MovieTitle </cfquery> <!--- Turn FilmIDs into a simple comma-separated list ---> <cfset APPLICATION.movieList = valueList(getFilmIDs.FilmID)> </cfif> <!--- Pick the first movie in the list to show right now ---> <cfset thisMovieID = listGetAt(APPLICATION.movieList, 1)> <!--- Re-save the list, as all movies *except* the first ---> <cfset APPLICATION.movieList = listDeleteAt(APPLICATION.movieList, 1)> </cflock> <!--- Now that we have chosen the film to "Feature", ---> <!--- Get all important info about it from database. ---> <cfquery name="GetFilm" datasource="#REQUEST.dataSource#"> SELECT MovieTitle, Summary, Rating, AmountBudgeted, DateInTheaters FROM Films f, FilmsRatings r WHERE FilmID = #thisMovieID# AND f.RatingID = r.RatingID </cfquery> <!--- Now Display Our Featured Movie ---> <cfoutput> <!--- Define formatting for our "feature" display ---> <style type="text/css"> TH.fm {background:RoyalBlue;color:white;text-align:left; font-family:sans-serif;font-size:10px} TD.fm {background:LightSteelBlue; font-family:sans-serif;font-size:12px} </style> <!--- Show info about featured movie in HTML Table ---> <table width="150" align="right" border="0" cellspacing="0"> <tr><th > Featured Film </th></tr> <!--- Movie Title, Summary, Rating ---> <tr><td > <b>#getFilm.MovieTitle#</b><br> #getFilm.Summary#<br> <p align="right">Rated: #getFilm.Rating#</p> </td></tr> <!--- Cost (rounded to millions), release date ---> <tr><th > Production Cost $#round(getFilm.AmountBudgeted / 1000000)# Million<br> In Theaters #dateFormat(getFilm.DateInTheaters, "mmmm d")#<br> </th></tr> </table> <br clear="all"> </cfoutput> The purpose of the <cflock> tag in Listing 19.14 is to ensure that only one instance of the block is ever allowed to occur at the very same moment in time. For example, consider what happens if two different users request the page within a moment of each other. If by chance the second page request gets to the start of the block before the first one has exited it, the second request will be forced to wait until the first instance of the block has completed its work. This guarantees that funny race condition behavior doesn't take place (like one of the movies getting skipped or shown twice). TIP If it helps, think of locks as being like hall passes back in grade school. If you wanted to go to the bathroom, you needed to get a pass from the teacher. Nobody else was allowed to go to the bathroom until you came back and returned the pass. This was to protect the students (and the bathroom) from becoming, um, corrupted, right? Using ReadOnly LocksOkay, you've seen how exclusive locks work. They simply make sure that no two blocks of the same scope are allowed to execute at once. If two requests need the same lock at the same time, the first one blocks the second one. But in some situations this can be overkill, and lead to more waiting around than is really necessary. ColdFusion also provides ReadOnly locks, which are less extreme. ReadOnly locks don't block each other. They only get blocked by exclusive locks. In plain English, a ReadOnly lock means, "If the variables in this block are being changed somewhere else, wait until the changes are finished before running this block." Use a ReadOnly lock if you have some code that definitely needs to read the correct, current value of an application variable, but isn't going to change it at all. Then just double-check that all code that does change the variable is between Exclusive locks. This way, you are guaranteed to always be reading or displaying the correct, most current value of the variable, but without the unwanted side effect of forcing other page requests to wait in line. Do this whenever you are going to be reading the value of a variable a lot, but changing its value only occasionally. NOTE In other words, read-only locks don't have any effect on their own. They only have an effect when some other page request has an Exclusive lock. To demonstrate how much sense this all makes, let's adapt the featured movie example a bit. So far, the featured movie has rotated with every page request. What if you still wanted the movies to rotate evenly and in order, but instead of rotating with every page request, you want the movie to change once every five minutes (or ten minutes, or once an hour)? Here is an adapted version of the featured movie template that gets this job done (see Listing 19.15). The code is a bit more complicated than the last version. For the moment, don't worry about the code itself. Just note that the portion of the code that makes changes in the APPLICATION scope is in an exclusive lock. The portion of the code that grabs the current feature movie from the APPLICATION scope (which is also really short and quick) is inside a ReadOnly lock. Listing 19.15. FeaturedMovie3.cfmUsing Exclusive and ReadOnly Locks<!--- Filename: FeaturedMovie.cfm Created by: Nate Weiss (NMW) Purpose: Displays a single movie on the page, on a rotating basis Please Note Application variables must be enabled ---> <!--- We want to obtain an exclusive lock if this ---> <!--- is the first time this template has executed, ---> <!--- or the time for this featured movie has expired ---> <cfif (not isDefined("APPLICATION.movieRotation")) or (dateCompare(APPLICATION.movieRotation.currentUntil, now()) eq -1)> <!--- Make sure all requests wait for this block ---> <!--- to finish before displaying the featured movie ---> <cflock scope="APPLICATION" type="Exclusive" timeout="10"> <!--- If this is the first time the template has executed... ---> <cfif not isDefined("APPLICATION.movieRotation")> <!--- Get all current FilmIDs from the database ---> <cfquery name="GetFilmIDs" datasource="#REQUEST.dataSource#"> SELECT FilmID FROM Films ORDER BY MovieTitle </cfquery> <!--- Create structure for rotating featured movies ---> <cfset st = structNew()> <cfset st.movieList = valueList(getFilmIDs.FilmID)> <cfset st.currentPos = 1> <!--- Place structure into APPLICATION scope ---> <cfset APPLICATION.movieRotation = st> <!--- ...otherwise, the time for the featured movie has expired ---> <cfelse> <!--- Shorthand name for structure in application scope ---> <cfset st = APPLICATION.movieRotation> <!--- If we haven't gotten to the last movie yet ---> <cfif st.currentPos lt listLen(st.movieList)> <cfset st.currentPos = st.currentPos + 1> <!--- if already at last movie, start over at beginning ---> <cfelse> <cfset st.currentPos = 1> </cfif> </cfif> <!--- In any case, choose the movie at the current position in list ---> <cfset st.currentMovie = listGetAt(st.movieList, st.currentPos)> <!--- This featured movie should "expire" a short time from now ---> <cfset st.currentUntil = dateAdd("s", 5, now())> </cflock> </cfif> <!--- Use a ReadOnly lock to grab current movie from application scope... ---> <!--- If the exclusive block above is current executing in another thread, ---> <!--- then ColdFusion will 'wait' before executing the code in this block. ---> <cflock scope="APPLICATION" type="ReadOnly" timeout="10"> <cfset thisMovieID = APPLICATION.movieRotation.currentMovie> </cflock> <!--- Now that we have chosen the film to "Feature", ---> <!--- Get all important info about it from database. ---> <cfquery name="GetFilm" datasource="#REQUEST.dataSource#"> SELECT MovieTitle, Summary, Rating, AmountBudgeted, DateInTheaters FROM Films f, FilmsRatings r WHERE FilmID = #thisMovieID# AND f.RatingID = r.RatingID </cfquery> <!--- Now Display Our Featured Movie ---> <cfoutput> <!--- Define formatting for our "feature" display ---> <style type="text/css"> TH.fm {background:RoyalBlue;color:white;text-align:left; font-family:sans-serif;font-size:10px} TD.fm {background:LightSteelBlue; font-family:sans-serif;font-size:12px} </style> <!--- Show info about featured movie in HTML Table ---> <table width="150" align="right" border="0" cellspacing="0"> <tr><th > Featured Film </th></tr> <!--- Movie Title, Summary, Rating ---> <tr><td > <b>#getFilm.MovieTitle#</b><br> #getFilm.Summary#<br> <p align="right">Rated: #getFilm.Rating#</p> </td></tr> <!--- Cost (rounded to millions), release date ---> <tr><th > Production Cost $#round(val(getFilm.AmountBudgeted) / 1000000)# Million<br> In Theaters #dateFormat(getFilm.DateInTheaters, "mmmm d")#<br> </th></tr> </table> <br clear="all"> </cfoutput> The first thing this template does is check whether changes need to be made in the APPLICATION scope. Changes will be made if the template hasn't been run before, or if it's time to rotate the featured movie. (Remember, the rotation now based on time.) If changes are called for, an Exclusive lock is opened. Within the lock, if the template hasn't been run before, a list of movies is retrieved from the database and stored as a value called movieList, just as before. In addition, a value called currentPos is set to 1 (to indicate the first movie). This value will increase as the movies are cycled through. Execution then proceeds to the bottom of the <cflock> block, where the current movie id is plucked from the list, and a value called currentUntil is set to a moment in time a few seconds in the future. On the other hand, if the lock was opened because the currentUntil value has passed (we're still inside the Exclusive lock block), then it's time to pick the next movie from the list. As long as the end of the list hasn't already been reached, the only thing required is to advance currentPos by one. If the last movie has already been reached, the currentPos is reset to the beginning of the list. NOTE At any rate, the entire Exclusive lock block at the top of the template executes only once in a while, when the movie needs to change. If you are rotating movies every 10 minutes and have a fair number of visitors, the lock is needed only in the vast minority of page requests. Underneath, a second <cflock> block of type="ReadOnly" uses a <cfset> to read the current featured movie from the APPLICATION scope into a local variable. The read-only lock ensures that if the featured movie is currently being changed in some other page request, the <cfset> will wait until the change is complete. Since the change occurs only once in a while, the page is usually able to execute without having to wait at all. TIP Think of ReadOnly locks as a way of optimizing the performance of code that needs some kind of locking to remain correct. For instance, this template could have been written using only exclusive locks, and doing so would have made sure that the results were always correct (no race conditions). The introduction of the ReadOnly lock is a way of making sure that the locks have as little impact on performance as possible. NOTE You'll encounter the notion of explicitly locking potentially concurrent actions in database products as well. Conceptually, database products use the SQL keywords BEGIN TRANSACTION and COMMIT TRANSACTION in a way that's analogous to ColdFusion's interpretation of beginning and ending <cflock> tags.
See Chapter 30, "More About SQL and Queries" for details about database transactions. Using Named Locks instead of SCOPEYou've seen why locks are sometimes needed to avoid race conditions. You've seen the simplest way to implement themwith Exclusive locks. You've seen how to avoid potential bottlenecks by using a mix of Exclusive and ReadOnly locks. Hopefully, you've noticed a pattern emerging: If you're worried about race conditions, your goal should be to protect your data with <cflock>, but to do so in the least obtrusive way possible. That is, you want your code to be "smart" about when page requests wait for each other. So far, all of the <cflock> tags in this chapter have been scoped locks. Each has used a scope="Application" attribute to say, "This lock should block or be blocked by all other locks in the application." As you have seen, scoped locks are really simple to implement once you "get" the conceptual issue at hand. The problem with scoped locks is that they often end up locking too much. There's no problem when you're using only application variables to represent a single concept. For instance, the various versions of the Featured Movie template track a few different variables, but they are all related to the same concept of a featured movie that rotates over time. Consider what happens, though, if you need to add a rotating Featured Actor widget to your application. Such a widget would be similar to the featured movie but it would rotate at a different rate or according to some other logic. Just for the heck of it, pretend there's also a Featured Director widget, plus a couple of hit counters that also maintain data at the application level, and so on. Assume for the moment that these various widgets appear on different pages, rather than all on the same page. Using the techniques you've learned so far, whenever one of these mini-applications needs to change the data it keeps in the APPLICATION scope, it will use a <cflock> with scope="Application" to protect itself against race conditions. The problem is that the scope="Application" lock is not only going to block or be blocked by instances of that same widget in other page requests. It's going to block or be blocked by all locks in the entire application. If all of your widgets are only touching their own application variables, this approach is overkill. If the Featured Actor widget doesn't touch the same variables that the featured movie widget uses, then there's no possibility of a race condition. Therefore, allowing reads and writes by the two widgets to block one another is a waste of time, but scope="Application" doesn't know that. ColdFusion gives you further control over this kind of problem by supporting named locks. To add named locks to your code, you use a name attribute in your <cflock> tags, instead of a scope attribute. Named lock blocks will only block or wait for other lock blocks that have the same name. For instance, instead of using a scoped lock, like this: <cflock scope="Application" type="Exclusive" timeout="10"> you could use a named lock, like this: <cflock name="OrangeWhipMovieRotation" type="Exclusive" timeout="10"> This way, you can feel comfortable manipulating the variables used by the featured movie widget, knowing that the exclusive lock you've asked for will affect only those pieces of code that are also dealing with the same variables. Page requests that need to display the featured movie or featured director widgets won't be blocked needlessly. The name of the lock is considered globally for the entire server, not just for your application, so you need to make sure that the name of the lock isn't used in other applications. The easiest way to do this is to always incorporate the application's name (or something similar) as a part of the lock name. That's why the <cflock> tag shown above includes OrangeWhip at the start of the name attribute. Another way to get the same effect would be to use the automatic APPLICATION.applicationName variable as a part of the name, like so: <cflock name="#APPLICATION.applicationName#MovieRotation" type="Exclusive" timeout="10"> The CD-ROM for this book includes a FeauturedMovie4.cfm template, which is almost the same as FeaturedMovie3.cfm, shown in Listing 19.15. The only difference is that it uses name="OrangeWhipMovieRotation" (as shown above) instead of scope="Application" in each of the <cflock> tags. NOTE So it turns out that the scope="Application" attribute is really just a shortcut. Its effect is equivalent to writing a named lock that uses the name of your application (or some other identifier that is unique to your application) as the name. Nested Locks and DeadlocksIt's usually OK to nest named locks within one another, as long as the name for each lock block is different. However, if they aren't nested in the same order in all parts of your code, it's possible that your application will encounter deadlocks while it runs. Deadlocks are situations where it's impossible for two page requests to move forward because they are each requesting a lock that the other already has. Consider a template with an Exclusive lock named LockA, with another <cflock> named LockB nested within it. Now consider another template, which nests LockA within LockB. If both templates execute at the same time, the first page request might be granted an exclusive lock for LockA, and the second could get an exclusive lock for LockB. Now neither template can move forward. Both locks will time out and throw errors. This is deadlock. Entire books have been written about various ways to solve this kind of puzzle; there's no way we can tell you how to handle every possible situation. Our advice is this: If you need to nest named locks, go ahead as long as they will be nested in the same combination and order in all of your templates. If the combination or order needs to be different in different places, use scoped locks instead. The overhead and aggravation you might encounter in trying to manage and debug potential deadlocks isn't worth the added cost introduced by the scope shorthand. Don't confuse this discussion (nesting locks with different names) with nesting locks that have the same name or scope. In general, you should never nest <cflock> tags that have the same scope or name. A ReadOnly lock that is nested within an exclusive lock with the same scope or name has no additional benefit (it's always safe to read if you already have an exclusive lock). And if the exclusive lock is nested within a ReadOnly lock, then the exclusive lock can never be obtained (because it needs to wait for all ReadOnly locks to end first), and thus will always time out and throw an error. Locking with ColdFusion 5 and EarlierThe advice about when and how to use locks given in this chapter applies only to ColdFusion MX and later. Previous versions of ColdFusion approached locking differently within the guts of the server. The result was that every read or write of any shared variable needed to be locked, regardless of whether there was a possibility of a logical race condition. Without the locks, ColdFusion's internal memory space would eventually become corrupted, and the server would crash or exhibit strange and unstable behavior. In other words, locks were needed not only to protect the logical integrity of shared data, but also to protect the ColdFusion server from itself. Thankfully, this shortcoming has gone away as of ColdFusion MX, because shared variables end up being synchronized internally by the new Java-based runtime engine. This means that if you are writing an application that you want to be backward compatible with ColdFusion 5 and earlier, you must lock every single reference to any application, session, or server variable, even if you are just outputting its value. Even isDefined() tests and <cfparam> tags must be locked under ColdFusion 5 and earlier. |