home Links Articles Books Past Meetings Photos SiteMap
The MDCFUG is sponsored by TeraTech. Visit us at www.TeraTech.com

Please send
comments/questions to

michael@
teratech.com

 

How to Sidestep Locking

 

We look at the problems with locking shared scoped variables and explain a way to never have to explicitly lock your variables again. We also explain how to make session variables truly clusterable without pain.

Locking required

We all know we should lock our shared scoped variables (that is application, session and server variable that can be shared between multiple ColdFusion threads). Everywhere we turn, there is another article, another email, and another best practices that all entails locking. According to Macromedia "If you are not locking your shared scoped variables, your application will fail under load". We are not going to get into why you should lock; there are enough excellent articles around that cover that aspect. Rather we are going to give you some quick and easy ways to get around locking your shared scoped variables.

Get around locking? How can that be? We have to lock our variables, Macromedia (and a lot of other people) say we do. Well not entirely, the basic premise of locking shared scoped variables, still holds so the answer is, "don’t use shared scoped variables."

 

Application variables

We have all used Application variables for what I call global constants, constants that are set once in Application.cfm and are then used throughout our program. Application.datasource or Application.dsn might come to mind. When we consider that we have to not only lock all the writes to this scope, but all reads as well, suddenly, having every <CFQUERY> use the Application.datasource variable doesn’t look very good. So STOP, throw out all application variables. Well, don’t throw them away, but change them to a Request.App structure. Using this method of coding, Request.App means a global constant, which is always available. You can also use other request structures for other types of global constants. Request.site for site mappings and variables for paths, Request.page for page constants. You get the picture. When you place every global constant into the Request App structure within your Application.cfm, you still have a very visual way of noting what your global constants are, but you have also totally eliminated the need for locking.

 

Server variables

To be honest, most people don’t use server variables. These are like application variables but are available to all applications named on that server. Some people suggest storing query recordsets such as all states into a server-scoped variable. But again, that requires a lock to read as well. If you are using Server scope to store queries, cache the queries instead using the cachedwithin attribute of the <CFQUERY> tag. One use I saw at DevCon was caching things like CFDIRECTORY, CFPOP etc

Session variables

Now it starts to get interesting. The biggest problem with shared scope and locking that we developers experience is when using session scoped variables. What would you say if I told you there was a way to never have your developers write to the session or client scope again AND at the same time to switch your applications from using a session based state management to a client based state management with one variable. Interested? Keep reading.

 

Scenario One – lazy developers

We all have worked with "Joe" (names have been changed to protect the innocent). Joe is a good guy, a fairly good developer, but he’s lazy. Someone needs to look at Joe’s code a lot since Joe hasn’t been fully convinced yet of the necessity of locking his session variables. While, we can (and should in a development environment) certainly turn on the CF Admin option to lock all scopes so that it always throws errors, we want some better way to not have to worry about this.

 

Scenario Two – clustering complex session variables

You’ve spent months working on an application that went live last month. You’ve used a lot of complex structures in your session variables and the app is smoking. Suddenly a lot of users have accessed your applications and your boss wants to cluster it. The only problem? Since you have written the application using Session variables, you only have two options, Implementing sticky sessions on your load balancer (which not only will keep all your users on their original server, but is exactly what your Boss doesn’t want), or have to go into your code, and replace all session variables with client variables. Keeping in mind that you can’t really just run a search and replace since client variables can’t store complex data structures except through WDDX.

The situation described is exactly what I came up against on an application. I had a junior developer who wouldn’t or couldn’t understand about locking variables and three quarters of the way through the project, the powers that be decided that we had to make it clusterable. I needed one solution that solved both problems. The solution was a custom tag <CF_REQUEST> that takes one attribute, type, which can be set to "read" or "write". It also takes a Request scope flag (Request.storagetype) that is set and used in Application.cfm when using the <CFApplication> Tag.

<cfparam name="Request.StorageType" default="Session">

<cfswitch expression="#Request.StorageType#">

<cfcase value="Client">

<cfapplication name="DemoApp"

clientmanagement="Yes"

sessionmanagement="No"

setclientcookies="Yes"

clientstorage="StorageDB">

</cfcase>

<cfcase value="Session">

<cfapplication name="DemoApp"

clientmanagement="No"

sessionmanagement="Yes"

setclientcookies="Yes"

sessiontimeout=#CreateTimeSpan(0,0,20,0)#>

</cfcase>

</cfswitch>

<CF_Request type="read">

Application.cfm – Code Sample 1

By setting a Request Scoped variable called storage type, I could literally pick and choose whether to run either a session state management or a client state management application. If client scope is required, set up Request.StorageType to equal Client, which will then in turn run the <CFApplication> tag which sets up the client state management. So much for worrying whether the application needed to use session or client variables in the app. By changing one variable, that problem was solved.

Making request scope "permanent"

Then it was time for the more interesting problem. I knew that I needed to use the request scope for all the reads and writes within the application, but also knew that request scope only holds variables for the page calls and cannot store the information. So the need was to write the request scoped variables to either client or session scope depending on the Request.StorageType flag.

<CF_REQUEST> takes one attribute "Type" which can be set to either read or write. Depending on that attribute and the Request.StorageType variable set in Application.cfm, The request scope can then be used in all application development and have the program determine the best way to save the variables in between page calls.

<cfparam attributes.type="read">

 

<!--- Setup list of structures that are global constants and don't need to be written --->

<cfset nocopy = "page,app,site,cfid,cftoken,sessionid,urltoken,checklastvisit,StorageType">

<cfswitch expression="#attributes.type#">

<cfcase value="read">

<!--- Now choose whether to read from Client or Session Variables --->

<cfswitch expression="#Request.StorageType#">

<!--- If we are using Client Variables. --->

<cfcase value="Client">

<!--- Read the structure from the Client Variables using WDDX --->

<cfwddx action="WDDX2CFML" input="#Client.Request#" output="Temp">

</cfcase>

<!--- If we are using Session Variables. --->

<cfcase value="Session">

<cflock scope="SESSION" type="ReadOnly" timeout="30">

<cfset Temp = Duplicate(Session.Request)>

</cflock>

</cfcase>

</cfswitch>

<cfif isDefined('Temp')>

<!--- Now copy temporary variable to Request Scope --->

<cfset keystostruct = structkeyarray(temp)>

<cfloop index="i" from="1" to="#ArrayLen(keysToStruct)#">

<!--- As long as the Temporary variable is not one of the no copy structures. --->

<cfif listfindnocase(nocopy ,keystostruct[i])>

<cfset "Request.#keysToStruct[i]#" = #duplicate(temp[keystostruct[i]])#>

</cfif>

</cfloop>

<!--- Delete Temp of structure --->

<cfset Temp = StructDelete(Temp)>

</cfif>

</cfcase>

</cfswitch>

Read Portion of <CF_Request> Code Sample 2

Basically, there is a switch statement based on the request.storagetype variable which reads either the client scope or the session scope into a temporary variable. The tag then loops through the temporary variable structure and makes sure that the structure being copied does not belong to either a global constant or a read only variable (after all, these are always available and do not need to be saved between page calls).

At the end, the temporary structure is deleted and there is now a copy of either the client or session structure in the request scope.

<!--- Setup list of structures that are global constants and don't need to be written --->

<cfset nocopy = "page,app,site,cfid,cftoken,sessionid,urltoken,checklastvisit,StorageType">

<cfswitch expression="#attributes.type#">

<cfcase value="write">

<!--- Grab the keys to the request structure. --->

<cfset keystostruct = structkeyarray(request)>

<!--- Setup a temporary structure. --->

<cfset Temp = StructNew()>

<!--- Loop through the Request Structure. --->

<cfloop index="i" from="1" to="#ArrayLen(keysToStruct)#">

<!--- As long as the request variable is not one of the no copy structures. --->

<cfif listfindnocase(nocopy ,keystostruct[i])>

<!--- Copy that particular structure to the temporary structure using the duplicate() function --->

<cfset "Temp.#keysToStruct[i]#" = #duplicate(request[keystostruct[i]])#>

</cfif>

</cfloop>

<!--- Now choose how to save the structure --->

<cfswitch expression="#Request.StorageType#">

<!--- If we are using Client Variables. --->

<cfcase value="Client">

<!--- Write the structure to Client Variables using WDDX --->

<cfwddx action="CFML2WDDX" input="#Temp#" output="Client.Request"> --->

</cfcase>

<!--- If we are using Session Variables. --->

<cfcase value="Session">

<cflock scope="SESSION" type="Exclusive" timeout="30">

<cfset Session.Request = Duplicate(Temp)>

</cflock>

</cfcase>

</cfswitch>

</cfcase>

</cfswitch>

Write Portion of CF_Request Sample Code 3

The write portion of CF_Request works in much the same way. Except, instead of reading to the request scope, it writes from request scope to either session or client scope. The write also makes sure that global constants do not get written to the session or client scope by comparing the structure against the nocopy list.

The calls to the custom tag needs to come in 3 places. In Application.cfm, right after the implementation of the CFApplication tag the following call should be inserted:

<cf_request type="read">

The write portion (<cf_request type="write"> needs to be called twice. It needs to go into your onRequestEnd.cfm so that all variables called and written during a page call can be placed in the proper scope. It also needs to go just before any call to <CFLocation> this is because any call to cflocation will not run onRequestEnd. For simplicities sake, I used the Custom Tag <CF_Location> (found on the fusebox.org site) and simply added the call into this custom tag just before the actual location takes place.

Are there any problems with using this system? We have found none that relate technically. Reading and writing to the various scopes happens very quickly. The only problem we found is that programmers who use this technique rapidly forget there was ever such a thing as either client or session variables. And in retrospect, that hasn’t been so bad either.

 

Summary

We have shown how to avoid locking in your code by the automatic conversion of client and session scopes to request scope. We explain how to use the CF_Request custom tag in Application.cfm and OnRequestEnd.cfm to achieve this.

Resources

Depressed Press of Boston – Locking in CFML a Comprehensive Guide http://www.depressedpress.com/DepressedPress/Content/ColdFusion/Guides/Locking/Index.cfm

ColdFusion Locking Best Practices - http://www.allaire.com/Handlers/index.cfm?ID=17318&Method=Full&Cache=Off

<cf_request> can be found at http://www.shayna.com/index.cfm?fuseaction=coldfusion

 

Bio

Sandy Clark is an Advanced Macromedia Certified ColdFusion developer, SCCFUG speaker and author. She is president of Shayna Productions http://www.shayna.com, a ColdFusion consulting company. Sandy can be reached at 310-358-0549 or at slclark@shayna.com. Michael Smith is president of TeraTech http://www.teratech.com/ , a 12-year-old Rockville, Maryland based consulting company that specializes in ColdFusion, Database and Visual Basic development. Michael runs the MDCFUG and recently organized the two-day, Washington, DC-based CFUN-2k conference that attracted more than 750 participants. You can reach Michael at michael@teratech.com or 301-424-3903.


Home | Links | Articles | Past Meetings | Meeting Photos | Site Map
About MDCFUG | Join | Mailing List |Forums | Directions |Suggestions | Quotes | Newbie Tips
TOP

Copyright © 1997-2017, Maryland Cold Fusion User Group. All rights reserved.
< >