Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Kirsty Lansdell and James Allen
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Kirsty Lansdell ( @kirstylansdell ) and James Allen@CFJamesAllen )

Caching Function Outcomes With CachedWithin Caches Both The Return Value And The Output Buffer In Lucee 5.3.2.77

By Ben Nadel on
Tags: ColdFusion

One of the cool features of the Lucee CFML engine is that you can have it memoize the outcome of a Function if you define a cachedWithin directive in the Function's meta-data. The cachedWithin directive caches the outcome of the Function; and then, if the Function is invoked again with the same arguments, the cached result is applied and the processing of the underlying Function is skipped. One thing that surprised me as I was digging into this feature was that this memoization caches both the Function return value and the output buffer. Which means that any data that you write to the output buffer during the Function execution will also be applied to the cache.

To see this in action, I've set up a simple demo in which I have a Function that does three things:

  1. Writes to the system output via systemOutput().
  2. Writes to the output buffer via echo().
  3. Returns a value.

I then provide the cachedWithin="request" directive and proceed to call this Function with two different sets of arguments:

<cfscript>
	
	// I test the caching of Function "outcome" in Lucee.
	// --
	// NOTE: By using cachedWithin="request", the outcome of this Function will be
	// memoized based on its arguments.
	public any function testCaching() cachedWithin = "request" {

		// This WILL NOT BE CACHED - it does not relate to the "outcome" of the Function.
		systemOutput(
			obj = "SYSTEM OUTPUT: #serializeJson( arguments )#",
			addNewLine = true
		);

		// This WILL BE CACHED - it relates to the "outcome" of the Function.
		echo( "ECHO: #encodeForHtml( serializeJson( arguments ) )#" );

		// This WILL BE CACHED - it related to the "outcome" of the Function.
		return( "RETURN: #serializeJson( arguments )#" );

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Second line should consume cached outcome.	
	echo( "<p>" & encodeForHtml( testCaching( "a" ) ) & "</p>" );
	echo( "<p>" & encodeForHtml( testCaching( "a" ) ) & "</p>" );

	// Second line should consume cached outcome.	
	echo( "<p>" & encodeForHtml( testCaching( "b" ) ) & "</p>" );
	echo( "<p>" & encodeForHtml( testCaching( "b" ) ) & "</p>" );

</cfscript>

As you can see, each set of arguments is used to invoke the testCaching() Function twice. The first call primes the cache for the request; the second call consumes the cache within the request. Now, when we run the above Lucee ColdFusion code, we get the following page output:

The outcome a Function is cached using cachedWithin in Lucee 5.3.2.77.

Notice that with each invocation of the testCaching() function, we get both the return value and the changes to the output buffer (via echo()). And, to prove that the underlying Function is really only executing once (per set of arguments), let's look at the log:

Memoized Functions are only invoked once per unique set of arguments in Lucee 5.3.2.77.

As you can see, our systemOutput() call only fires twice - oncer per set of unique arguments. So, while the results of the echo() call are "applied" for each testCaching() consumption, we can see that the Function logic is truly skipped on subsequent calls with the same set of arguments.

Being able to cache the outcome of a Function without actually implementing any explicit memoization logic is a cool little feature of Lucee CFML 5.3.2.77. One thing to be aware of, however, is that the cached value contains both the return value and the alterations made to the output buffer.

Epilogue On CFContent And CachedWithin

Once I realized that the cachedWithin directive would cache the changes made to the output buffer, I wondered if it would also cache content that is written to the response stream via CFContent. To test, I ran this little experiment:

<cfscript>
	
	// I reset the output buffer and write a message to the response.
	public void function writeResponse() cachedWithin = createTimeSpan( 0, 0, 1, 0 ) {

		var message = "Now: #now().timeFormat( 'full' )#";

		content
			type = "text/plain; charset=utf-8"
			variable = charsetDecode( message, "utf-8" )
		;

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	writeResponse();

</cfscript>

Notice that, this time, the cachedWithin value is not request but rather a time-span of 1-minute. However, the cachedWithin directive doesn't appear to cache the variable written to the response; every time I refresh the page, I get a fresh time-stamp in the output.

It was worth a shot.



Reader Comments

Damn. Working with Lucee since early Railo days and never knew about this - thanks. I am loving these Lucee posts... keep it up!

Reply to this Comment

@All,

As a quick follow-up post, another behavior that was non-obvious to me, especially coming from a JavaScript-heavy background, was that input evaluation - in terms of caching - appears to inspect complex objects by value, not by reference. This is non-obvious to those coming from something like Redux / NgRx background:

https://www.bennadel.com/blog/3656-the-cachedwithin-function-memoization-feature-appears-to-compare-complex-objects-by-value-in-lucee-5-3-2-77.htm

Also, an empty Array and an empty Struct appear to be treated as the same value.

Reply to this Comment

@Andrew,

Thank you, good sir. It's fun to be digging into ColdFusion more deeply again. I've been on ColdFusion 10 for .... too long. So, being on Lucee now at work has reinvigorated my studies.

Reply to this Comment

Hi Ben. Just wondering why:

systemOutput

Does not relate to the outcome of the Function

And:

echo()

Does?

Surely, if something is executed within the function context, it is contributing to its outcome?

Reply to this Comment

@Charles,

I think because the systemOutput() is considered more of a "side-effect" of the function execution than an "outcome" of the function. In so much as the execution of a Function is expected to potentially return a value and it is expected to potentially write to the output buffer. But, nothing else inside the Function logic is "expected". Meaning, you would execute a query, write to a file, make an HTTP request - all of this is completely custom for that Function. It would not be feasible to include all of those possibilities in a caching strategy.

Of course, this all means that you have to use caching with function in which the caching makes sense :D

Reply to this Comment

OK. I see. That makes sense.

I had a look at the CF Docs Echo() definition and there was something I didn't understand:

CF Docs

"While writeOutput() writes to the page-output stream, echo() writes to the main response buffer"

Just out of interest is. What is the difference between, writing to the page output stream and the main response buffer.

Whenever I see the word "buffer", my mind goes blank:)

Reply to this Comment

@Charles,

Hmmmm, I have no idea :D I assumed they were the same thing. I have yet to see an example of echo() that didn't feel like a drop-in replacement for writeOutput(). I just thought it was a shorter alias.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.