Noted. Thanks.
I know Iâm joining in late, folks. And Iâm about to suggest something that no else has in this thread, which I do with trepidation. And I may be bringing an ACF bias, so Iâm open to being schooled, but Iâm not sure thatâs the case.
So correct me if Iâm wrong, folks, but it seems to me that the var in which one stores the RESULT of a query using cachedwithin should have no bearing at all on how the caching mechanism itself works, including any NPE one may get from the caching engine.
Doesnât the variable simply hold the RESULT of the query, for subsequent processing in that request (or others, if a shared scope), regardless of whether the cachedwithin resulted in the query result being cached or obtained from cache when it was put in that variable? Iâd think the varâs scope really should have no bearing on that CACHE process on the right-hand side of the variable assignment.
And I think thatâs what Zacâs reply was getting at, when he said that âwhen you use cachedWithin, it creates a hash of the arguments passed in the caching mechanismâ. I do think he was speaking there specifically of using cachedwithin in a function (which for now ACF does not support), given the context at the time and his mention of" arguments". But it would seem to also suggest thereâs no connection to the var holding that function result. Can you confirm, Zac?
And caching a query in Lucee would seem similar. I can say that in acf, the key for caching a query would have be the sql as evaluated, as well as the dsn and any user name. It literally has nothing at all to do with the ânameâ in a cfquery. (Nor would one need technically to even assign the queryexecute to a variable, to have it do its cache processingâthough of course you would need either if you wanted to process the query results.) Could Lucee work differently? Thatâs one reason I make my comment here, to ask.
As for the NPE BKBK said he got in ACF, there may well be reasons having nothing to do with this variable assignment, specifically (and Iâd think there must be).
As for the NPE youâre now getting, BKBK, it could help to hear if you have found something that can âpreventâ that NPE in Lucee, with this demo of caching a query. That may lead to some other solution, or perhaps identification of a bug, or it might well contend against (or confirm) my assertion here.
And to be clear, Iâm not at all addressing above the pros cons of what scope one may choose to use to hold the result of something, whether that things was cached or not. There can certainly be value in saving a query result or function in the application scope, for instance, for subsequent use by others. (How one goes about that has its own challenge and options, of course.) Iâm just saying Iâd not at all expect there to be any impact of that choice of scope on the process of CACHING of that result, or even the OBTAINING of that result from cache.
Does that make sense, or do you or does anyone feel I have things wrong? I really am trying to help, not merely to be argumentative.
100% correct
Agreed. One could easily confirm by running a simple cached query select 1
and dump.
Run the same (different variable) and dump it.
Check out the Cached: Yes/No and then you will know what to expect. I believe I suggested this and it was perhaps missed / misunderstood.
firstly, i know coz iâve read the source code,
secondly, that would be quite a lot more work to implement something so quirky
I know the later, coz Iâd like to improve some types of exception in lucee, but we donât track that info
Thanks for that confirmation, Zac.
@carehart , thanks for chiming in.
I should make something clear. I am aware of how the cachedWithin mechanism works in Lucee and in Adobe ColdFusion. That is NOT what my latest question is about. The caching discussion, however interesting, is a diversion. Thatâs how I see it anyway.
My question is about the fundamental principles on which Lucee and Adobe ColdFusion are designed. I think that it is wrong to use the cache-provider to explain the behaviour of queryExecute. A well-designed function should encapsulate its inner workings. The caller shouldnât be aware of the wheels turning underneath.
Let me take it again from the top, using bare minimum code for illustration.
query function getRecords() {
var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)});
}
Assume weâre currently within the context of the function getRecords. The function getRecords in turn calls the function queryExecute.
In software design terms we may consider getRecords to be the client calling the queryExecute service. Normally, assuming all goes well, the client (getRecords) expects the service (queryExecute) to return the following result:
- a query object.
- the value of the query object remains unchanged in memory for 1 day.
The client (getRecords) neednât know anything about and shouldnât care about how the service (queryExecute) got the result. In other words, getRecords is - or, should be - totally oblivious to any underlying machinery responsible for producing the result.
If youâre an object and you, in turn, call getRecords, then you will be the client of the getRecords service. You, too, will expect from getRecords the same result as getRecords expected from queryExecute: a query object, with the same value available in memory for a day.
To repeat: your contract with the service is that the query object be available in memory for a day. You, the client, neednât know anything about caching.
Software principles such as responsibility (GRASP), design-by-contract and separation-of-concerns come into play here. These may sound pretentious and highfalutin. But they come from real life.
You get a pint of milk at the supermarket. It is fresh, has a temperature of 3 degrees Celsius and will keep for 2 to 3 days. Thatâs all you need to know about that service.
You neednât know about the number of cows involved, the state of their udders, whether the farmers used manual or mechanical pump action and how the milk got to you.
I hope I have been able to convince you that, the moment you use the cache-provider to explain the behaviour of queryExecute or getRecords, something is amiss.
With that out of the way, on to the question.
query function getRecords() {
var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)});
}
The right-hand-side of the assignment represents a persistent object in memory. So, we would expect it to be in memory when getRecords() is repeatedly called, say, within an hour. However, the left-hand-side defines a transient variable which may no longer exist between getRecords() calls. Isnât this inconsistent?
If so, then Lucee and Adobe ColdFusion neednât worry about the NullPointerException. For, assigning a cachedWithin query to var scope would simply be bad coding practice.
Yep Jason, I caught your mention of that earlier in the thread, sure. And I agree with what you said. I just didnât see it as specifically addressing the point Iâm now raising: that the scope of the variable used does not affect the caching. I see youâre saying now that one could use the dump to confirm the state of caching. Yep, on that.
Iâll have more to say in another commentâwhich probably should be split off into a separate topic. Iâll leave others to judge/do that.
Zac, itâs unclear who or what that is referring to. I donât see anyone proposing any new ideas between your previous comments here and this one. I almost think you may have been meaning to reply to some other thread, or perhaps a deleted comment from someone there. Can you clarify?
And I really want to clarify that because I am just about to make a comment that WILL propose somethingâŠand I just want to be clear to readers that his comment canât (yet) be referring to that.
In recent years, we saw hundreds of NullPointerExceptions resulting from cachedWithin queries being assigned to var scope in Adobe ColdFusion.
Here are 2 examples, on CF2018:
âErrorâ,âajp-nio-127.0.0.1-8030-exec-14â,â11/02/20â,â07:28:22â,âstxxxmeterLive2018â,
"ââ The specific sequence of files included or processed is:
E:\wwwroot\data_stxxxmeter_nl\htdocs\scheduled\ImportNatschool.cfm, line: 51
"java.lang.NullPointerException
at coldfusion.tagext.sql.QueryTag.setupCachedQuery(QueryTag.java:1186)
at coldfusion.tagext.sql.QueryTag.startQueryExecution(QueryTag.java:833)
at coldfusion.tagext.sql.QueryUtils.executeQuery(QueryUtils.java:72)
at coldfusion.runtime.CFPage.QueryExecute(CFPage.java:12450)
at cfUserMethods2ecfc802731659$funcSETMETHODINFO.runFunction(E:\wwwroot\data_stxxxmeter_nl\htdocs\nl\devxxxx\stxxxmeter\orm\UserMethods.cfc:51)
var qProductMethod=queryExecute(sSql, stSqlParams, {cachedwithin: createTimeSpan(0, 24, 0, 0)});
âErrorâ,âajp-nio-8014-exec-6â,â09/19/19â,â13:49:41â,âstxxxmeterStaging2018â,
"ââ The specific sequence of files included or processed is:
E:\wwwroot\staging_stxxxmeter_nl\htdocs\nl\devxxxx\stxxxmeter\facades\UserDataFacade.cfc, line: 239 "
java.lang.NullPointerException
at coldfusion.tagext.sql.QueryTag.setupCachedQuery(QueryTag.java:1186)
at coldfusion.tagext.sql.QueryTag.startQueryExecution(QueryTag.java:833)
at coldfusion.tagext.sql.QueryUtils.executeQuery(QueryUtils.java:72)
at coldfusion.runtime.CFPage.QueryExecute(CFPage.java:12254)
at cfUserData2ecfc795832988$funcGETUSERRESULTSTOTALS.runFunction(E:\wwwroot\staging_stxxxmeter_nl\htdocs\nl\devxxxx\stxxxmeter\service\UserData.cfc:239)
var qUserResultsTotals=queryExecute(sSql, stSqlParams, {cachedwithin: createTimeSpan(0, 24, 0, 0)});
BKBK, I donât see that as âinconsistentâ. I do see it as potentially confusing, sure. But to be clear, the result of the queryexecute on the right will always produce some query result: either it will come from cache or it will not, depending on things like that cachedwithin timespan.
But then that query result (however it was obtained on that right-handside queryexecute) will in this case be put in a variable whose life will only be within the method, given the var scope.
I suppose one could argue that the inconsistency comes from a seeming mix of âlifetimesâ. But the two things (on the right and left) are very different. To use your analogy, one is the producer (rhs) and one is the consumer (lhs).
As for the caller to the method, sure, they would seemingly not really care at all about ANY of this (lifetime of the var scoped variable, or whether the result was or wasnât cached).
But this gets muddled by a couple of things. First, your function isnât returning anything at all. So really, one could say that the variable assignment here is entirely moot.
On the other hand, the caching of the queryexecute is really a side-effect. One might make a purist argument that a method being self-contained should not CAUSE such a side-effect, sure (which it will, in possibly caching the result). But as soon as you choose to use caching in the method, youâve âbought that milkâ, in your analogy.
But back to the point I was making, it really makes no difference at all (to the CACHING of the query on the rhs) whether you do or do not use a var scoped variable for holding its result.
As I did touch on, one could certainly argue that if the lhs variable were an application scope (or any shared scope, like session or server or Luceeâs cluster), that WOULD of course make the result be available âoutsideâ the function. And that, too, could be seen to be an undesired side-effect, to those who want to prevent such things.
Hope that helps clarify something.
BKBK, in those cases (or in the case you are now working with here in Lucee), are you confirming that as soon as you change it to NOT be a var scoped variable, the NPE goes away? That would really surprise me, again because to me thereâs no bearing on the caching process on the rhs of the assignment (the queryexecute), relative to then saving the result (whether it was in cache or not, or saved to cache or not) in the var on the lhs.
(And if anyone reading along may be confused about lhs/rhs acronyms, that refers to left-hand side and right-hand side of an assignment. I clarified it a couple of times earlier, but dropped into the acronyms later. And I realize some folks donât read every message. No disrespect meant to anyone in clarifying lhs/rhs. We canât know who may read this days, weeks, or months later.)
There are several things being discussed here.
Regarding NPEs in Adobe, this is a bug in Adobe and you should report it in their ticket tracker. It has nothing to do with scopes, is not by design, and has no relation to any NPEs you may have encountered in Lucee or 3rd party Ortus extensions.
Regarding NPEs in the Ortus Redis cache extension, this was a bug in Ortusâ extension that has been fixed in the latest version. It had nothing to do with scopes, was not by design, and had no relation to any NPEs you may have encountered in Adobe or Lucee.
Regarding using scopes to cache data. This is fine, but itâs not really a matter of which scope is âbetterâ. Given the documentation of the live cycle of each scope, you use the one that fits your needs. I.E
-
server
if you want to cache for as long as the server is up -
application
if you want each application to have a copy and you want to cache for as long as the application exists -
session
if you want each session to have a copy and you want to cache for as long as the session exists -
request
if you want each request to have a copy and you want to cache for as long as the request exists - etc, etc.
Now, that said, none of that is related to using a caching implementation which uses an out-of-process store which seems to be what youâre doing.
Regarding using cached within (whether on the function or the query). There is also nothing wrong with this. It does not necessitate you using any scope other than local
to hold the result, and the NPEs you experienced in Adobe and the Ortus extension were just unfortunate programming bugs.
Just to clarify, this is not quite true. The caching assures you the same equivalent query data will be returned every time you run it. There is no guarantee of where or how the cache has decided to (possibly serialize) and store your query.
I donât know what you think Zac said, but youâre arguing against a vapor cloud. Everyone here understands encapsulation-- Zac was more making commentary on how the caching is deterministic against the inputs (which should be fairly obvious, otherwise it would be pretty useless.)
Youâre conflating your local
scoped variable which contains a pointer to the object in cache. Just because the local scope goes away after the function is done executing doesnât mean the query isnât in the cache.
application.longLivedData = [ 'foo' ];
session.bar=application.longLivedData;
request.baz=application.longLivedData;
local.bum=application.longLivedData;
There is one array in memory and it will exist in memory until there are no more references to it. The demise of the local or request scopes have no effect on that.
No, the correlation of your NPEs is not a causation. The NPEs are nothing more than bugs that are unrelated to the scopes you were using to store the cached query.
Nope, totally and completely unrelated. And a bug.
Incorrect. The reference in the local scope will be removed, but the references the cache holds to the object still exist. Destroying a reference does not remove the actual data being referenced from memory so long as there are other references.
// create data and a reference so it
application.foo={ 'brad' : now() };
//Create another reference to it
local.bar=application.foo;
// delete the local reference
structDelete( local, 'bar' )
// The data is still here!
dump( application.foo )
No, this is incorrect.
@bdw429s , thanks for chiming in. Thanks also for your thoughts on the appropriate use of scopes.
I donât quite get the rest of your post. In particular, I do not see how your answer connects to my var-scoped-cachedWithin question.
Your answer is based on
local.bar=application.foo;
Or, equivalently, in the style of my question:
var bar=application.foo;
You started with that, and developed your answer further along that path. But that code doesnât quite match the one in my question, namely
var bar=a_cachedWithin_query_resulting_from_queryExecute;
In ACF and Lucee, queryExecute is the script variant of the cfquery tag. So, here, bar is the equivalent of the name attribute in a cfquery tag.
I donât see any such consideration in your var bar=application.foo; example. Yet the rest of your answer relies on this foo-bar example.
@bdw429s
I donât know what you think Zac said, but youâre arguing against a vapor cloud.
Everyone here understands encapsulation-- Zac was more making commentary
on how the caching is deterministic against the inputs (which should be fairly
obvious, otherwise it would be pretty useless.)
You say you donât know what I think, so I shall repeat it: the caller of a function should be totally unaware of those internal implementation details of the function that have nothing to do with the interface available to the caller.
Deterministic? My var-scope-cachedWithin question is whether weâre in fact dealing with a Schrödinger cat.
Again, Iâm just asking.
In addition, youâve repeated what I considered to be a design mistake. However, when I re-read it I realized we may perhaps be talking about the same thing, but in 2 different ways.
You respond to my point, âThe cachedWithin implies you may assume the object is held in memoryâ with:
@bdw429s
Just to clarify, this is not quite true. The caching assures you the same equivalent query data
will be returned every time you run it. There is no guarantee of where or how the cache has
decided to (possibly serialize) and store your query.
I think youâve misread me. I havenât said a cachedWithin query is stored in memory. My thiniking is that, by design, the caller expects the query to be in memory on subsequent calls of queryExecute.
Youâre talking about the internals of the query-caching process. I am talking from the point of view of the caller of the queryExecute() service. The caller doesnât - and shouldnât know - anything about the caching internals.
In fact, the query may equally be stored using some technology other than a cache-provider. All the caller expects is that subsequent queryExecute calls will take zero time.
Hence my statement, âcachedWithin implies you may assume the object is held in memoryâ.
@Zackster
Incorrect. The reference in the local scope will be removed, but the references
the cache holds to the object still exist. Destroying a reference does not remove
the actual data being referenced from memory so long as there are other references.
Youâve said âincorrectâ, then agreed with my point immediately after.
I said, âwhen you var-scope a variable within a function, youâre telling the application that the corresponding object may be deleted from memory after the function returns.â. Which is the same as your statement, âThe reference in the local scope will be removedâ. To which anyone in ACF or Lucee Land would say âDuhâ, I guess.
I am only talking about the consequence of the var scope within the context of a function. I made no connection to caching. Youâre the one who brings in references from elsewhere. Here again, youâre attributing responsibility where it shouldnât exist. The caller of queryExecute does not - and should not - know or care about what the cache-provider does.
In any case, we agree on one point. Namely, that the var-scoped variable is independent of any references that exist elsewhere, outside the function.
Because itâs irrelevant to my example where the long-lived data is stored. Whether itâs in the application scope, or pulled form the cache, your question was about the implications of storing a reference to a long-lived variable with a short-lived variable. So whether weâre talking about
var myVar = longLivedScope.foo;
or
var myVar = functionThatReturnsLongLivedValue()
the behavior of the local-scoped variable will be the same.
No, I said I didnât know what you thought Zac said because your reply to him before and now is completely off-topic. Zac just made a simple side comment about how the cached values are internally dependent upon the inputs and you typed an incredibly long reply about design patterns and such. To be clear, Zac wasnât implying that the internal behavior of the cache had anything at all to do with the behavior of scopes. He was just providing some contextual information about how the caching works, probably because he thought it would be useful to you. But it was unrelated to how scopes work.
It is. So the good news is youâre correct there. The confusing part for me was that no one ever suggested otherwise, so wasnât sure why you even thought that may be the case.
Iâm⊠not really sure what you mean by that (and yes, I do know what Schrödingerâs cat is ). The scope behavior has been explained. The Ortus Redis extension bug has been fixed (and was related to a connection timeout causing an internal reference in the Redis SDK to become null) and the Adobe bug weâll probably never know about since itâs closed source
Thanks for clarifying, I think I understand what what you mean now. And yes, it is a reasonable expectation that code like this
var foo = functionThatReturnsAValue();
will result in the foo
variable existing with a value in it,. That said, Iâm not entirely certain why weâre covering such basic ground here. Setting variables is a thing that works in CFML. Now, in case you thought the NPE was somehow indicating that foo
itself was null, that was not what was happening. The NPE was coming from deep inside the cache-specific code that just happens to run as part of functionThatReturnsAValue()
.
I donât think itâs the same, even though the two statements may sound similar. Your original premise was what the scope of the variable you declared in the function was directly responsible for the NPE you were seeing-- and that was what was incorrect. Also, when we use ambigious terms such as âcorresponding object in memoryâ, itâs not clear if weâre talking about
- the long lived data itself (a query in this case)
- the variable reference that the caching mechanism may hold pointing to it (for an in-process cache at least)
- the variable reference that your function holds pointing to it.
It sounded like me that you were surmising that the scope of bullet #3 may be affecting the cached lifespan of bullet #1 and causing the NPE. But in reality, there is no connection whatsoever between any of those things.
Thanks for clarifying, because it sure seemed like you were., In fact, this entire thread is about caching.
I havenât attributed any responsibility anywhere. Iâve simply explained the errors you saw and explained how scopes work. But at this point, I guess Iâm not even clear what your question is since everything you appeared to be saying, youâre now claiming you never said
That is correct, and to be very clear, again, no one said it did. So I donât understand why you keep talking about it.
Yep, weâll take this as progress And at this point, it may be useful to start a new, more narrow topic, if there are still any outstanding points of confusion. But it sounds like after initially misunderstanding everyone, weâre mostly on the same page, lol.
Hi @bdw429s ,
I shall leave it here. We have given the discussion a good go.
I came in with 2 questions. They have both been answered along the way.
- If a query is to be cached throughout the lifetime of an application (e.g. 1 day), then using the application scope as follows is good practice:
query function getRecords() {
application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)});
return application.recordsQuery;
}
- The NullPointerException was due to a bug that has since been fixed. If a query is to be cached throughout the lifetime of an application (e.g. 1 day), then using the var scope as follows is fine, too:
query function getRecords() {
var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)});
return recordsQuery;
}
@bdw429s , @carehart , @markdrew , @Phillyun , @Zackster , I owe you an immense thank you for your explanations and for your time.
To be clear, no one suggested this or backed up this idea. Storing the result is not only unnecessary to place in a long-lived scope, but could cause bugs related to race conditions if there were several versions of the cache query that could come back from the cache (overwriting the same shared variable). I would actively discourse this idea Either cache the query manually in the application
scope, or let queryExecute()
handle the caching, but pick one or the other. Itâs breaking encapsulation to create an application
-scoped variable in this way as part of your function where only a temporary local var is required.
@bdw429s ,
Thanks for the clarification. My apologies. In fact, I still had doubts about that, following a discussion with a colleague.
So, let me see whether I am leaving with the correct take-aways from this discussion:
The correct possibilities:
- Assigning the cachedWithin query to a function-local variable:
query function getRecords() {
var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin=createTimeSpan(1,0,0,0)});
return recordsQuery;
}
- Calling a function whose result happens to be the query (the result will be cached):
query function getRecords() cachedWithin=createTimeSpan(1,0,0,0) {
var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {});
return recordsQuery;
}
- Assigning the cachedWithin query directly to Application scope:
application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin=createTimeSpan(1,0,0,0)});
// Alternatively, using one of the functions in 1. or 2.
application.recordsQuery=getRecords();
- only works when your running it every request, and Iâd never that way myself
Thanks, @Zackster .
- only works when your running it every request, and Iâd never that way myself
Precisely. I share your thinking. To my mind, you would, ideally, want to set application variables once. For example, in onApplicationStart.
I included case 3. just for completeness, imagining a use-case such as:
if (! structKeyExists(application, "recordsQuery")) {
application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin=createTimeSpan(1,0,0,0)});
// Alternatively, using one of the functions in 1. or 2.
application.recordsQuery=getRecords();
}