-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invalidate before Refresh completes still stores value #193
Comments
I do recall refreshing being mind bending, so off-hand I can't speak to it without more thought. I'd first look into the prior issues (where some discussion took place) and Guava's behavior. When the refresh completes, the current code is... compute(key, (k, currentValue) -> {
if (currentValue == null) {
return value;
} else if ((currentValue == oldValue) && (node.getWriteTime() == refreshWriteTime)) {
return value;
}
discard[0] = true;
return currentValue;
}, /* recordMiss */ false, /* recordLoad */ false);
if (discard[0] && hasRemovalListener()) {
notifyRemoval(key, value, RemovalCause.REPLACED);
} Like you said, when My initial guess would be due to how
In
This is word-for-word (or nearly) the same as in Guava. Since a @yrfselrahc is the expert in this regard, since he wrote Guava's. I think the arguments would be,
In retrospect I would lean towards stronger consistency guarantees. |
Yah ideally for my case it would remain not loaded and invalidated. Further I would love to see a removalListener get fired for this event too as otherwise there is no where (except a finalizer) to catch this value if it has a lifecycle that needs to be managed. Either way it would make sense to have a work around I think. Something does feel like it's missing here. |
At first glance I would agree that the more correct behavior would be for refresh operations to be cancelled by concurrent writes or removals. That seems to be the only way to maintain linearizability. |
Yes agreed, cancel might seem most appropriate in this case, and would certainly work for my case. The more I thought about it the more I realize there is a big unanswered question: Given the flow: Regardless any action I can think of would be a major change to existing functionality as it is today so that is also an issue. I think I've been able to code around this issue by maintaining a separate Set of keys that should be filled and then in the async loader operation check against this Set once the value materializes and return null instead of the discovered value if the key no longer exists. It's a bit hacky in my opinion but I think it's sound from a concurrent point of view. |
Internally there is a writeTime, as shown in the code above. That resolves the ABA problem for Unfortunately, that is not the case for I don't have a strong opinion of whether this is a major or minor version change, according to semver. If we discard with the |
If we can discard the loading/refreshed value with EXPLICIT that solves everything for me, but it is really your call as to whether this is acting in accordance with what the API should be. You are the expert for this one and I can adjust as necessary based on the outcome so long as things are deterministic. |
I am working through this for v3 to flush where we can resolve these possibly backwards incompatible tickets. To resolve some other issues, a secondary mapping used for capturing the in-flight refreshes. This as the benefit of (1) not disabling expiration during a refresh, (2) matching Guava of doing nothing if an in-flight refresh was already in progress, (3) detecting the ABA problem of absent -> refresh start -> put -> invalidate -> refresh end where the refresh should be dropped as the mapping was modified. I think this would solve your problems, but I'm hazy about this conversation. Also, I don't remember exactly our thoughts on |
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in an undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. TODO: unit tests for these changes fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in an undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. TODO: unit tests for these changes fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in an undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. TODO: unit tests for these changes fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in an undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. TODO: unit tests for these changes fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
I ported your test into the v3 branch and it now passes. I haven't looked at what removal cause to use yet and I am unsure about that, so it is still REPLACED for the time being. |
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
I don't remember my exact use case, as I've been on a new project for the last 3 years, but I think it's fair to say if the test passes it would be a good fix. The case: It is a design choice and a case could be made for either as we discovered... Maybe PUT and/or INVALIDATE should cause the refresh() future to be CANCELLED (now that we have a future for it), or at least the value not applied but still returned from refresh() which I think is what is now working. |
The value wouldn't be applied to the cache now, but would you expect any user attached downstream chained actions on the returned refresh future to also observe it as cancelled? |
Observing cancelled could be useful but then the question is now, should calling PUT cancel the actual refresh operation itself? This would could IO implications as well if done correctly. Might be good, might be troublesome. It's almost like you need a 3rd option, so maybe the caller needs to decide somehow. Maybe it's a flag we set on the cache when it's created even to give expected behavior. Dealers choice :) |
So another quirk with cancelling is that for an Refresh is this odd special case where this and the #143 makes sense, but those choices might not in the general |
An in-flight refresh is now discarded for any explicit write, such as updates or removals. An eviction, such as size, does not invalidate the refresh. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
I strengthened this new behavior in the v3 branch. It will now more aggressively discard the in-flight refresh for any write to that entry, be it explicit or by eviction. This should provide a better guard against ABA problems. If the entry was removed and the refresh discarded, then its removal cause is now When the future is discarded it is not canceled. There are a few reasons for this,
4b) May be overly optimistic and perhaps the refresh should occur regardless. If the future is in-flight, data updated, and the explicit |
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
A mapping of in-flight refreshes is now maintained and lazily initialized if not used. This allows the cache to ignore redundant requests for reloads, like Guava does. It also removes disablement of expiration during refresh and resolves an ABA problem if the entry is modified in a previously undectectable way. The refresh future can now be obtained from LoadingCache to chain operations against. fixes #143 fixes #193 fixes #236 fixes #282 fixes #322 fixed #373 fixes #467
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
An in-flight refresh is now discarded for any write to the entry, such as an update, removal, or eviction. In the case where the entry was removed and the refresh was discarded, the removal cause is now "explicit" instead of "replaced" as there is no mapping. Guava and previous releases would populate the cache, or at best used "replaced" as the cause even if it was not accurate. The resulting behavior is more pessimistic by trying to avoid a refresh from populating the cache with stale data. In practice this should have little to no penalty.
Released in 3.0 |
Maybe this has come up before, but this seems a bit odd. Here is my test.
It would appear that a refresh that started before an invalidate takes precedence over the invalidate? What I really wanted to test was the removal listener, but I ran into this first.
I'm using Caffeine 2.5.3
Thanks again for your good work.
The text was updated successfully, but these errors were encountered: