I've been doing some major restructuring of a legacy application lately and was looking for the safest method to "reset" the Application scope. We wanted to be able to safely push out small bug fixes to CFCs stored in the Application scope without having to restart the ColdFusion service just in order for the changes to take affect. We were looking for a way that would do this that was safe across threads, without having to go through and put locks on every one of our Application scope reads.
I did some playing with various techniques, but really wanted something like the ApplicationStop() function that was introduced in ColdFusion 9. Fortunately Dave, over at Mister Dai, had already done the ground work and discovered that ColdFusion 7 & 8 both have a coldfusion.runtime.ApplicationScopeTracker object that manages the Application scope. Inside this object is a method called cleanUp() which appears to do exactly what the ApplicationStop() function does in ColdFusion 9.
After a couple of quick tests, Dave's claim seem to be accurate—calling the cleanUp() method really did seem to safely end an Application lifecycle. However, I wanted to go deeper and really put together some tests I could run under load to see if my simple tests we accurate. Fortunately, even through significant load at the code showed stability. I put together several tests all of which should that the cleanUp() method seems to safely end the Application scope and the next new page request will re-initialize the Application scope as if the application was initialized for the first time.
While I'm not going to bother post all my load test scripts, I will share some very simple code which shows that the cleanUp() method does safely clean up the application:
<cfparam name="url.thread" type="numeric" default="1" /> <cfset secretKey = "your-secret-url-variable-name" /> <cfset secretValue = "your-secret-url-value" /> <!---// create random key //---> <cfset key = createUUID() /> <cfif not structKeyExists(Application, "_")> <cfset Application["_"] = {} /> </cfif> <cfoutput> <!---// show an existing random App keys //---> <cfdump var="#duplicate(Application['_'])#" /> <!---// store the new random key in the App scope //---> <cfset Application["_"][key] = key /> <!---// output key //---> <div> #Application["_"][key]# </div> <!---// for the first thread, let's run a CFHTTP to reset the Application scope //---> <cfif url.thread eq 1> <cfthread action="run" name="threadReinitApp"> <cfset sleep(1500) /> <cfhttp url="#getPageContext().getRequest().getRequestURL().toString()#?#secretKey#=#urlEncodedFormat(secretValue)#" method="get" result="thread.cfhttp" redirect="false"> </cfhttp> </cfthread> </cfif> <!---// delay for 5 seconds, to make sure the reinit request has completed //---> <cfset sleep(5000) /> <!---// output key, if Application scope is completely killed, then this would error //---> <div> #Application["_"][key]# </div> <!----// show the results of re-initializing app //---> <cfif isDefined("threadReinitApp")> <cfthread action="join" name="threadReinitApp" timeout="600" /> <cfdump var="#threadReinitApp.cfhttp#" /> </cfif> </cfoutput>
What this code does is:
If the Application scope was reset with something like structClear(Application) then the second time we tried to output the Application["_"][key] the script would have generated an error. However, in all my load testing, this code has never thrown an error.
To actually re-initialize my Application, I implemented 2 custom UDFs that I add to my Application.cfc that are based on Dave's original code. The two functions are ApplicationReset() and ApplicationKill(). The ApplicationReset() method takes in your URL secret key parameter name and calls the ApplicationKill() method and then re-load the current page without the secret URL key. The ApplicationKill() is based on Dave's code, but provides a cross-ColdFusion version of ColdFusion 9's ApplicationStop() method.
<cffunction name="ApplicationReset" access="public" returntype="void" output="false" hint="Resets the application scope"> <cfargument name="secretKey" type="string" required="true" hint="Secret URL key used to reset the application" /> <!---// declare local variables //---> <cfset var currentUrl = getPageContext().getRequest().getRequestURL().toString() /> <!---// get the current query string, but remove the secret key //---> <cfset var queryString = reReplaceNoCase(cgi.query_string, "(^|&)" & arguments.secretKey & "=.*?(&|$)", "\2", "all") /> <!---// reset the application (from Application.CFC) //---> <cfset ApplicationKill() /> <!---// add the query string //---> <cfif len(queryString)> <cfset currentUrl = currentUrl & "?" & queryString /> </cfif> <!---// reload the current page w/out the reinit token //---> <cflocation url="#currentUrl#" addtoken="false" /> <cfabort /> </cffunction> <!---// stops an application, so that on next reque //---> <cffunction name="ApplicationKill" access="public" returntype="boolean" output="false" hint="Implementation of ApplicationStop() that works with CF7 and CF8"> <cfif isDefined("application")> <cftry> <cfif listFind("7,8", listFirst(server.coldfusion.productVersion))> <!---// this is just in case there's no app scope but variables.application //---> <cfset createObject("java", "coldfusion.runtime.ApplicationScopeTracker").cleanUp(application) /> <cfelse> <!---// use CF9+'s thread safe method of restarting the application //---> <cfset applicationStop() /> </cfif> <cfreturn true /> <cfcatch type="any"></cfcatch> </cftry> </cfif> <cfreturn false /> </cffunction>
While you should always test stuff for yourself, I did spend a good amount of time running various load tests to ensure that these methods were safe. I was running 20 concurrent users simulating heavy load and would randomly cycle in a request to reset the Application scope at least 10% of the time. I ran dozens of these tests, resulting in 50,000+ page views without seeing any errors or unexpected results—and that was just with one specific load test, I actually ran a couple different tests and they all worked as expected.
So, if you're looking for a safe way to "restart" (or "reset") the Application scope in ColdFusion 8 it appears the safest method would be to use the ApplicationKill() method listed above.
4 Comments
Comments for this entry have been disabled.