-
Notifications
You must be signed in to change notification settings - Fork 32
feat: Added a generic LRUCache interface and a default implementation #482
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
Conversation
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.
Looks good! A few suggestions and clarifications.
*/ | ||
package com.optimizely.ab.internal; | ||
|
||
public interface LRUCache<T> { |
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.
public interface LRUCache<T> { | |
public interface Cache<T> { |
This interface can be for generic caches, not only for LRUCache. Not sure about the best name to avoid conflicts., but "LRU" should be dropped. I think this is for internal use (not for public custom segments cache). If this interface is for public, I have a few other suggestions.
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.
Changing it to Cache for now as per your suggestion. We can discuss this further in subsequent PRs when things become more clear.
public void setMaxSize(Integer size) { | ||
Integer sizeToSet = size; | ||
if (size < 0 ) { | ||
sizeToSet = DEFAULT_MAX_SIZE; |
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.
either way is fine, but let's use "sizeToSet = 0" for all SDKs.
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.
Done! Defaulting to zero now when max size is negative
if (linkedList.size() > sizeToSet) { | ||
// If cache is bigger than the new maxSize, remove additional items from the end. | ||
int itemsToTrim = linkedList.size() - sizeToSet; | ||
for (int i = 0; i < itemsToTrim; i++) { | ||
ItemWrapper item = linkedList.removeLast(); | ||
hashMap.remove(item.key); | ||
} | ||
} |
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.
Do we need this support for dynamic cache size changes? We can allow them cache size setting only when app starts.
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 changed it to only allow when maxSize passed in is less than the current size of the cache.
|
||
if (hashMap.containsKey(key)) { | ||
ItemWrapper oldItem = hashMap.get(key); | ||
linkedList.remove(oldItem); |
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.
Isn't this linear search? It may be a factor in large cache.
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.
This is a good catch. Because i was passing the item itself and it uses a doubly linked list, i thought the item should have references to its neighbors and should be able to cleanly remove itself in O(1) but it looks like the actual node with references is managed internally and remove
actually searched for the item linearly. I then went ahead and redid the whole implementation using LunkedHashMap
} | ||
|
||
@Test | ||
public void saveAndLookupMultipleItems() { |
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.
This test looks good. It will be good to have the same ordering switch tests for save ops as well.
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.
Done!
return; | ||
} | ||
|
||
if (linkedList.size() == maxSize) { |
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.
if (linkedList.size() == maxSize) { | |
if (linkedList.size() >= maxSize) { |
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.
Changed the whole implementation. It does not apply anymore.
} | ||
|
||
@Test | ||
public void whenMaxSizeIsReducedInBetween() { |
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.
We can remove this test
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.
Modified the test to check the changed implementation
} | ||
|
||
@Test | ||
public void saveAndPeekItems() { |
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.
A test for reset()?
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.
Done!
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.
The new solution with LinkedHashMap looks great!
I suggest to remove support for dynamic cache config changes (setMaxsize/setTimeout) for simplicity and also consistency with other SDKs. Other than that, all changes look good.
void setMaxSize(Integer size); | ||
void setTimeout(Long timeoutSeconds); |
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.
We can set these in constructor. Can we remove them for consistency with other SDKs unless we have a strong case?
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.
Done! I also kept a no argument constructor too that will use default values.
synchronized (lock) { | ||
linkedHashMap.put(key, new ItemWrapper(value)); | ||
} |
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.
Wow, a single-line solution :)
} | ||
return null; | ||
} | ||
} | ||
|
||
public T peek(String key) { |
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.
It's surprising that they do not have this method in LinkedHashMap. It should be ok without this since it's mostly for testing/debugging.
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.
LGTM
Summary
Added an interface for a generic LRUCache and a default Implementation.
Test plan
Issues
OASIS-8385