CFCs Are Cached, CFIncludes Are Not
As a design pattern, developers like to create and cache Singleton objects in persistent scopes within an application. To do this, you basically create a ColdFusion component instance and store it in something like the APPLICATION scope (or within another object stored within the APPLICATION scope). This is, effectively, caching that instance for later use. One of my readers emailed me today and told me that they decided not to go with a mixin-based hack because, while the parent ColdFusion component was caching properly, the code within the included file was not.
I don't want to argue whether or not this mixin was an appropriate solution, I want to talk about caching behavior. If you stop to think about the nature of the CFInclude tag, the caching he talked about makes a lot of sense. CFInclude tags are dynamic, in that they can change at run time:
<cfinclude template="#some_calculated_runtime_path#" />
Therefore, files included into a ColdFusion component cannot be cached just because the parent CFC is cached. If that were the case, then CFIncludes would have to be valid at compile time and I am sure that would make us all very unhappy.
Regardless, I thought you guys might like to see this in action, to explore caching behavior, just so no one gets confused about it. To demonstrate this, I have set up a small application that does nothing but creates and caches a single ColdFusion component:
<cfcomponent
output="false">
<!--- Define application settings. --->
<cfset THIS.Name = "App Cache Test" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
<!--- Define page settings. --->
<cfsetting
showdebugoutput="false"
requesttimeout="10"
/>
<cffunction
name="OnRequestStart"
access="public"
returntype="boolean"
output="false"
hint="Fires when the current page begins to process.">
<!--- See if we need to re-init. --->
<cfif (
StructKeyExists( URL, "reset" ) OR
(NOT StructKeyExists( APPLICATION, "Cache" ))
)>
<!---
Create a Cache instance and store it in the
APPLICATION scope.
--->
<cfset APPLICATION.Cache = CreateObject(
"component",
"Cache"
) />
</cfif>
<!--- Return out. --->
<cfreturn true />
</cffunction>
</cfcomponent>
The Cache.cfc ColdFusion component then turns around and includes a function, mixin-style:
<cfcomponent
output="false">
<!--- Include the Function mixin. --->
<cfinclude template="function.txt" />
</cfcomponent>
The function.txt file then outputs some text and in turn, includes another file:
<cffunction
name="Go"
access="public"
returntype="void"
output="true"
hint="Outputs two values.">
<p>
From function.txt: [[ DEFAULT ]]
</p>
<!--- Include sub-function. --->
<cfinclude template="sub_function.txt" />
<!--- Return out. --->
<cfreturn />
</cffunction>
This is the sub_function.txt being included into the function.txt code above:
<p>
From sub_function.txt: [[ DEFAULT ]]
</p>
Let's take a moment to review whats going on here. The Cache.cfc ColdFusion component includes another function via CFInclude. That function then includes a chunk of text from another file. These are both includes, and therefore there paths are evaluated at run time, but be careful. Since the Cache.cfc is only being compiled once, the function.txt is only being included once (at instantiation of the Cache.cfc). The sub_function.txt, on the other hand, is being evaluated every time the Go() function is being invoked.
Ok, so now, let's create an index file that tests the caching:
<p>
First Run
</p>
<!--- Run original code. --->
<cfset APPLICATION.Cache.Go() />
<!---
Loop over the two files and replace the
[[ DEFAULT ]] text with [[ BLAM ]] text to
see where caching is effective.
--->
<cfloop
index="strFileName"
list="function.txt:sub_function.txt"
delimiters=":">
<!--- Read in file name. --->
<cffile
action="read"
file="#ExpandPath( './#strFileName#' )#"
variable="strFileContent"
/>
<!--- Replace DEFAULT with BLAM. --->
<cfset strFileContent = Replace(
strFileContent,
"[[ DEFAULT ]]",
"[[ BLAM ]]",
"ONE"
) />
<!--- Write the file back to disk. --->
<cffile
action="write"
file="#ExpandPath( './#strFileName#' )#"
output="#strFileContent#"
/>
</cfloop>
<p>
Second Run
</p>
<!---
Run again with modified code that has not
been re-cached.
--->
<cfset APPLICATION.Cache.Go() />
Here, we are calling the Go() method once. Then, we are overwriting the data files with new code. Then, we are invoking the Go() method for a second time in the same page request. Running the above code, we get the following output:
First Run
From function.txt: [[ DEFAULT ]]
From sub_function.txt: [[ DEFAULT ]]
Second Run
From function.txt: [[ DEFAULT ]]
From sub_function.txt: [[ BLAM ]]
Notice that text output in the function.txt file stays the same ([[ DEFAULT ]]) even though its corresponding code file was updated. Notice also that the text output in the sub_function.txt file changed to read "[[ BLAM ]]" in the second run. Again, this is because the function.txt file was included once at CFC instantiation time and the sub_function.txt file is dynamically evaluated once per method execution.
This is just a subtle point, but it makes a whole lot of sense when you stop to think about how CFInclude works. Also, I am unclear as to the proper use of the phrase "compile time" and "run time", but I hope you get the jist of what I am saying.
Want to use code from this post? Check out the license.
Reader Comments
Yeah, everything is compiled. The issue is simply what code is executed.
When a cfinclude is executed, the target file is compiled if it hasn't been already and then the compiled (include) file is executed. In many ways, it's helpful to think of an include as a function call really: the compiled (include) file is "called" by the code that includes it.
A lot of people seem to think cfinclude is some sort of textual include but it absolutely is not (as you are attempting to prove here).
Great blog - lots of thought-provoking stuff. Keep it up!
Oh, and just a nit-pick: application scope is not "persistent". That's a terrible piece of bad terminology that's been in the CF docs for years. File systems and databases are persistent. Memory is never persistent (well, unless it's special memory!). Persistent means you can turn the machine off and then on again and the data is still there.
Cookies can be persistent (or not, if they are browser session cookies). Client scope is persistent (cookies or database). Session, application and server scope are not. They could be called shared scopes - since they are shared between requests, sessions and applications respectively.
Sorry, just one of my hobby horses!
@Sean
indeed, the root of this evil can be found in original ColdFusion Docs like this one: http://livedocs.adobe.com/coldfusion/7/htmldocs/00001148.htm
The document is called: "About persistent scope variables" and lists the Client, Session, Application and Server scopes.
@Ben
Thanks for this dealing with my questions that explicitly! In my own tests, the "first-level" is also not cached! Weird. I need to make some additional testing. Is your method dependent of any ColdFusion Server Setting?
Let's assume, I have a component called "MyComponent" with a function called "Test". Inside of the "Test" function, i include the file "test.cfm" which is empty, followed by some test output. I create the "MyComponent" instance in Application scope just like you did. Now, when calling the "Test" Function, it displays the test output. When I edit the Function (insert a CFABORT before the test output) and run the page, it will still display the text, because the component is cached. But when i insert the CFABORT tag inside my empty "test.cfm" include and run the page, it will break. That means, my first-level include also was compiled on runtime. Which differs from your experiences.
@Sean
>> Persistent means you can turn the machine off and then on again and the data is still there. <<
It also means something that never goes away (ie: never turns off), and a few other definitions - like most English words.
Simply using the word 'persistent' doesn't mean something is saved to disk, and assuming it does is a worse crime than using it in context to describe something that persists across requests/clients/etc.
Ben's use of "persistent scopes within an application" is not at all bad terminology - it is a valid use of the word.
*removes pedant hat*
:)
@Sean,
Terminology is not my strong suit by any means. Persistent seems like an easy way to explain the difference in scopes. For instance, SESSION is a user-specific global scope that persists across page calls were as REQUEST is a user-specific global scope that does NOT persist across page calls.
To me, whether this is technically right or not, feel like an easy way to teach. However, at the same time, I would not want to be mis-educating people... can you think of a better terminology to explain the different between scopes that last across page calls?
@Thomas,
It sounds like what you are doing is the same as what I am doing.
@Ben
exactly... The only difference is that I don't include the cffunction code (function.txt), its simply inline-/hardcoded. This means, that when you would hard-copy the content of function.txt into Cache.cfc, you would get the same behaviour.
Regarding this case, your example might be irritating, because it's suggestive of beeing dependent of some kind of "include level" (include or sub-include) but it isn't really. Interestingly, looks like includes are compiled and cached when included in the root-level of the cfc, but not when included inside of functions? Looks like I need even more testing ;) If you put a CF syntax error in your "function.txt" file (unknown tag, not closing a tag or something) will it cause an error when compiling Cache.cfc? It should, I guess.
@Peter, whilst it doesn't specifically mean saved to disk, it does mean it exists "permanently", across machine restarts etc. The most common way to persist data is to store it on disk.
Some memory can be persistent, of course, if it retains its information in the absence of external power.
@Thomas, I think you're confused about Ben's example.
In his Cache.cfc, he includes a file in the pseudo-constructor area (outside any functions) and that will only be executed *once* when the object is created (and then stored in application scope).
Inside a function that you call, any included file is executed every time you call the function.
Using the word "persistence" is fine from an English stand point.
http://dictionary.reference.com/browse/persistence
The below statements work just fine:
Persist means that something has the property in which it continues to exist past a determined point.
The application scope persists between requests.
Thus, the application scope is persistent.
And yeah, we can nitpick at this, but then again we'd be setting our selves up for all kinds of shenanigans.
For instance, if you create a persistence layer and then swap out the persistence mechanism, which was a disk based database, with an in memory database (MySQL supports this) instead, is it still a persistence layer at all?
Maybe. Sean talks about surviving a restart, but the definition at http://en.wikipedia.org/wiki/Persistence_%28computer_science%29 contradicts that since it says "that outlives the execution of the program that created it," (does give an example about restarts, but doesn't say it's a requirement) and the MySQL database can certainly do that even if the JEE server restarts, but it definitely wouldn't survive a system restart.
And it all gets even more muddy when we think that filesystems can exist entirely in memory, so that "persistence mechanism" in our code, such as writing the file to "disk," may not be NV memory at all.
Anyway, my real point is that you should be weary of getting too pedantic at either end of the spectrum on issues like this because somewhere around the corner there's a ruby user who's going to stab you in the back when you call that thing a static method since it's really a class method, when in fact the terminology means the SAME DAMN THING. :)
@Ben, I'm a little surprised you allowed an *anonymous* (read: COWARDLY) ad hominem drive by comment...
@anonymous, grow up and use a real name and stand behind your insults!
@Sean,
Sorry about that. Been behind in my emails so I didn't see the alert. People are lame. His/Her comment has been removed. I don't even know what they were talking about??? I reread your comments and found nothing off color for my taste? People just freak out sometime.
Ben, you always seem to come up with obscure stuff that applies to my projects. Kudos to you.
We have alot of functions that a number of programmers are working on so putting the contents of those functions info separate files helps since we haven't implemented a good version control system as of yet (long story). Anyhow, we saw this and did some of our own tests.
Just to clarify, we noticed that if you put an include inside of a function within your component it will not cache the contents of the include where as if you put the full function inside of the include, then it will cache it.
The below assumes that the CFCs are instantiated into an application variable when the application first runs.
Include Cached : (whole function is in the myfunction.cfm)
<Cfcomponent>
<cfinclude template="myfunction.cfm">
</cfcomponent>
Include NotCached : (only body of function is in myFuctionContents.cfm)
<Cfcomponent>
<cffunction name="myfunction">
<cfinclude template="myFunctionContents.cfm">
</cffunction>
</cfcomponent>
These were our results on the matter.
Thanks again Ben,
~Chris C.
@Chris,
Good testing. I guess that makes sense since it allows ColdFusion to compile the entire function into a stand-alone Java class (maybe)??
It is not caching the include. The CFC is cached (in application scope) and the pseudo-constructor is run once when the CFC is created - which is when the include is run. In other words, in your first example, you are only running the include once. In the second example you are running it every time you execute the function.
@Sean,
Ah, nice explanation.
I haven't been able to find it yet --
But how do you CLEAR this cache ? I can't stand the inability to do this simply.
(cfwheels)
<cfcomponent extends="controller">
<cfset renter = model("renter").new()>
When not in a function -- cached.
When deleted, cached.
Wheels.TableNotFound
The renters table could not be found in the database.
6: <cfset renter = model("renter").new()>
What a USEFUL feature, but the double edged sword is that it's a PITA for development.
Notes: All cache features turned off in CFAdmin, it's coldfusion 8 and grr..
I need to ask the same question as above, and how do you clear this caching?
I have a cfc throwing an error, which I fix and repost, and still in error. I add an error to the page, and it's still only the old error. I *delete* the page, and it's still working.
Before I break out the the proton packs and trap, how can I flush this? I tried both clear buttons on Caching page, and still nothing.