-
Notifications
You must be signed in to change notification settings - Fork 6.1k
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
Use Iterator.remove when trimming cache #2550
Conversation
…() can be used here. Fixes bumptech#2532
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed, please reply here (e.g.
|
I signed it! |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
I'm unable to change the email on the CLA, it is always set to my gmail addresseven after changing my Google settings to use my new one. Maybe somebody else can re-apply the patch? |
CLAs look good, thanks! |
final Y toRemove = last.getValue(); | ||
currentSize -= getSize(toRemove); | ||
final T key = last.getKey(); | ||
cache.remove(key); | ||
cacheIterator.remove(); | ||
onItemEvicted(key, toRemove); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a chance of ConcurrentModificationException
because of this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, using the Iterator to remove is safe when using the Iterator itself to loop over the collection. I added another commit to ensure that we don't overrun the collection, although I don't see how it could happen unless the size to trim to is <0. Can't hurt though I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sjudd before you merge, please consider this test (it throws with new code):
@Test
public void testPreventEviction() {
final MemoryCache cache = new LruResourceCache(100);
final Resource<?> first = getResource(30);
final Key firstKey = new MockKey();
cache.put(firstKey, first);
Resource<?> second = getResource(30);
Key secondKey = new MockKey();
cache.put(secondKey, second);
Resource<?> third = getResource(30);
Key thirdKey = new MockKey();
cache.put(thirdKey, third);
cache.setResourceRemovedListener(new ResourceRemovedListener() {
@Override
public void onResourceRemoved(Resource<?> removed) {
if (removed == first) {
cache.put(firstKey, first);
}
}
});
// trims from 100 to 50, having 30+30+30 items, it should trim to 1 item
cache.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
// and that 1 item must be first, because it's forced to return to cache in the listener
@SuppressWarnings("unchecked")
LruCache<Key, Resource<?>> lruCache = (LruCache<Key, Resource<?>>) cache;
assertTrue(lruCache.contains(firstKey));
assertFalse(lruCache.contains(secondKey));
assertFalse(lruCache.contains(thirdKey));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@unverbraucht thoughts? I don't think we typically have concurrent modifications like this, but it's probably possible (and may be why the code was written the way it was initially).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sjudd @TWiStErRob good point. It might make sense then to get a fresh instance of the Iterator after every loop run. I've committed proposed changes for that.
LGTM, just waiting on 4.3.1 to go out before merging. |
@@ -166,12 +168,14 @@ public void clearMemory() { | |||
*/ | |||
protected synchronized void trimToSize(int size) { | |||
Map.Entry<T, Y> last; | |||
Iterator<Map.Entry<T, Y>> cacheIterator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a preference (declare close to usage, and in scope): these two vars are only used within the loop, so they could be defined inside:
final Iterator<Map.Entry<T, Y>> cacheIterator = cache.entrySet().iterator();
final Map.Entry<T, Y> last = cacheIterator.next();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree in principle, but this matches the existing style, so let's just leave it as is for now.
Thanks! |
Description
Instead of explicitly removing a key from the LruCache (which fails if the key class violates the equals() contract) call remove() on the iterator instead.
Motivation and Context
Using this even cache keys violating equals() can be used here. Fixes #2532.