aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-03-11 02:04:31 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-03-11 02:04:31 -0500
commitdb4584c37380f1826986b3acfe35bbf92693dfc6 (patch)
tree3a264c95c6f3bf74453662cc8d180a9d570bfbfe
parenta27a4ce58d6e09b34027b30d0c5c988e3112a54d (diff)
Persistant cache abstraction and runtime loading
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs57
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs14
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs23
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs22
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs39
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs5
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs11
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs43
-rw-r--r--lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs79
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs2
-rw-r--r--lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs2
-rw-r--r--plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs242
-rw-r--r--plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs27
13 files changed, 503 insertions, 63 deletions
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs
index 440981a..f77587b 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs
@@ -23,8 +23,8 @@
*/
using System;
-using System.Collections.Generic;
using System.Diagnostics;
+using System.Collections.Generic;
using VNLib.Utils.Memory;
using VNLib.Utils.Memory.Caching;
@@ -35,9 +35,10 @@ namespace VNLib.Data.Caching.ObjectCache
/// <summary>
/// A general purpose binary data storage
/// </summary>
- public sealed class BlobCache : LRUCache<string, CacheEntry>, IBlobCache
+ public sealed class BlobCache : LRUCache<string, CacheEntry>, IBlobCache, IMemoryCacheEntryFactory
{
private bool disposedValue;
+ private IPersistantCacheStore? _persistance;
///<inheritdoc/>
public override bool IsReadOnly { get; }
@@ -48,21 +49,29 @@ namespace VNLib.Data.Caching.ObjectCache
///<inheritdoc/>
public IUnmangedHeap CacheHeap { get; }
+ ///<inheritdoc/>
+ public uint BucketId { get; }
/// <summary>
/// Initializes a new <see cref="BlobCache"/> store
/// </summary>
+ /// <param name="bucketId">The id of the bucket that manages this instance</param>
/// <param name="maxCapacity">The maximum number of items to keep in memory</param>
/// <param name="heap">The unmanaged heap used to allocate cache entry buffers from</param>
+ /// <param name="store">The optional backing persistant cache storage</param>
/// <exception cref="ArgumentException"></exception>
- public BlobCache(int maxCapacity, IUnmangedHeap heap)
- :base(StringComparer.Ordinal)
+ public BlobCache(uint bucketId, int maxCapacity, IUnmangedHeap heap, IPersistantCacheStore? store)
+ :base(maxCapacity, StringComparer.Ordinal)
{
if(maxCapacity < 1)
{
throw new ArgumentException("The maxium capacity of the store must be a positive integer larger than 0", nameof(maxCapacity));
}
+ BucketId = bucketId;
+
+ _persistance = store;
+
CacheHeap = heap;
MaxCapacity = maxCapacity;
@@ -74,19 +83,32 @@ namespace VNLib.Data.Caching.ObjectCache
///<inheritdoc/>
protected override bool CacheMiss(string key, out CacheEntry value)
{
- value = default;
- return false;
+ if(_persistance == null)
+ {
+ value = default;
+ return false;
+ }
+ //Use the persistant cache
+ return _persistance.OnCacheMiss(BucketId, key, this, out value);
}
///<inheritdoc/>
protected override void Evicted(ref KeyValuePair<string, CacheEntry> evicted)
{
- //Dispose the cache item
- evicted.Value.Dispose();
+ try
+ {
+ //Call persistance store record eviction
+ _persistance?.OnEntryEvicted(BucketId, evicted.Key, evicted.Value);
+ }
+ finally
+ {
+ //Dispose the cache item
+ evicted.Value.Dispose();
+ }
}
///<inheritdoc/>
- public bool TryChangeKey(string objectId, string newId, out CacheEntry blob)
+ public bool TryChangeKey(string objectId, string newId, out CacheEntry entry)
{
//Try to get the node at the current key
if (LookupTable.Remove(objectId, out LinkedListNode<KeyValuePair<string, CacheEntry>> ? node))
@@ -95,10 +117,10 @@ namespace VNLib.Data.Caching.ObjectCache
List.Remove(node);
//Get the stored blob
- blob = node.ValueRef.Value;
+ entry = node.ValueRef.Value;
//Update the
- node.Value = new KeyValuePair<string, CacheEntry>(newId, blob);
+ node.Value = new KeyValuePair<string, CacheEntry>(newId, entry);
//Add to end of list
List.AddLast(node);
@@ -109,13 +131,16 @@ namespace VNLib.Data.Caching.ObjectCache
return true;
}
- blob = default;
+ entry = default;
return false;
}
///<inheritdoc/>
public override bool Remove(string key)
{
+ //Remove from persistant store also
+ _persistance?.OnEntryDeleted(BucketId, key);
+
//Remove the item from the lookup table and if it exists, remove the node from the list
if (!LookupTable.Remove(key, out LinkedListNode<KeyValuePair<string, CacheEntry>>? node))
{
@@ -192,5 +217,13 @@ namespace VNLib.Data.Caching.ObjectCache
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
+
+
+ ///<inheritdoc/>
+ CacheEntry IMemoryCacheEntryFactory.CreateEntry(ReadOnlySpan<byte> entryData)
+ {
+ //Create entry from the internal heap
+ return CacheEntry.Create(entryData, CacheHeap);
+ }
}
}
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs
index f79db3f..6af1a20 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs
@@ -29,11 +29,18 @@ using VNLib.Utils.Memory;
namespace VNLib.Data.Caching.ObjectCache
{
+
+ /// <summary>
+ /// A concrete implementation of an <see cref="IBlobCacheBucket"/>
+ /// </summary>
public sealed class BlobCacheBucket : IBlobCacheBucket
{
private readonly IBlobCache _cacheTable;
private readonly SemaphoreSlim _lock;
+ ///<inheritdoc/>
+ public uint Id { get; }
+
/// <summary>
/// Initialzies a new <see cref="BlobCacheBucket"/> and its underlying
/// <see cref="IBlobCache"/>
@@ -42,11 +49,14 @@ namespace VNLib.Data.Caching.ObjectCache
/// The maxium number of entries allowed in the LRU cache
/// before LRU overflow happens.
/// </param>
+ /// <param name="bucketId">The unique id of the new bucket</param>
/// <param name="heap">The heap to allocate object cache buffers</param>
- public BlobCacheBucket(int bucketCapacity, IUnmangedHeap heap)
+ /// <param name="persistantCache">An optional <see cref="IPersistantCacheStore"/> for cache persistance</param>
+ public BlobCacheBucket(uint bucketId, int bucketCapacity, IUnmangedHeap heap, IPersistantCacheStore? persistantCache)
{
+ Id = bucketId;
_lock = new(1, 1);
- _cacheTable = new BlobCache(bucketCapacity, heap);
+ _cacheTable = new BlobCache(bucketId, bucketCapacity, heap, persistantCache);
}
///<inheritdoc/>
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs
index 818dfcf..f69c2a4 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheLIstener.cs
@@ -3,9 +3,9 @@
*
* Library: VNLib
* Package: VNLib.Data.Caching.ObjectCache
-* File: BlobCacheLIstener.cs
+* File: BlobCacheListener.cs
*
-* BlobCacheLIstener.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger
+* BlobCacheListener.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
@@ -53,9 +53,9 @@ namespace VNLib.Data.Caching.ObjectCache
public delegate ReadOnlySpan<byte> GetBodyDataCallback<T>(T state);
/// <summary>
- /// A <see cref="FBMListener"/> implementation of a <see cref="CacheListener"/>
+ /// An <see cref="FBMListener"/> for key-value object data caching servers.
/// </summary>
- public class BlobCacheLIstener : FBMListenerBase, IDisposable
+ public class BlobCacheListener : FBMListenerBase, IDisposable
{
private bool disposedValue;
@@ -74,21 +74,22 @@ namespace VNLib.Data.Caching.ObjectCache
/// <summary>
- /// Initialzies a new <see cref="BlobCacheLIstener"/>
+ /// Initialzies a new <see cref="BlobCacheListener"/>
/// </summary>
- /// <param name="cacheMax">The maxium number of items per bucket</param>
- /// <param name="buckets">The number of cache store buckets</param>
- /// <param name="log"></param>
+ /// <param name="cache">The cache table to work from</param>
+ /// <param name="log">Writes error and debug logging information</param>
/// <param name="heap">The heap to alloc FBM buffers and <see cref="CacheEntry"/> cache buffers from</param>
/// <param name="singleReader">A value that indicates if a single thread is processing events</param>
- public BlobCacheLIstener(uint buckets, uint cacheMax, ILogProvider log, IUnmangedHeap heap, bool singleReader)
+ /// <exception cref="ArgumentNullException"></exception>
+ public BlobCacheListener(IBlobCacheTable cache, ILogProvider log, IUnmangedHeap heap, bool singleReader)
{
Log = log;
+ Cache = cache ?? throw new ArgumentNullException(nameof(cache));
+
//Writes may happen from multple threads with bucket design and no lock
EventQueue = new(false, singleReader);
-
- Cache = new BlobCacheTable(buckets, cacheMax, heap);
+
InitListener(heap);
}
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs
index 270cf1e..f3f1b50 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs
@@ -39,6 +39,7 @@ namespace VNLib.Data.Caching.ObjectCache
{
private readonly uint _tableSize;
private readonly IBlobCacheBucket[] _buckets;
+ private readonly IPersistantCacheStore? _persistant;
/// <summary>
/// Initializes a new <see cref="BlobCacheTable"/>
@@ -46,9 +47,10 @@ namespace VNLib.Data.Caching.ObjectCache
/// <param name="bucketSize">The number of elements in each bucket</param>
/// <param name="tableSize">The number of buckets within the table</param>
/// <param name="heap">The heap used to allocate cache entry buffers from</param>
+ /// <param name="persistantCache">An optional <see cref="IPersistantCacheStore"/> for persistant cache implementations</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
- public BlobCacheTable(uint tableSize, uint bucketSize, IUnmangedHeap heap)
+ public BlobCacheTable(uint tableSize, uint bucketSize, IUnmangedHeap heap, IPersistantCacheStore? persistantCache)
{
_ = heap ?? throw new ArgumentNullException(nameof(heap));
@@ -61,16 +63,18 @@ namespace VNLib.Data.Caching.ObjectCache
_tableSize = tableSize;
_buckets = new IBlobCacheBucket[tableSize];
+ _persistant = persistantCache;
+
//Init buckets
- InitBuckets(tableSize, bucketSize, _buckets, heap);
+ InitBuckets(tableSize, bucketSize, _buckets, heap, persistantCache);
}
- private static void InitBuckets(uint size, uint bucketSize, IBlobCacheBucket[] table, IUnmangedHeap heap)
+ private static void InitBuckets(uint size, uint bucketSize, IBlobCacheBucket[] table, IUnmangedHeap heap, IPersistantCacheStore? persistantCache)
{
- for(int i = 0; i < size; i++)
+ for(uint i = 0; i < size; i++)
{
- table[i] = new BlobCacheBucket((int)bucketSize, heap);
+ table[i] = new BlobCacheBucket(i, (int)bucketSize, heap, persistantCache);
}
}
@@ -118,8 +122,12 @@ namespace VNLib.Data.Caching.ObjectCache
///<inheritdoc/>
protected sealed override void Free()
{
- //Dispose buckets
- Array.ForEach(_buckets, static b => b.Dispose());
+ //Dispose persistance store
+ using (_persistant)
+ {
+ //Dispose buckets
+ Array.ForEach(_buckets, static b => b.Dispose());
+ }
}
///<inheritdoc/>
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs b/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs
index 3d61790..e778b30 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Data.Caching.ObjectCache
@@ -23,19 +23,21 @@
*/
using System;
+using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
+
namespace VNLib.Data.Caching
{
/// <summary>
/// A structure that represents an item in cache. It contains the binary content
/// of a cache entry by its internal memory handle
/// </summary>
- public readonly struct CacheEntry : IDisposable, IEquatable<CacheEntry>
+ public readonly record struct CacheEntry : IDisposable
{
private const int TIME_SEGMENT_SIZE = sizeof(long);
@@ -53,7 +55,7 @@ namespace VNLib.Data.Caching
/// </summary>
/// <param name="data">The initial data to store</param>
/// <param name="heap">The heap to allocate the buffer from</param>
- /// <returns>The new <see cref="CacheEntry"/></returns>
+ /// <returns>The newly initialized and ready to use <see cref="CacheEntry"/></returns>
public static CacheEntry Create(ReadOnlySpan<byte> data, IUnmangedHeap heap)
{
//Calc buffer size
@@ -74,6 +76,7 @@ namespace VNLib.Data.Caching
return entry;
}
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetRequiredHandleSize(int size)
{
@@ -202,27 +205,31 @@ namespace VNLib.Data.Caching
Span<byte> segment = GetDataSegment();
#if DEBUG
- //Test segment length is equvalent to the requested data length
+ //Test segment length is equivalent to the requested data length
System.Diagnostics.Debug.Assert(segment.Length == data.Length);
#endif
//Copy data segment
data.CopyTo(segment);
}
-
- ///<inheritdoc/>
- public override bool Equals(object? obj) => obj is CacheEntry entry && Equals(entry);
-
///<inheritdoc/>
public override int GetHashCode() => _handle.GetHashCode();
- ///<inheritdoc/>
- public static bool operator ==(CacheEntry left, CacheEntry right) => left.Equals(right);
-
- ///<inheritdoc/>
- public static bool operator !=(CacheEntry left, CacheEntry right) => !(left == right);
-
- ///<inheritdoc/>
- public bool Equals(CacheEntry other) => other.GetHashCode() == GetHashCode();
+ /// <summary>
+ /// Gets a <see cref="MemoryHandle"/> offset to the start of the
+ /// internal data segment, and avoids calling the fixed keyword.
+ /// The handle must be disposed/released to avoid memeory leaks.
+ /// </summary>
+ /// <remarks>
+ /// WARNING: You must respect the <see cref="GetLength"/> return value so
+ /// as no to overrun the valid data segment.
+ /// </remarks>
+ /// <returns>A handle that points to the begining of the data segment</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly MemoryHandle UnsafeGetDataSegmentHandle()
+ {
+ //Get the handle offset to the data segment start, the caller must know when the data segment ends
+ return _handle.Pin(DATA_SEGMENT_START);
+ }
}
}
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs
index 52d53ff..bc3180b 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs
@@ -35,6 +35,11 @@ namespace VNLib.Data.Caching.ObjectCache
public interface IBlobCache : IEnumerable<KeyValuePair<string, CacheEntry>>, IDisposable
{
/// <summary>
+ /// The id of the bucket this memory cache belongs to
+ /// </summary>
+ public uint BucketId { get; }
+
+ /// <summary>
/// The internal heap used to allocate <see cref="CacheEntry"/> buffers
/// </summary>
IUnmangedHeap CacheHeap { get; }
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs
index 4876c5f..dbe095c 100644
--- a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs
@@ -3,10 +3,10 @@
*
* Library: VNLib
* Package: VNLib.Data.Caching.ObjectCache
-* File: ObjectCacheStore.cs
+* File: IBlobCacheBucket.cs
*
-* ObjectCacheStore.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger
-* VNLib collection of libraries and utilities.
+* IBlobCacheBucket.cs is part of VNLib.Data.Caching.ObjectCache which
+* is part of the larger VNLib collection of libraries and utilities.
*
* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -35,6 +35,11 @@ namespace VNLib.Data.Caching.ObjectCache
public interface IBlobCacheBucket : IDisposable
{
/// <summary>
+ /// The unique integer id of a bucket within an <see cref="IBlobCacheTable"/>
+ /// </summary>
+ uint Id { get; }
+
+ /// <summary>
/// Gets a <see cref="CacheBucketHandle"/> that holds an exclusive lock
/// for the current bucekt and holds a referrence to the stored
/// <see cref="IBlobCache"/>
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs
new file mode 100644
index 0000000..1454fc0
--- /dev/null
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.ObjectCache
+* File: IMemoryCacheEntryFactory.cs
+*
+* IMemoryCacheEntryFactory.cs is part of VNLib.Data.Caching.ObjectCache which
+* is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Data.Caching.ObjectCache
+{
+ /// <summary>
+ /// A factory abstraction that builds <see cref="CacheEntry"/> structures
+ /// linked to internally configured memory implementations, for cache
+ /// promotions.
+ /// </summary>
+ public interface IMemoryCacheEntryFactory
+ {
+ /// <summary>
+ /// Creates and initalizes a new <see cref="CacheEntry"/> from the desired object data
+ /// </summary>
+ /// <param name="entryData">The non-owned memory to copy into the the new <see cref="CacheEntry"/></param>
+ /// <returns>The newly initalized <see cref="CacheEntry"/></returns>
+ CacheEntry CreateEntry(ReadOnlySpan<byte> entryData);
+ }
+}
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs
new file mode 100644
index 0000000..40f39f2
--- /dev/null
+++ b/lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs
@@ -0,0 +1,79 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Data.Caching.ObjectCache
+* File: IPersistantCacheStore.cs
+*
+* IPersistantCacheStore.cs is part of VNLib.Data.Caching.ObjectCache which
+* is part of the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Data.Caching.ObjectCache
+{
+ /// <summary>
+ /// Provides a persitance layer to memory caching.
+ /// </summary>
+ public interface IPersistantCacheStore : IDisposable
+ {
+ /// <summary>
+ /// Invoked when an entry has been evicted from main-memory cache
+ /// and is expected to be stored in a "persistant" storage solution.
+ /// <para>
+ /// When this method returns, the <paramref name="entry"/> is no longer valid.
+ /// </para>
+ /// <para>
+ /// This method is called while the bucket lock is held. This call is maded
+ /// during an <see cref="IBlobCache.Add(string, CacheEntry)"/> method call.
+ /// </para>
+ /// </summary>
+ /// <param name="bucketId">The id of the bucket requesting the operation</param>
+ /// <param name="key">The key identifying the the entry</param>
+ /// <param name="entry">The entry containing the object data to store</param>
+ void OnEntryEvicted(uint bucketId, string key, in CacheEntry entry);
+
+ /// <summary>
+ /// Called when a cache item does not exist in main memory cache and should
+ /// be promoted from persistant cache to main memory cache.
+ /// <para>
+ /// This method is called while the bucket lock is held. This call is maded
+ /// during an <see cref="IBlobCache.Add(string, CacheEntry)"/> method call.
+ /// </para>
+ /// <para>
+ /// The <see cref="IMemoryCacheEntryFactory"/> should be used to create the
+ /// cache entry for the return value. Once this method returns, the caller owns the new <see cref="CacheEntry"/>
+ /// </para>
+ /// </summary>
+ /// <param name="key">The key identifying the entry to promot</param>
+ /// <param name="factory">The cache entry factory</param>
+ /// <param name="bucketId">The id of the bucket requesting the operation</param>
+ /// <param name="entry">The newly created entry when data is found</param>
+ /// <returns>
+ /// A value inidcating if the entry was successfully recovered from the persistant storage and
+ /// was successfully promoted.
+ /// </returns>
+ bool OnCacheMiss(uint bucketId, string key, IMemoryCacheEntryFactory factory, out CacheEntry entry);
+
+ /// <summary>
+ /// Removes an entry from the backing store
+ /// </summary>
+ /// <param name="key">The key identifying the entry to remove</param>
+ /// <param name="bucketId">The id of the bucket requesting the operation</param>
+ void OnEntryDeleted(uint bucketId, string key);
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs
index 7b0fe72..92d7048 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs
@@ -80,7 +80,7 @@ namespace VNLib.Plugins.Extensions.VNCache
}
//Setup cache table
- _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, _bufferHeap);
+ _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, _bufferHeap, null);
/*
* Default to json serialization by using the default
diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs
index 67fb550..fb0e9e2 100644
--- a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs
+++ b/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs
@@ -56,7 +56,7 @@ namespace VNLib.Plugins.Extensions.VNCache
MemoryCacheConfig memCacheConfig = config[VNCacheExtensions.MEMORY_CACHE_CONFIG_KEY].Deserialize<MemoryCacheConfig>()!;
//Setup cache table
- _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, Client.Config.BufferHeap ?? MemoryUtil.Shared);
+ _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, Client.Config.BufferHeap ?? MemoryUtil.Shared, null);
_cacheConfig = memCacheConfig;
diff --git a/plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs b/plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs
new file mode 100644
index 0000000..669b84f
--- /dev/null
+++ b/plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs
@@ -0,0 +1,242 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: ObjectCacheServer
+* File: CacheSystemUtil.cs
+*
+* CacheSystemUtil.cs is part of ObjectCacheServer which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* ObjectCacheServer is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* ObjectCacheServer is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+using VNLib.Plugins;
+using VNLib.Utils.Memory;
+using VNLib.Plugins.Extensions.Loading;
+
+namespace VNLib.Data.Caching.ObjectCache.Server
+{
+ internal static class CacheSystemUtil
+ {
+ const string PERSISTANT_ASM_CONFIF_KEY = "persistant_cache_asm";
+ const string USER_CACHE_ASM_CONFIG_KEY = "custom_cache_impl_asm";
+ const string LOAD_METHOD_NAME = "OnRuntimeLoad";
+ const string TEARDOWN_METHOD_NAME = "OnSystemDetach";
+
+ /// <summary>
+ /// Loads the <see cref="IBlobCacheTable"/> implementation (dynamic or default) into the process
+ /// and initializes it and it's backing store.
+ /// </summary>
+ /// <param name="plugin"></param>
+ /// <param name="config">The configuration object that contains loading variables</param>
+ /// <param name="heap">The heap for memory cache table to allocate buffers from</param>
+ /// <param name="cacheConf">The cache configuration object</param>
+ /// <returns>The loaded <see cref="IBlobCacheTable"/> implementation</returns>
+ /// <exception cref="FileNotFoundException"></exception>
+ public static IBlobCacheTable LoadMemoryCacheSystem(this PluginBase plugin, IConfigScope config, IUnmangedHeap heap, CacheConfiguration cacheConf)
+ {
+ //First, try to load persitant cache store
+ PersistantCacheManager? pCManager = GetPersistantStore(plugin, config);
+
+ IBlobCacheTable table;
+
+ //See if the user defined a custom cache table implementation
+ if (config.TryGetValue(USER_CACHE_ASM_CONFIG_KEY, out JsonElement customEl))
+ {
+ string asmName = customEl.GetString() ?? throw new FileNotFoundException("User defined a custom blob cache assembly but the file name was null");
+
+ //Return the runtime loaded table
+ table = LoadCustomMemCacheTable(plugin, asmName, pCManager);
+ }
+ else
+ {
+ //Default type
+ table = GetInternalBlobCache(heap, cacheConf, pCManager);
+ }
+
+ //Initialize the subsystem from the cache table
+ pCManager?.InitializeSubsystem(table);
+
+ return table;
+ }
+
+ private static IBlobCacheTable GetInternalBlobCache(IUnmangedHeap heap, CacheConfiguration config, IPersistantCacheStore? store)
+ {
+ return new BlobCacheTable(config.BucketCount, config.MaxCacheEntries, heap, store);
+ }
+
+ private static IBlobCacheTable LoadCustomMemCacheTable(PluginBase plugin, string asmName, IPersistantCacheStore? store)
+ {
+ //Load the custom assembly
+ AssemblyLoader<IBlobCacheTable> customTable = plugin.LoadAssembly<IBlobCacheTable>(asmName);
+
+ try
+ {
+ //Try get onload method and pass the persistant cache instance
+ Action<PluginBase, IPersistantCacheStore?>? onLoad = customTable.TryGetMethod<Action<PluginBase, IPersistantCacheStore?>>(LOAD_METHOD_NAME);
+ onLoad?.Invoke(plugin, store);
+ }
+ catch
+ {
+ customTable.Dispose();
+ throw;
+ }
+
+ return new RuntimeBlobCacheTable(customTable);
+ }
+
+ private static PersistantCacheManager? GetPersistantStore(PluginBase plugin, IConfigScope config)
+ {
+ //Get the persistant assembly
+ if (!config.TryGetValue(PERSISTANT_ASM_CONFIF_KEY, out JsonElement asmEl))
+ {
+ return null;
+ }
+
+ string? asmName = asmEl.GetString();
+ if (asmName == null)
+ {
+ return null;
+ }
+
+ //Load the dynamic assembly into the alc
+ AssemblyLoader<IPersistantCacheStore> loader = plugin.LoadAssembly<IPersistantCacheStore>(asmName);
+ try
+ {
+ //Call the OnLoad method
+ Action<PluginBase, IConfigScope>? loadMethod = loader.TryGetMethod<Action<PluginBase, IConfigScope>>(LOAD_METHOD_NAME);
+
+ loadMethod?.Invoke(plugin, config);
+ }
+ catch
+ {
+ loader.Dispose();
+ throw;
+ }
+
+ //Return the
+ return new(loader);
+ }
+
+
+ private sealed class RuntimeBlobCacheTable : IBlobCacheTable
+ {
+
+ private readonly IBlobCacheTable _table;
+ private readonly Action? OnDetatch;
+
+ public RuntimeBlobCacheTable(AssemblyLoader<IBlobCacheTable> loader)
+ {
+ OnDetatch = loader.TryGetMethod<Action>(TEARDOWN_METHOD_NAME);
+ _table = loader.Resource;
+ }
+
+ public void Dispose()
+ {
+ //We can let the loader dispose the cache table, but we can notify of detatch
+ OnDetatch?.Invoke();
+ }
+
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ IBlobCacheBucket IBlobCacheTable.GetBucket(ReadOnlySpan<char> objectId) => _table.GetBucket(objectId);
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IEnumerator<IBlobCacheBucket> GetEnumerator() => _table.GetEnumerator();
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_table).GetEnumerator();
+ }
+
+ internal sealed class PersistantCacheManager : IPersistantCacheStore
+ {
+ const string INITIALIZE_METHOD_NAME = "OnInitializeForBucket";
+
+
+ /*
+ * Our referrence can be technically unloaded, but so will
+ * this instance, since its loaded into the current ALC, so
+ * this referrence may exist for the lifetime of this instance.
+ *
+ * It also implements IDisposable, which the assembly loader class
+ * will call when this plugin is unloaded, we dont need to call
+ * it here, but we can signal a detach.
+ *
+ * Since the store implements IDisposable, its likely going to
+ * check for dispose on each call, so we don't need to add
+ * and additional disposed check since the method calls must be fast.
+ */
+
+ private readonly IPersistantCacheStore store;
+
+ private readonly Action<uint>? InitMethod;
+ private readonly Action? OnServiceDetatch;
+
+ public PersistantCacheManager(AssemblyLoader<IPersistantCacheStore> loader)
+ {
+ //Try to get the Initialize method
+ InitMethod = loader.TryGetMethod<Action<uint>>(INITIALIZE_METHOD_NAME);
+
+ //Get the optional detatch method
+ OnServiceDetatch = loader.TryGetMethod<Action>(TEARDOWN_METHOD_NAME);
+
+ store = loader.Resource;
+ }
+
+ /// <summary>
+ /// Optionally initializes the backing store by publishing the table's bucket
+ /// id's so it's made aware of the memory cache bucket system.
+ /// </summary>
+ /// <param name="table">The table containing buckets to publish</param>
+ public void InitializeSubsystem(IBlobCacheTable table)
+ {
+ //Itterate all buckets
+ foreach (IBlobCacheBucket bucket in table)
+ {
+ InitMethod?.Invoke(bucket.Id);
+ }
+ }
+
+ void IDisposable.Dispose()
+ {
+ //Assembly loader will dispose the type, we can just signal a detach
+
+ OnServiceDetatch?.Invoke();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ bool IPersistantCacheStore.OnCacheMiss(uint bucketId, string key, IMemoryCacheEntryFactory factory, out CacheEntry entry)
+ {
+ return store.OnCacheMiss(bucketId, key, factory, out entry);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IPersistantCacheStore.OnEntryDeleted(uint bucketId, string key) => store.OnEntryDeleted(bucketId, key);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ void IPersistantCacheStore.OnEntryEvicted(uint bucketId, string key, in CacheEntry entry) => store.OnEntryEvicted(bucketId, key, in entry);
+ }
+ }
+}
diff --git a/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs b/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs
index 1a7331d..6517537 100644
--- a/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs
+++ b/plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: ObjectCacheServer
@@ -37,6 +37,7 @@ using VNLib.Net.Http;
using VNLib.Utils.Async;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
+using VNLib.Data.Caching;
using VNLib.Hashing.IdentityUtility;
using VNLib.Net.Messaging.FBM;
using VNLib.Net.Messaging.FBM.Client;
@@ -46,6 +47,7 @@ using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Essentials.Endpoints;
using VNLib.Plugins.Essentials.Extensions;
+
namespace VNLib.Data.Caching.ObjectCache.Server
{
@@ -55,7 +57,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server
private static readonly TimeSpan AuthTokenExpiration = TimeSpan.FromSeconds(30);
private readonly string AudienceLocalServerId;
- private readonly BlobCacheLIstener Store;
+ private readonly BlobCacheListener Store;
private readonly PluginBase Pbase;
private readonly ConcurrentDictionary<string, AsyncQueue<ChangeEvent>> StatefulEventQueue;
@@ -105,7 +107,7 @@ namespace VNLib.Data.Caching.ObjectCache.Server
StatefulEventQueue = new(StringComparer.OrdinalIgnoreCase);
//Init the cache store
- Store = InitializeCache((ObjectCacheServerEntry)plugin, CacheConfig.BucketCount, CacheConfig.MaxCacheEntries);
+ Store = InitializeCache((ObjectCacheServerEntry)plugin, CacheConfig, config);
/*
* Generate a random guid for the current server when created so we
@@ -117,25 +119,30 @@ namespace VNLib.Data.Caching.ObjectCache.Server
_ = plugin.ObserveWork(this, 100);
}
- private static BlobCacheLIstener InitializeCache(ObjectCacheServerEntry plugin, uint buckets, uint maxCache)
+
+ private static BlobCacheListener InitializeCache(ObjectCacheServerEntry plugin, CacheConfiguration cacheConf, IConfigScope config)
{
- if(maxCache < 2)
+ if(cacheConf.MaxCacheEntries < 2)
{
throw new ArgumentException("You must configure a 'max_cache' size larger than 1 item");
}
//Suggestion
- if(maxCache < 200)
+ if(cacheConf.MaxCacheEntries < 200)
{
plugin.Log.Information("Suggestion: You may want a larger cache size, you have less than 200 items in cache");
}
- plugin.Log.Verbose("Creating cache store with {bc} buckets, with {mc} items/bucket", buckets, maxCache);
+ plugin.Log.Verbose("Creating cache store with {bc} buckets, with {mc} items/bucket", cacheConf.BucketCount, cacheConf.MaxCacheEntries);
+
+ //Load the blob cache table system
+ IBlobCacheTable bc = plugin.LoadMemoryCacheSystem(config, plugin.CacheHeap, cacheConf);
//Endpoint only allows for a single reader
- return new (buckets, maxCache, plugin.Log, plugin.CacheHeap, true);
+ return new (bc, plugin.Log, plugin.CacheHeap, true);
}
+
/// <summary>
/// Gets the configured cache store
/// </summary>
@@ -492,9 +499,9 @@ namespace VNLib.Data.Caching.ObjectCache.Server
private sealed class CacheStore : ICacheStore
{
- private readonly BlobCacheLIstener _cache;
+ private readonly BlobCacheListener _cache;
- public CacheStore(BlobCacheLIstener cache)
+ public CacheStore(BlobCacheListener cache)
{
_cache = cache;
}