Created
June 21, 2022 11:31
-
-
Save bennadel/bbbeb24a2b153ff2970a2b3e75e5f0f7 to your computer and use it in GitHub Desktop.
Considering A Stale-While-Revalidate Pattern To Caching In ColdFusion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
component | |
output = false | |
hint = "I provide access to a flag value which is refreshed asynchronously." | |
{ | |
/** | |
* I initialize the flag value. | |
*/ | |
public void function init() { | |
variables.currentValue = false; | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
/** | |
* I get the CURRENT flag value, which may be STALE. Accessing the flag value MAY | |
* trigger a BACKGROUND FETCH to re-cache the latest value. | |
*/ | |
public boolean function getValue() { | |
revalidateInBackground(); | |
// NOTE: For the sake of this demo, we're going to consider this value to be a | |
// thread-safe value. As such, we're not going to apply any locking around its | |
// access despite it being cached in-memory and shared across requests. | |
return( currentValue ); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
/** | |
* I spawn an asynchronous thread to revalidate the flag in the background. | |
*/ | |
private void function revalidateInBackground() { | |
var futureKey = "$$flagAsyncFuture"; | |
// Only run the async validation once per request (an optimization). | |
// -- | |
// NOTE ON LOCKING: Normally, when I only want to do something once, I would add a | |
// double-check lock around it. However, in this case, since the scope of the | |
// FETCH contention is a single request, I'm not going to worry about it. More | |
// than likely, there will be no race-condition (again, this is scoped to the | |
// request); and, even if there is contention in this case, the worst-case | |
// scenario is that we run the asynchronous check more than once, which is fine. | |
if ( request.keyExists( futureKey ) ) { | |
return; | |
} | |
try { | |
// CAUTION: We do not want to use the `.error()` method on the runAsync() | |
// result because doing so will turn it from an ASYNCHRONOUS call into a | |
// BLOCKING / SYNCHRONOUS call. Instead, we're going to use a try/catch block | |
// inside the closure so that we can retain the asynchronicity while still | |
// catching errors internally. | |
request[ futureKey ] = runAsync( | |
() => { | |
try { | |
systemOutput( "Revalidating in runAsync() closure.", true ); | |
// !! Simulating some sort of background flag change. !! | |
variables.currentValue = ! variables.currentValue; | |
} catch ( any error ) { | |
systemOutput( "ERROR REVALIDATING FLAG!!!!" ); | |
} | |
} | |
); | |
// Catch any errors when spawning the thread that powers the runAsync() function. | |
// -- | |
// CAUTION: I AM NOT SURE that this is strictly necessary. I don't know how the | |
// thread-pool exhaustion will manifest in the calling context. That said, I | |
// usually wrap my thread-spawning code in a try/catch block since I know that the | |
// CFThread tag will sometimes throw an error when no thread can be spawned. | |
} catch ( any error ) { | |
// Swallow thread-pool exhaustion errors for now... | |
systemOutput( "ERROR: runAsync() could not obtain thread.", true, true ); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfscript> | |
systemOutput( "+ + + + + + + + + + + + + + + ", true ); | |
systemOutput( "Starting Demo Request.", true ); | |
systemOutput( "Checking flag...", true ); | |
systemOutput( "--> Flag: [ #application.flag.getValue()# ]", true ); | |
systemOutput( "Checking flag...", true ); | |
systemOutput( "--> Flag: [ #application.flag.getValue()# ]", true ); | |
systemOutput( "Checking flag...", true ); | |
systemOutput( "--> Flag: [ #application.flag.getValue()# ]", true ); | |
systemOutput( "Ending request.", true ); | |
systemOutput( "- - - - - - - - - - - - - - - ", true ); | |
</cfscript> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment