-
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
Difference in iteration order between caffeine and guava in LoadingCache.getAll #220
Comments
Good point. I didn't see the benefit of providing stable iteration order based on the input. It would be interesting to know if specifying this would be a nice-to-have feature or unimportant. I don't see the harm, but also don't see the benefits. The main reason why I decided not to replicate this was another possible bug. Guava and Caffeine do not consider weak keys in their result mapping. The weak key configuration uses reference equality and identity hash code for lookup. If k1 and k2 are logically equal but different instances, then there may be two entries in the cache. When the result of |
One of our use-cases is the following:
However, resorting the result after getting it from the cache isn't a showstopper at the moment at least. |
Released. Thanks! (now has Guava ordering, but similarly not doc'd) |
Oh joy, undefined behaviour. 😄 I just stumbled upon a strange ordering issue related to this. Consider the following sequence in pseudo-code for a
This is how Guava's cache behaves. Caffeine gets any existing cached values first, and after that uses the loader to load the missing ones, causing the following behaviour:
|
I guess we should write some tests for these and try to resolve them. From my reading of Guava's code, this was the behavior and so I followed it (even though I thought it was odd as not fully ordered). In Guava's ImmutableMap<K, V> getAll(Iterable<? extends K> keys) {
Map<K, V> result = Maps.newLinkedHashMap();
Set<K> keysToLoad = Sets.newLinkedHashSet();
for (K key : keys) {
V value = get(key);
if (!result.containsKey(key)) {
result.put(key, value);
if (value == null) {
keysToLoad.add(key);
}
}
}
Map<K, V> newEntries = loadAll(keysToLoad, defaultLoader);
for (K key : keysToLoad) {
V value = newEntries.get(key);
result.put(key, value);
}
return ImmutableMap.copyOf(result);
} |
Oh! It clicked what Guava was doing and I wrote a quick test to verify. Sorry that I misunderstood and implemented it wrong. The first Since I didn't notice the use of null values, I naively assumed it out of order. Sorry that I didn't write tests to verify that. |
The iteration order of the
Map<K, V>
that Guava'sLoadingCache.getAll( Iterable<? extends K> keys )
returns is the same as the iteration order of the input keys, due to Guava using aLinkedHashMap
under the hood. Caffeine, instead, uses aHashMap
, and therefore the iteration is highly unlikely to be the same. I noticed this while migrating from Guava to Caffeine caches, as we have unfortunately relied on the iteration order being consistent. ThegetAll
javadoc doesn't mention anything about the iteration order, and that should probably have told me to consider it as unspecified iteration order.I'm creating this issue mainly as a way of documenting the difference in behaviour for others who migrate from Guava to Caffeine. Maybe an entry about this on the Guava wiki page would be suitable? Because I suppose you don't want to replace the internal
HashMap
with aLinkedHashMap
.The text was updated successfully, but these errors were encountered: