I found a weird bug in ColdFusion 9's new caching system. It seems that when you cache an object that is based on a file, if you delete the file while the object is still in the cache, the cache becomes corrupt. To demonstrate this, I have created a ColdFusion user defined function that simply grabs a reference to itself from the system cache (based on the method name) and tries to update its own meta data:
<cffunction name="clicker" access="public" returntype="numeric" output="false" hint="I keep track of how many times I was clicked."> <!--- Get the refernce to this method from the cache. ---> <cfset local.this = cacheGet( "clicker" ) /> <!--- Get the meta data for this method. ---> <cfset local.metaData = getMetaData( local.this ) /> <!--- Param the click count to be zero. ---> <cfparam name="local.metaData.clickCount" type="numeric" default="0" /> <!--- Increment the click count and return the current click count on this method. ---> <cfreturn ++local.metaData.clickCount /> </cffunction>
As you can see, this method, "clicker", assumes that its reference was cached in the system cache at the ID, "clicker". Using this ID, it then grabs a reference to itself, params a click count variable, increments the variable, and then returns the current click count. Since ColdFusion function objects are first-class citizens, there should be nothing questionable about this.
Then, I created another ColdFusion page that uses the above UDF. However, rather than including it directly, it reads in the above UDF file as plain text and writes it to ColdFusion 9's new virtual file system (RAM disk). Then, the UDF gets included from the RAM disk, making the Clicker() method available in the page's variables scope. This method instance is then cached in the system cache and the RAM disk is cleaned up:
<!--- Read in the clicker file. ---> <cfset clickerCode = fileRead( expandPath( "./clicker.cfm" ) ) /> <!--- Write the clicker ColdFusion function code to teh RAM disk so that we can then include it into the current file. ---> <cfset fileWrite( "ram://clicker.cfm", clickerCode ) /> <!--- Include the RAM-based clicker method into the current page. This will make the method "clicker" available to the variables scope of this page. NOTE: We have a mapping to the ram disk as "/ram". ---> <cfinclude template="/ram/clicker.cfm" /> <!--- Get a reference to the first-class method object. ---> <cfset clickerMethod = clicker /> <!--- Add the clicker method to system cache so that the clicker method can refer to itself internally. ---> <cfset cachePut( "clicker", clickerMethod, createTimeSpan( 0, 0, 5, 0 ), createTimeSpan( 0, 0, 5, 0 ) ) /> <!--- Now that we have included the clicker method and have a handle on the function object, we can delete the code from the RAM. ---> <cfset fileDelete( "ram://clicker.cfm" ) /> <!--- Execute the clicker method a few times to see that it is successfully working - retrieving itself from the system cache and updating its own meta data. ---> <cfoutput> Clicked: #clicker()#<br /> Clicked: #clicker()#<br /> Clicked: #clicker()#<br /> Clicked: #clicker()#<br /> Clicked: #clicker()#<br /> </cfoutput>
As you can see, once we have a handle on the clicker Function object, we store it for 5 minutes in the system cache and delete the scratch code file from the RAM disk. Since ColdFusion treats Function objects as first-class citizens, there should be nothing odd about this and nothing that requires the Function object to have a corresponding code file. However, when we run the code above, rather than seeing the click count output, we get the following ColdFusion error:
An error occurred when performing a file operation lastModified on file /clicker.cfm. The cause of this exception was: org.apache.commons.vfs.FileSystemException: Could not determine the last modified timestamp of "ram:///clicker.cfm" because it does not exist.
If I go back and comment out this line:
<cfset fileDelete( "ram://clicker.cfm" ) />
... then everything works fine and when we run the page, we get the following output:
It seems that there is a bug in the ColdFusion 9 cache system where the cached objects need to correspond to a physical files. Because Functions and Components are so dynamic at runtime and can be manipulated to be completely different than their original definitions, it seems wrong that any physical file would be required for simple caching.
Want to use code from this post? Check out the license.