-
Notifications
You must be signed in to change notification settings - Fork 20
feat: Added a generic LRUCache interface and a default implementation #311
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
Changes from 1 commit
671e159
3d1ae15
b987d9d
295fdd2
50fd4e1
22acf8e
d2e3373
d758efb
da33c99
9fe246e
33dc181
2f35de3
24737fe
e042d99
6749182
2bac003
03c9abf
209c3bd
42a735d
722780c
ea2f32a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,57 +24,77 @@ namespace OptimizelySDK.Odp | |
public class LruCache<T> : ICache<T> | ||
where T : class | ||
{ | ||
/// <summary> | ||
/// Default maximum number of elements to store | ||
/// </summary> | ||
private const int DEFAULT_MAX_SIZE = 10000; | ||
|
||
/// <summary> | ||
/// The maximum number of elements that should be stored | ||
/// </summary> | ||
private readonly int _maxSize; | ||
|
||
/// <summary> | ||
/// An object for obtaining a mutually exclusive lock for thread safety | ||
/// </summary> | ||
private readonly object _mutex; | ||
|
||
/// <summary> | ||
/// The maximum age of an object in the cache | ||
/// </summary> | ||
private readonly TimeSpan _timeout; | ||
|
||
/// <summary> | ||
/// Indication that timeout is disabled and objects should remain in cache indefinitely | ||
// 8000 / </summary> | ||
private readonly TimeSpan _timeoutDisabled = TimeSpan.Zero; | ||
|
||
/// <summary> | ||
/// Implementation used for recording LRU events or errors | ||
/// </summary> | ||
private readonly ILogger _logger; | ||
|
||
/// <summary> | ||
/// Indexed data held in the cache | ||
/// </summary> | ||
private readonly Dictionary<string, (LinkedListNode<string> node, ItemWrapper value)> _cache; | ||
|
||
/// <summary> | ||
/// Ordered list of objects being held in the cache | ||
/// </summary> | ||
private readonly LinkedList<string> _list; | ||
|
||
public LruCache(int? maxSize = null, | ||
TimeSpan? itemTimeout = null, ILogger logger = null | ||
/// <summary> | ||
/// A Least Recently Used in-memory cache | ||
/// </summary> | ||
/// <param name="maxSize">Maximum number of elements to allow in the cache</param> | ||
/// <param name="itemTimeout">Timeout or time to live for each item</param> | ||
/// <param name="logger">Implementation used for recording LRU events or errors</param> | ||
public LruCache(int maxSize = DEFAULT_MAX_SIZE, | ||
TimeSpan? itemTimeout = default, ILogger logger = null | ||
) | ||
{ | ||
const int DEFAULT_MAX_SIZE = 10000; | ||
const int CACHE_DISABLED = 0; | ||
var defaultTimeout = TimeSpan.FromMinutes(10); | ||
|
||
_mutex = new object(); | ||
|
||
if (maxSize is null) | ||
{ | ||
_maxSize = DEFAULT_MAX_SIZE; | ||
} | ||
else if (maxSize < 0) | ||
{ | ||
_maxSize = CACHE_DISABLED; | ||
} | ||
else | ||
{ | ||
_maxSize = maxSize.Value; | ||
} | ||
|
||
if (itemTimeout is null) | ||
{ | ||
_timeout = defaultTimeout; | ||
} | ||
else if (itemTimeout?.TotalMilliseconds < 0) | ||
{ | ||
_timeout = _timeoutDisabled; | ||
} | ||
else | ||
{ | ||
_timeout = itemTimeout.Value; | ||
} | ||
_maxSize = Math.Max(CACHE_DISABLED, maxSize); | ||
|
||
_timeout = TimeSpan.FromTicks(Math.Max(_timeoutDisabled.Ticks, | ||
(itemTimeout ?? TimeSpan.FromMinutes(10)).Ticks)); | ||
|
||
_logger = logger ?? new DefaultLogger(); | ||
|
||
_cache = | ||
new Dictionary<string, (LinkedListNode<string> node, ItemWrapper value)>(_maxSize); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Am just curious can't we just take LinkedListNode as value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the idea is to pop off both the node from the Tuple and put it in the Let's talk through this too. |
||
_list = new LinkedList<string>(); | ||
} | ||
|
||
/// <summary> | ||
/// Saves a new element into the cache | ||
/// </summary> | ||
/// <param name="key">Key of the element used for future retrieval</param> | ||
/// <param name="value">Strongly-typed value to store against the key</param> | ||
public void Save(string key, T value) | ||
{ | ||
if (_maxSize == 0) | ||
|
@@ -105,6 +125,11 @@ public void Save(string key, T value) | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Retrieves an element from the cache, reordering the elements | ||
/// </summary> | ||
/// <param name="key">Key of element to find and retrieve</param> | ||
/// <returns>Strongly-typed value from the cache</returns> | ||
public T Lookup(string key) | ||
{ | ||
if (_maxSize == 0) | ||
|
@@ -141,6 +166,9 @@ public T Lookup(string key) | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Clears all the elements from the cache | ||
/// </summary> | ||
public void Reset() | ||
{ | ||
lock (_mutex) | ||
|
@@ -150,18 +178,36 @@ public void Reset() | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Wrapping class around a generic value stored in the cache | ||
/// </summary> | ||
private class ItemWrapper | ||
{ | ||
/// <summary> | ||
/// Value of the item | ||
/// </summary> | ||
public readonly T Value; | ||
|
||
/// <summary> | ||
/// Unix timestamp of when the item was added | ||
/// </summary> | ||
public readonly long CreationTimestamp; | ||
|
||
/// <summary> | ||
/// Initialize the wrapper | ||
/// </summary> | ||
/// <param name="value">Item to be stored</param> | ||
public ItemWrapper(T value) | ||
{ | ||
Value = value; | ||
CreationTimestamp = DateTime.Now.MillisecondsSince1970(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Read the current cache index/linked list for unit testing | ||
/// </summary> | ||
/// <returns></returns> | ||
public LinkedList<string> _readCurrentCacheKeys() | ||
{ | ||
_logger.Log(LogLevel.WARN, "_readCurrentCacheKeys used for non-testing purpose"); | ||
|
Uh oh!
There was an error while loading. Please reload this page.