8000 feat: Added a generic LRUCache interface and a default implementation by mikechu-optimizely · Pull Request #311 · optimizely/csharp-sdk · GitHub
[go: up one dir, main page]

Skip to content

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

Merged
merged 21 commits into from
Aug 17, 2022
Merged
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
671e159
Skeleton
mikechu-optimizely Aug 5, 2022
3d1ae15
LruCache initial implementation
mikechu-optimizely Aug 5, 2022
b987d9d
Add helper extensions
mikechu-optimizely Aug 5, 2022
295fdd2
WIP Fill unit tests
mikechu-optimizely Aug 5, 2022
50fd4e1
WIP corrections based on tests 2 more failing
mikechu-optimizely Aug 5, 2022
22acf8e
Remove SetTimeout, fix for non-passing test
mikechu-optimizely Aug 8, 2022
d2e3373
Add copyright notices
mikechu-optimizely Aug 8, 2022
d758efb
Remove InternalsVisibleTo for testing
mikechu-optimizely Aug 8, 2022
da33c99
Add new line at end of files via .editorconfig
mikechu-optimizely Aug 8, 2022
9fe246e
Readonly and remove excess methods/constructor
mikechu-optimizely Aug 8, 2022
33dc181
Code review corrections
mikechu-optimizely Aug 10, 2022
2f35de3
WIP code review changes
mikechu-optimizely Aug 10, 2022
24737fe
Possibly better solution to cast
mikechu-optimizely Aug 10, 2022
e042d99
Move readonlys into constructor
mikechu-optimizely Aug 10, 2022
6749182
Switch to using TimeSpan + refactors
mikechu-optimizely Aug 11, 2022
2bac003
A few more refactors
mikechu-optimizely Aug 11, 2022
03c9abf
Change underlying implementation of LRU
mikechu-optimizely Aug 12, 2022
209c3bd
Attempt to fulfill a default Timespan
mikechu-optimizely Aug 16, 2022
42a735d
Add logging; use 0s instead of descriptive consts
mikechu-optimizely Aug 16, 2022
722780c
Remove Tuple from LRU Cache
mikechu-optimizely Aug 16, 2022
ea2f32a
Remove System.ValueTuple NuGet package
mikechu-optimizely Aug 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Attempt to fulfill a default Timespan
  • Loading branch information
mikechu-optimizely committed Aug 16, 2022
commit 209c3bd8e54b8f721efa2f56bc565679ea57f9ca
104 changes: 75 additions & 29 deletions OptimizelySDK/Odp/LruCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am just curious can't we just take LinkedListNode as value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 _list & back in the _cache along with the item see linw 150ish.

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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -141,6 +166,9 @@ public T Lookup(string key)
}
}

/// <summary>
/// Clears all the elements from the cache
/// </summary>
public void Reset()
{
lock (_mutex)
Expand All @@ -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");
Expand Down
0