Safely restart the Application scope in ColdFusion 8 and above

Posted by Dan on Mar 28, 2011 @ 3:06 PM

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:

  1. Creates a random key in the Application scope
  2. Spawns a thread to re-initialize your application using your secret reinit key/value
  3. While the other thread has been spawned, the main thread waits 5 seconds so we have enough time for the re-init script to run
  4. Attempt to output the variable in the Application scope.

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.

Categories: HTML/ColdFusion

4 Comments

  • Here's another little gem I got from Mister Dai:

    <cfset application.setIsInited(false) />

    Basically, this tells CF that your OnApplicationStart() method has not been run so that it runs it again on the next request.
  • @Eric:

    One thing I was trying to avoid was running into race conditions--and I'm not sure that the setIsInited(false) method avoids those.
  • Does any one know why method cleanUp does not show when you run "createObject("java", "coldfusion.runtime.SessionTracker")".

    It used to show, but since we ran "The DISA Security Technical Implementation Guides (STIG)", it disappears and the logout page through an error message "The cleanUp method was not found."



    ======Return a list of methods after ran createObject("java", "coldfusion.runtime.SessionTracker")====
    object of coldfusion.runtime.SessionTracker

    Class Name coldfusion.runtime.SessionTracker
    Methods Method Return Type
    createSession(java.lang.String, java.lang.String) coldfusion.runtime.SessionScope
    createSession(java.lang.String, java.lang.String, java.lang.String) coldfusion.runtime.SessionScope
    createSession(javax.servlet.http.HttpSession, java.lang.String) coldfusion.runtime.SessionScope
    getMSessionPool() java.util.Hashtable
    getSession(java.lang.String, java.lang.String, java.lang.String) coldfusion.runtime.SessionScope
    getSession(java.lang.String, java.lang.String) coldfusion.runtime.SessionScope
    getSession(javax.servlet.http.HttpSession, java.lang.String) coldfusion.runtime.SessionScope
    getSession(java.lang.String) coldfusion.runtime.SessionScope
    getSessionCollection(java.lang.String) coldfusion.runtime.AppSessionCollection
    getSessionCount() int
    getSessionKeys() java.util.Enumeration
    sessionInvalidate() void

    Fields Field Value
    int mSweepInterval 10000
  • @Kimberly:

    I'm unfamiliar with the document you're talking about. Have you run any recent CF updates/hotfixes?

    One issue with using "undocumented" methods is you always run the risk of ColdFusion removing support for them.

    I really suggest you ask your question on an ColdFusion forum and/or mailing list. You're much more likely to get a useful response.

Comments for this entry have been disabled.