Clearing template cache automatically through the ColdFusion gateway .... varScoper bundle NOW included
Coldfusion Add commentsA bit back Brian Szoszorek showed a wonderful example on how to use Cold Fusion's gateway functionality to help streamline the use of the Trusted Cache. This article was brought to my attention by a fellow co-worker (Mike Schierberl) and after reviewing the article in detail I was stunned on how powerful this was and how easy it was to implement. So I decided to tweak it here and there to see what else it could do.
Here are the tweaks:
- Incorporated Mike Schierberl's varScoper as an optional feature. If you update or add a cfc it will run a varScope check against it and log it into a VarScoper.log file within cold fusions log folder. Auto-watch it threw your favorite file watching tool (Example: Logwatcher for cfEclipse) and you have a realtime varScoper running telling you if there are any unscoped vars within the file you just added/updated. For varScoper fans I see this as being very nice!
- Made the clear caching method to be just a little smarter (ability to just removing specific files from the cache rather than clearing the entire cache itself)
- New method "setEventGateway" that will create the needed cfg file and gateway entries within Cold Fusion Administrator. Very Cool.. Very Powerful.. and Very Easy!
You can download the code here.
NOTES:
There seems to be a few bugs within the adminapi interface for clearing the cache. Currently when you attempt to remove a file from the cache after a file has been removed you receive an error stating it cannot remove it from the cache since the file does not exist on the server. To fix this we just clear the entire cache when a file is removed. Another bug is when you delete, then add the file back into the cache. For some reason it does not pick it up. To fix this we do clear cache call on the file being added. I'm not sure why this works, but it does. I will be posting this to Adobe to see what's up.
Here are the steps on how to implement:
1) Create a new directory that the component will live in and place the component in there
a. Example: C:\inetpub\wwwroot\templateListener
2) Set the cfpassword variable to reflect your cfpassword. This needs to be hardcoded within the component in order for the gateway to work properly.
3) Access and run the setEventGateway method provided within the component and provide it the directory you want to watch
a. Example: #createObject("component", "templateListener").setEventGateway(directoryToWatch="c:/inetpub/wwwroot")#
4) Turn on Template Caching within Cold Fusion Administrator by (CF Admin -> Server Settings -> Caching -> Trusted Cache)
5) Confirm that the Gateway Instance is running by (CF Admin -> Event Gateways -> Gateway Instances)
6) If you want to turn off the varScoper check then you can turn it off by adjusting the "runVarScoper" flag at the top of the component to false
May 16, 2008 at 10:33 PM This is great stuff, I grabbed your code and I'm gonna try this out... we run into this issue all the time in dev and stage. I'll let you know how this works for me - sounds pretty straight forward.
May 17, 2008 at 10:29 AM @ Pat,
I work with Dan and he linked me in to this post. Gotta say I really like the changes you made to Brian's class. When a server is under load like we deal with clearing the template cache is enough to necessitate removing the server from the load balancer while it processes the clear. Making it more pinpointed to the changes files will make this approach for more enterprise friendly. Whats interesting is Blue Dragon actually does provide a "dev" version of template caching, though it is a little confusing just how it works.
Whats funny is last night I sat down to make the exact same modifications to the original class that you did because it wasn't a realistic solution for us prior to those modifications. Thanks for saving me some time and effort.
-Matt
May 17, 2008 at 6:01 PM @Matt
Glad to hear it will help you out. When I first saw it I was pretty thrilled about how simple it could be implemented.
May 18, 2008 at 5:37 AM I would love to tie it into coldspring and have it detect if any of my classes changed at the IO level and restart them but I don't think its going to be a very realistic option, attempting to restart individual classes, given the dependent nature. Probably going to be stuck forcing a clean load when any file referenced in my services.xml changes, which is still way better then blindly restarting all my classes with each change I deploy.
May 19, 2008 at 7:21 AM @Matt
Right now I don't believe CS provided a view into its caching mechanism to clear individual beans. You can only rebuild the bean factory, which makes since now that you are trying to work with created objects. Rebuilding the factory may be the safest bet (clear out all references, etc).
I do have a recommendation on how to possibly rebuild the factory by using the event gateway as a helper though.
What you could do is build a server scope variable that flags a boolean stating to rebuild the BeanFactory itself. Try setting it within the event gateway (to true). You will want to set this flag whenever a file is added, updated or deleted. Then within your application.cfc (onRequestStart) check the variable and if it is true then rebuild the factory. Don't forget to set it back to false after the factory has been rebuild otherwise you will always rebuild your factory after the variable has been set to true for the first time. Make sense?
NOTE: If you have multiple environments on one system then this could get a little more complex. But I think you get the picture and if you do have multiple environments let me know and I will help figure something out as this will play well into what I am doing for my stuff too.
Let me know how this goes. I have not tried it, but theoretically I believe it should work.
May 19, 2008 at 8:56 AM @ Pat
I started going down this path and I am probably going to do something similiar to what you proposed. However I am going rebuild the serviceFactory in a separate shared scope then inject it in right as I restart my classes in onRequestStart. This way there is only the briefest lock around that transfer of data between scopes, not ideal from an OO standpoint but it will reduce my lag on a restart.
Also In implementing your code this morning I ran into a bug with deleting items from the IO. When you delete a file the event gets triggered after the file was deleted (logically) and the adminAPI fails to allow you to clear the cache unless the file exists on the IO (bug if CF if you ask me). However I wrote work around which is not beautiful but better then the alternative of clearing the entire cache.
<cffunction access="public" name="onDelete" output="no"><cfargument name="CFEvent" type="struct" required="yes">
<cfset var tempFile = "">
<cfset var path = "">
<!--- call our clearCache function --->
<cfset data=CFEvent.data>
<!--- create temporary flag file --->
<cfset path = replace(data.FileName, listLast(data.FILENAME, "/"), "")>
<cfset tempFile = hash( listLast(data.FILENAME, "/") )>
<!--- if the hash file exists delete it otherwise create it and delete/clear the original file --->
<cfif ! fileExists('#path##tempFile#')>
<cffile action="write" file="#path##tempFile#" output="">
<!--- create original file that wast just deleted so we can clear the cache on it --->
<cflock name="#data.FILENAME#" timeout="1"><!--- we lock this as it appears that the write is not completing by the time the clearCache command is called below --->
<cffile action="write" file="#data.FILENAME#" output="">
</cflock>
<cfinvoke method="clearCache" template="#data.FILENAME#">
<cffile action="delete" file="#data.FILENAME#">
<!--- log this action --->
<cflog file="TemplateCacheEvents" application="No" text=" ACTION: #data.type#; FILE: #data.filename#">
<cfelse>
<cffile action="delete" file="#path##tempFile#">
</cfif>
</cffunction>
May 19, 2008 at 9:33 AM @Matt
Interesting... You know since you would be doing a delete and chances are you will not be doing a lot of them it might be best to just clear the entire cache when you do a delete instead of the above workaround. It leaves less to worry about and keeps it within the KISS process.
May 19, 2008 at 9:36 AM I dunno about the clearing the whole cache thing. We discussed that, it largely depends on the size/complexity of your app. Us clearing our cache would cause a slowdown for thousands of concurrent users as ALL KINDS of templates get re-cached across our app. That's why we did this single file workaround.
For smaller apps with less concurrent users, I'd agree.
May 19, 2008 at 9:37 AM @Matt
Interesting... You know since you would be doing a delete and chances are you will not be doing a lot of them it might be best to just clear the entire cache when you do a delete instead of the above workaround. It leaves less to worry about and keeps it within the KISS process.
May 19, 2008 at 11:43 AM @Matt
When you have a moment check to see if you are seeing the same thing I am. Try removing a file from cache, hitting the template, then add it back in (create the file again). Does the cache see it? Mine does not and I needed to make a small adjustment to get it to work.