From db4584c37380f1826986b3acfe35bbf92693dfc6 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 11 Mar 2023 02:04:31 -0500 Subject: Persistant cache abstraction and runtime loading --- .../src/BlobCache.cs | 57 ++++- .../src/BlobCacheBucket.cs | 14 +- .../src/BlobCacheLIstener.cs | 23 +- .../src/BlobCacheTable.cs | 22 +- .../src/CacheEntry.cs | 39 ++-- .../src/IBlobCache.cs | 5 + .../src/IBlobCacheBucket.cs | 11 +- .../src/IMemoryCacheEntryFactory.cs | 43 ++++ .../src/IPersistantCacheStore.cs | 79 +++++++ .../src/MemoryCache.cs | 2 +- .../src/RemoteBackedMemoryCache.cs | 2 +- .../src/Endpoints/CacheSystemUtil.cs | 242 +++++++++++++++++++++ .../src/Endpoints/ConnectEndpoint.cs | 27 ++- 13 files changed, 503 insertions(+), 63 deletions(-) create mode 100644 lib/VNLib.Data.Caching.ObjectCache/src/IMemoryCacheEntryFactory.cs create mode 100644 lib/VNLib.Data.Caching.ObjectCache/src/IPersistantCacheStore.cs create mode 100644 plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs 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 /// /// A general purpose binary data storage /// - public sealed class BlobCache : LRUCache, IBlobCache + public sealed class BlobCache : LRUCache, IBlobCache, IMemoryCacheEntryFactory { private bool disposedValue; + private IPersistantCacheStore? _persistance; /// public override bool IsReadOnly { get; } @@ -48,21 +49,29 @@ namespace VNLib.Data.Caching.ObjectCache /// public IUnmangedHeap CacheHeap { get; } + /// + public uint BucketId { get; } /// /// Initializes a new store /// + /// The id of the bucket that manages this instance /// The maximum number of items to keep in memory /// The unmanaged heap used to allocate cache entry buffers from + /// The optional backing persistant cache storage /// - 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 /// 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); } /// protected override void Evicted(ref KeyValuePair 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(); + } } /// - 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> ? 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(newId, blob); + node.Value = new KeyValuePair(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; } /// 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>? node)) { @@ -192,5 +217,13 @@ namespace VNLib.Data.Caching.ObjectCache Dispose(disposing: true); GC.SuppressFinalize(this); } + + + /// + CacheEntry IMemoryCacheEntryFactory.CreateEntry(ReadOnlySpan 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 { + + /// + /// A concrete implementation of an + /// public sealed class BlobCacheBucket : IBlobCacheBucket { private readonly IBlobCache _cacheTable; private readonly SemaphoreSlim _lock; + /// + public uint Id { get; } + /// /// Initialzies a new and its underlying /// @@ -42,11 +49,14 @@ namespace VNLib.Data.Caching.ObjectCache /// The maxium number of entries allowed in the LRU cache /// before LRU overflow happens. /// + /// The unique id of the new bucket /// The heap to allocate object cache buffers - public BlobCacheBucket(int bucketCapacity, IUnmangedHeap heap) + /// An optional for cache persistance + 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); } /// 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 GetBodyDataCallback(T state); /// - /// A implementation of a + /// An for key-value object data caching servers. /// - public class BlobCacheLIstener : FBMListenerBase, IDisposable + public class BlobCacheListener : FBMListenerBase, IDisposable { private bool disposedValue; @@ -74,21 +74,22 @@ namespace VNLib.Data.Caching.ObjectCache /// - /// Initialzies a new + /// Initialzies a new /// - /// The maxium number of items per bucket - /// The number of cache store buckets - /// + /// The cache table to work from + /// Writes error and debug logging information /// The heap to alloc FBM buffers and cache buffers from /// A value that indicates if a single thread is processing events - public BlobCacheLIstener(uint buckets, uint cacheMax, ILogProvider log, IUnmangedHeap heap, bool singleReader) + /// + 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; /// /// Initializes a new @@ -46,9 +47,10 @@ namespace VNLib.Data.Caching.ObjectCache /// The number of elements in each bucket /// The number of buckets within the table /// The heap used to allocate cache entry buffers from + /// An optional for persistant cache implementations /// /// - 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 /// 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()); + } } /// 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 { /// /// A structure that represents an item in cache. It contains the binary content /// of a cache entry by its internal memory handle /// - public readonly struct CacheEntry : IDisposable, IEquatable + public readonly record struct CacheEntry : IDisposable { private const int TIME_SEGMENT_SIZE = sizeof(long); @@ -53,7 +55,7 @@ namespace VNLib.Data.Caching /// /// The initial data to store /// The heap to allocate the buffer from - /// The new + /// The newly initialized and ready to use public static CacheEntry Create(ReadOnlySpan 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 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); } - - /// - public override bool Equals(object? obj) => obj is CacheEntry entry && Equals(entry); - /// public override int GetHashCode() => _handle.GetHashCode(); - /// - public static bool operator ==(CacheEntry left, CacheEntry right) => left.Equals(right); - - /// - public static bool operator !=(CacheEntry left, CacheEntry right) => !(left == right); - - /// - public bool Equals(CacheEntry other) => other.GetHashCode() == GetHashCode(); + /// + /// Gets a 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. + /// + /// + /// WARNING: You must respect the return value so + /// as no to overrun the valid data segment. + /// + /// A handle that points to the begining of the data segment + [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 @@ -34,6 +34,11 @@ namespace VNLib.Data.Caching.ObjectCache /// public interface IBlobCache : IEnumerable>, IDisposable { + /// + /// The id of the bucket this memory cache belongs to + /// + public uint BucketId { get; } + /// /// The internal heap used to allocate buffers /// 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 @@ -34,6 +34,11 @@ namespace VNLib.Data.Caching.ObjectCache /// public interface IBlobCacheBucket : IDisposable { + /// + /// The unique integer id of a bucket within an + /// + uint Id { get; } + /// /// Gets a that holds an exclusive lock /// for the current bucekt and holds a referrence to the stored 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 +{ + /// + /// A factory abstraction that builds structures + /// linked to internally configured memory implementations, for cache + /// promotions. + /// + public interface IMemoryCacheEntryFactory + { + /// + /// Creates and initalizes a new from the desired object data + /// + /// The non-owned memory to copy into the the new + /// The newly initalized + CacheEntry CreateEntry(ReadOnlySpan 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 +{ + /// + /// Provides a persitance layer to memory caching. + /// + public interface IPersistantCacheStore : IDisposable + { + /// + /// Invoked when an entry has been evicted from main-memory cache + /// and is expected to be stored in a "persistant" storage solution. + /// + /// When this method returns, the is no longer valid. + /// + /// + /// This method is called while the bucket lock is held. This call is maded + /// during an method call. + /// + /// + /// The id of the bucket requesting the operation + /// The key identifying the the entry + /// The entry containing the object data to store + void OnEntryEvicted(uint bucketId, string key, in CacheEntry entry); + + /// + /// Called when a cache item does not exist in main memory cache and should + /// be promoted from persistant cache to main memory cache. + /// + /// This method is called while the bucket lock is held. This call is maded + /// during an method call. + /// + /// + /// The should be used to create the + /// cache entry for the return value. Once this method returns, the caller owns the new + /// + /// + /// The key identifying the entry to promot + /// The cache entry factory + /// The id of the bucket requesting the operation + /// The newly created entry when data is found + /// + /// A value inidcating if the entry was successfully recovered from the persistant storage and + /// was successfully promoted. + /// + bool OnCacheMiss(uint bucketId, string key, IMemoryCacheEntryFactory factory, out CacheEntry entry); + + /// + /// Removes an entry from the backing store + /// + /// The key identifying the entry to remove + /// The id of the bucket requesting the operation + 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()!; //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"; + + /// + /// Loads the implementation (dynamic or default) into the process + /// and initializes it and it's backing store. + /// + /// + /// The configuration object that contains loading variables + /// The heap for memory cache table to allocate buffers from + /// The cache configuration object + /// The loaded implementation + /// + 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 customTable = plugin.LoadAssembly(asmName); + + try + { + //Try get onload method and pass the persistant cache instance + Action? onLoad = customTable.TryGetMethod>(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 loader = plugin.LoadAssembly(asmName); + try + { + //Call the OnLoad method + Action? loadMethod = loader.TryGetMethod>(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 loader) + { + OnDetatch = loader.TryGetMethod(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(); + } + + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IBlobCacheBucket IBlobCacheTable.GetBucket(ReadOnlySpan objectId) => _table.GetBucket(objectId); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerator GetEnumerator() => _table.GetEnumerator(); + + /// + [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? InitMethod; + private readonly Action? OnServiceDetatch; + + public PersistantCacheManager(AssemblyLoader loader) + { + //Try to get the Initialize method + InitMethod = loader.TryGetMethod>(INITIALIZE_METHOD_NAME); + + //Get the optional detatch method + OnServiceDetatch = loader.TryGetMethod(TEARDOWN_METHOD_NAME); + + store = loader.Resource; + } + + /// + /// Optionally initializes the backing store by publishing the table's bucket + /// id's so it's made aware of the memory cache bucket system. + /// + /// The table containing buckets to publish + 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> 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); } + /// /// Gets the configured cache store /// @@ -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; } -- cgit