diff options
Diffstat (limited to 'lib')
22 files changed, 656 insertions, 162 deletions
diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs index 525227c..5a425ec 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCache.cs @@ -26,7 +26,6 @@ using System; using System.Diagnostics; using System.Collections.Generic; -using VNLib.Utils.Memory; using VNLib.Utils.Memory.Caching; namespace VNLib.Data.Caching.ObjectCache @@ -47,7 +46,7 @@ namespace VNLib.Data.Caching.ObjectCache protected override int MaxCapacity { get; } ///<inheritdoc/> - public IUnmangedHeap CacheHeap { get; } + public ICacheEntryMemoryManager MemoryManager { get; } ///<inheritdoc/> public uint BucketId { get; } @@ -57,10 +56,10 @@ namespace VNLib.Data.Caching.ObjectCache /// </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="manager">The cache entry memory manager instance</param> /// <param name="store">The optional backing persistant cache storage</param> /// <exception cref="ArgumentException"></exception> - public BlobCache(uint bucketId, int maxCapacity, IUnmangedHeap heap, IPersistantCacheStore? store) + public BlobCache(uint bucketId, int maxCapacity, ICacheEntryMemoryManager manager, IPersistantCacheStore? store) :base(maxCapacity, StringComparer.Ordinal) { if(maxCapacity < 1) @@ -72,7 +71,7 @@ namespace VNLib.Data.Caching.ObjectCache _persistance = store; - CacheHeap = heap; + MemoryManager = manager ?? throw new ArgumentNullException(nameof(manager)); MaxCapacity = maxCapacity; @@ -188,7 +187,7 @@ namespace VNLib.Data.Caching.ObjectCache //remove the entry and bypass the disposal bool result = base.Remove(objectId); - Debug.Assert(result == true); + Debug.Assert(result == true, "The cache entry was found in the table, but failed to remove"); return true; } @@ -223,7 +222,7 @@ namespace VNLib.Data.Caching.ObjectCache CacheEntry IMemoryCacheEntryFactory.CreateEntry(ReadOnlySpan<byte> entryData) { //Create entry from the internal heap - return CacheEntry.Create(entryData, CacheHeap); + return CacheEntry.Create(entryData, MemoryManager); } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs index 6af1a20..71c815d 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheBucket.cs @@ -25,8 +25,6 @@ using System.Threading; using System.Threading.Tasks; -using VNLib.Utils.Memory; - namespace VNLib.Data.Caching.ObjectCache { @@ -50,13 +48,13 @@ namespace VNLib.Data.Caching.ObjectCache /// 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> + /// <param name="memMan">The cache entry memory manager intance</param> /// <param name="persistantCache">An optional <see cref="IPersistantCacheStore"/> for cache persistance</param> - public BlobCacheBucket(uint bucketId, int bucketCapacity, IUnmangedHeap heap, IPersistantCacheStore? persistantCache) + public BlobCacheBucket(uint bucketId, int bucketCapacity, ICacheEntryMemoryManager memMan, IPersistantCacheStore? persistantCache) { Id = bucketId; _lock = new(1, 1); - _cacheTable = new BlobCache(bucketId, bucketCapacity, heap, persistantCache); + _cacheTable = new BlobCache(bucketId, bucketCapacity, memMan, persistantCache); } ///<inheritdoc/> @@ -69,16 +67,8 @@ namespace VNLib.Data.Caching.ObjectCache ///<inheritdoc/> public async ValueTask<IBlobCache> ManualWaitAsync(CancellationToken cancellation) { - //try to enter the lock synchronously - if (_lock.Wait(0, CancellationToken.None)) - { - return _cacheTable; - } - else - { - await _lock.WaitAsync(cancellation).ConfigureAwait(false); - return _cacheTable; - } + await _lock.WaitAsync(cancellation).ConfigureAwait(false); + return _cacheTable; } ///<inheritdoc/> @@ -86,20 +76,5 @@ namespace VNLib.Data.Caching.ObjectCache { _lock.Release(); } - - ///<inheritdoc/> - public async ValueTask<CacheBucketHandle> WaitAsync(CancellationToken cancellation) - { - //try to enter the lock synchronously - if (_lock.Wait(0, CancellationToken.None)) - { - return new(this, _cacheTable); - } - else - { - await _lock.WaitAsync(cancellation).ConfigureAwait(false); - return new(this, _cacheTable); - } - } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs index 4a8692d..a114236 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheExtensions.cs @@ -34,9 +34,42 @@ namespace VNLib.Data.Caching.ObjectCache /// </summary> public static class BlobCacheExtensions { + + /// <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"/> + /// </summary> + /// <param name="bucket"></param> + /// <param name="cancellation">A token to cancel the wait operation</param> + /// <returns>A <see cref="CacheBucketHandle"/> that holds the <see cref="IBlobCache"/> referrence</returns> + public static ValueTask<CacheBucketHandle> WaitAsync(this IBlobCacheBucket bucket, CancellationToken cancellation) + { + _ = bucket ?? throw new ArgumentNullException(nameof(bucket)); + + //Try enter the bucket lock + ValueTask<IBlobCache> cacheWait = bucket.ManualWaitAsync(cancellation); + + if (cacheWait.IsCompleted) + { + IBlobCache bucketHandle = cacheWait.GetAwaiter().GetResult(); + return new ValueTask<CacheBucketHandle>(new CacheBucketHandle(bucket, bucketHandle)); + } + else + { + return GetHandleAsync(cacheWait, bucket); + } + + static async ValueTask<CacheBucketHandle> GetHandleAsync(ValueTask<IBlobCache> waitTask, IBlobCacheBucket bucket) + { + IBlobCache cache = await waitTask.ConfigureAwait(false); + return new CacheBucketHandle(bucket, cache); + } + } + internal static CacheEntry CreateEntry(this IBlobCache cache, string objectId, ReadOnlySpan<byte> initialData, DateTime time) { - CacheEntry entry = CacheEntry.Create(initialData, cache.CacheHeap); + CacheEntry entry = CacheEntry.Create(initialData, cache.MemoryManager); try { //try to add the entry, but if exists, let it throw @@ -115,17 +148,27 @@ namespace VNLib.Data.Caching.ObjectCache DateTime time, CancellationToken cancellation = default) { + + _ = table ?? throw new ArgumentNullException(nameof(table)); + _ = bodyData ?? throw new ArgumentNullException(nameof(bodyData)); + //See if an id change is required if (string.IsNullOrWhiteSpace(alternateId)) { //safe to get the bucket for the primary id IBlobCacheBucket bucket = table.GetBucket(objectId); - //Wait for the bucket - using CacheBucketHandle handle = await bucket.WaitAsync(cancellation); + //Wait for the bucket to be available + IBlobCache cache = await bucket.ManualWaitAsync(cancellation); - //add/update for single entity - _ = handle.Cache.AddOrUpdateEntry(objectId, bodyData(state), time); + try + { + _ = cache.AddOrUpdateEntry(objectId, bodyData(state), time); + } + finally + { + bucket.Release(); + } } else { @@ -136,11 +179,17 @@ namespace VNLib.Data.Caching.ObjectCache //Same bucket if (ReferenceEquals(primary, alternate)) { - //wait for lock on only one bucket otherwise dealock - using CacheBucketHandle handle = await primary.WaitAsync(cancellation); + IBlobCache cache = await primary.ManualWaitAsync(cancellation); - //Update the entry for the single bucket - _ = handle.Cache.TryChangeKey(objectId, alternateId, bodyData(state), time); + try + { + //Update the entry for the single bucket + _ = cache.TryChangeKey(objectId, alternateId, bodyData(state), time); + } + finally + { + primary.Release(); + } } else { @@ -187,11 +236,17 @@ namespace VNLib.Data.Caching.ObjectCache //Try to get the bucket that the id should belong to IBlobCacheBucket bucket = table.GetBucket(objectId); - //Wait for lock on bucket async - using CacheBucketHandle handle = await bucket.WaitAsync(cancellation); + //Wait for the bucket + IBlobCache cache = await bucket.ManualWaitAsync(cancellation); - //Remove the object from the blob store - return handle.Cache.Remove(objectId); + try + { + return cache.Remove(objectId); + } + finally + { + bucket.Release(); + } } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs index f3f1b50..9443737 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/BlobCacheTable.cs @@ -28,10 +28,10 @@ using System.Collections; using System.Collections.Generic; using VNLib.Utils; -using VNLib.Utils.Memory; namespace VNLib.Data.Caching.ObjectCache { + /// <summary> /// A concrete implementation of a <see cref="IBlobCacheTable"/> /// </summary> @@ -41,18 +41,32 @@ namespace VNLib.Data.Caching.ObjectCache private readonly IBlobCacheBucket[] _buckets; private readonly IPersistantCacheStore? _persistant; + /// <summary> /// Initializes a new <see cref="BlobCacheTable"/> /// </summary> /// <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="manager">A single cache memory manger to share across all buckets</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, IPersistantCacheStore? persistantCache) + public BlobCacheTable(uint tableSize, uint bucketSize, ICacheEntryMemoryManager manager, IPersistantCacheStore? persistantCache) + :this(tableSize, bucketSize, new SharedMemManager(manager), persistantCache) + { } + + /// <summary> + /// Initializes a new <see cref="BlobCacheTable"/> + /// </summary> + /// <param name="bucketSize">The number of elements in each bucket</param> + /// <param name="tableSize">The number of buckets within the table</param> + /// <param name="factory">A factory that can generate bucket-local memory managers</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, ICacheMemoryManagerFactory factory, IPersistantCacheStore? persistantCache) { - _ = heap ?? throw new ArgumentNullException(nameof(heap)); + _ = factory ?? throw new ArgumentNullException(nameof(factory)); if(tableSize == 0) { @@ -66,15 +80,18 @@ namespace VNLib.Data.Caching.ObjectCache _persistant = persistantCache; //Init buckets - InitBuckets(tableSize, bucketSize, _buckets, heap, persistantCache); + InitBuckets(tableSize, bucketSize, _buckets, factory, persistantCache); } - private static void InitBuckets(uint size, uint bucketSize, IBlobCacheBucket[] table, IUnmangedHeap heap, IPersistantCacheStore? persistantCache) + private static void InitBuckets(uint size, uint bucketSize, IBlobCacheBucket[] table, ICacheMemoryManagerFactory man, IPersistantCacheStore? persistantCache) { for(uint i = 0; i < size; i++) { - table[i] = new BlobCacheBucket(i, (int)bucketSize, heap, persistantCache); + //Get the memory manager for the bucket + ICacheEntryMemoryManager manager = man.CreateForBucket(i); + + table[i] = new BlobCacheBucket(i, (int)bucketSize, manager, persistantCache); } } @@ -143,5 +160,12 @@ namespace VNLib.Data.Caching.ObjectCache Check(); return _buckets.AsEnumerable().GetEnumerator(); } + + private sealed record class SharedMemManager(ICacheEntryMemoryManager Manager) : ICacheMemoryManagerFactory + { + ///<inheritdoc/> + public ICacheEntryMemoryManager CreateForBucket(uint bucketId) => Manager; + + } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs b/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs index 9183d0a..917052f 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/CacheEntry.cs @@ -29,10 +29,10 @@ 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 @@ -45,28 +45,28 @@ namespace VNLib.Data.Caching private const int DATA_SEGMENT_START = TIME_SEGMENT_SIZE + LENGTH_SEGMENT_SIZE; - - //Only contain ref to backing handle to keep struct size small - private readonly MemoryHandle<byte> _handle; - + private readonly ICacheEntryMemoryManager _manager; + private readonly object _handle; /// <summary> /// Creates a new <see cref="CacheEntry"/> and copies the initial data to the internal buffer /// </summary> /// <param name="data">The initial data to store</param> - /// <param name="heap">The heap to allocate the buffer from</param> + /// <param name="dataManager">The heap to allocate the buffer from</param> /// <returns>The newly initialized and ready to use <see cref="CacheEntry"/></returns> - public static CacheEntry Create(ReadOnlySpan<byte> data, IUnmangedHeap heap) + /// <exception cref="ArgumentNullException"></exception> + public static CacheEntry Create(ReadOnlySpan<byte> data, ICacheEntryMemoryManager dataManager) { + _ = dataManager ?? throw new ArgumentNullException(nameof(dataManager)); + //Calc buffer size - int bufferSize = GetRequiredHandleSize(data.Length); + uint bufferSize = GetRequiredHandleSize(data.Length); - //Alloc buffer - MemoryHandle<byte> handle = heap.Alloc<byte>(bufferSize); + object handle = dataManager.AllocHandle(bufferSize); //Create new entry from handle - CacheEntry entry = new (handle); - entry.SetLength(data.Length); + CacheEntry entry = new(dataManager, handle); + entry.SetLength((uint)data.Length); //Get the data segment Span<byte> segment = entry.GetDataSegment(); @@ -79,24 +79,46 @@ namespace VNLib.Data.Caching return entry; } + /// <summary> + /// Creates a new <see cref="CacheEntry"/> from an existing handle + /// </summary> + /// <param name="handle">The cache data handle to create the entry around</param> + /// <param name="manager">The cache entry memory manager the handle blongs to</param> + /// <returns>The re-constructed entry</returns> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentException"></exception> + public static CacheEntry FromExistingHandle(object handle, ICacheEntryMemoryManager manager) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + _ = manager ?? throw new ArgumentNullException(nameof(manager)); + + //validate handle size it at least the minimum size + if (manager.GetHandleSize(handle) < DATA_SEGMENT_START) + { + throw new ArgumentException("Memory segment is too small to be a valid cache entry"); + } + + return new(manager, handle); + } - private static int GetRequiredHandleSize(int size) + private static uint GetRequiredHandleSize(int size) { //Caculate the minimum handle size to store all required information, rounded to nearest page - return (int)MemoryUtil.NearestPage(size + DATA_SEGMENT_START); + return (uint)MemoryUtil.NearestPage(size + DATA_SEGMENT_START); } - private CacheEntry(MemoryHandle<byte> handle) + private CacheEntry(ICacheEntryMemoryManager manager, object handle) { + _manager = manager; _handle = handle; } ///<inheritdoc/> - public readonly void Dispose() => _handle?.Dispose(); + public readonly void Dispose() => _manager?.FreeHandle(_handle); - private readonly Span<byte> GetTimeSegment() => _handle.AsSpan(0, TIME_SEGMENT_SIZE); + private readonly Span<byte> GetTimeSegment() => _manager.GetSpan(_handle, 0, TIME_SEGMENT_SIZE); - private readonly Span<byte> GetLengthSegment() => _handle.AsSpan(TIME_SEGMENT_SIZE, LENGTH_SEGMENT_SIZE); + private readonly Span<byte> GetLengthSegment() => _manager.GetSpan(_handle, TIME_SEGMENT_SIZE, LENGTH_SEGMENT_SIZE); /// <summary> /// Gets the size of the block of memory held by the underlying handle @@ -104,11 +126,7 @@ namespace VNLib.Data.Caching /// <returns>The size of the block held by the current entry</returns> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly nuint GetMemoryUsage() - { - _handle.ThrowIfClosed(); - return _handle.ByteLength; - } + public readonly nuint GetMemoryUsage() => _manager.GetHandleSize(_handle); /// <summary> /// Gets the last set time @@ -148,21 +166,21 @@ namespace VNLib.Data.Caching /// <returns>The length of the data segment</returns> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly int GetLength() + public readonly uint GetLength() { //Get the length segment ReadOnlySpan<byte> segment = GetLengthSegment(); //Recover the integer - return BinaryPrimitives.ReadInt32BigEndian(segment); + return BinaryPrimitives.ReadUInt32BigEndian(segment); } - private readonly void SetLength(int length) + private readonly void SetLength(uint length) { //Get the length segment Span<byte> segment = GetLengthSegment(); //Update the length value - BinaryPrimitives.WriteInt32BigEndian(segment, length); + BinaryPrimitives.WriteUInt32BigEndian(segment, length); } /// <summary> @@ -174,9 +192,9 @@ namespace VNLib.Data.Caching public readonly Span<byte> GetDataSegment() { //Get the actual length of the segment - int length = GetLength(); + uint length = GetLength(); //Get the segment from its begining offset and - return _handle.AsSpan(DATA_SEGMENT_START, length); + return _manager.GetSpan(_handle, DATA_SEGMENT_START, length); } /// <summary> @@ -188,13 +206,17 @@ namespace VNLib.Data.Caching public readonly void UpdateData(ReadOnlySpan<byte> data) { //Calc required buffer size - int bufferSize = GetRequiredHandleSize(data.Length); + uint bufferSize = GetRequiredHandleSize(data.Length); - //Resize handle if required - _handle.ResizeIfSmaller(bufferSize); + //Resize buffer if necessary + if(_manager.GetHandleSize(_handle) < bufferSize) + { + //resize handle + _manager.ResizeHandle(_handle, bufferSize); + } //Reset data length - SetLength(data.Length); + SetLength((uint)data.Length); //Get the data segment Span<byte> segment = GetDataSegment(); @@ -206,9 +228,6 @@ namespace VNLib.Data.Caching data.CopyTo(segment); } - ///<inheritdoc/> - public override int GetHashCode() => _handle.GetHashCode(); - /// <summary> /// Gets a <see cref="MemoryHandle"/> offset to the start of the /// internal data segment, and avoids calling the fixed keyword. @@ -216,14 +235,25 @@ namespace VNLib.Data.Caching /// </summary> /// <remarks> /// WARNING: You must respect the <see cref="GetLength"/> return value so - /// as no to overrun the valid data segment. + /// as not 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); + return _manager.PinHandle(_handle, DATA_SEGMENT_START); + } + + /// <summary> + /// Gets the internal memory handle and manager its associated with + /// </summary> + /// <param name="handle">The opaque memory handle</param> + /// <param name="manager">The associated memory manager</param> + public readonly void GetInternalHandle(out object handle, out ICacheEntryMemoryManager manager) + { + handle = _handle; + manager = _manager; } } } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs index bc3180b..7333898 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCache.cs @@ -25,8 +25,6 @@ using System; using System.Collections.Generic; -using VNLib.Utils.Memory; - namespace VNLib.Data.Caching.ObjectCache { /// <summary> @@ -40,9 +38,9 @@ namespace VNLib.Data.Caching.ObjectCache public uint BucketId { get; } /// <summary> - /// The internal heap used to allocate <see cref="CacheEntry"/> buffers + /// The memory manager used to create <see cref="CacheEntry"/> memory handles /// </summary> - IUnmangedHeap CacheHeap { get; } + ICacheEntryMemoryManager MemoryManager { get; } /// <summary> /// Attempts to retreive the entry at the given id. diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs index dbe095c..b506600 100644 --- a/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs +++ b/lib/VNLib.Data.Caching.ObjectCache/src/IBlobCacheBucket.cs @@ -40,15 +40,6 @@ namespace VNLib.Data.Caching.ObjectCache 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"/> - /// </summary> - /// <param name="cancellation">A token to cancel the wait operation</param> - /// <returns>A <see cref="CacheBucketHandle"/> that holds the <see cref="IBlobCache"/> referrence</returns> - ValueTask<CacheBucketHandle> WaitAsync(CancellationToken cancellation); - - /// <summary> /// Allows for waiting for the cache directly, IE without receiving a lock handle /// </summary> /// <param name="cancellation"></param> @@ -56,7 +47,7 @@ namespace VNLib.Data.Caching.ObjectCache ValueTask<IBlobCache> ManualWaitAsync(CancellationToken cancellation); /// <summary> - /// Releases an exlcusive lock on the current bucket, DO NOT CALL BY USER CODE + /// Releases an exlcusive lock on the current bucket that was obtained by <see cref="ManualWaitAsync(CancellationToken)"/> /// </summary> void Release(); } diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/ICacheEntryMemoryManager.cs b/lib/VNLib.Data.Caching.ObjectCache/src/ICacheEntryMemoryManager.cs new file mode 100644 index 0000000..dffbfa2 --- /dev/null +++ b/lib/VNLib.Data.Caching.ObjectCache/src/ICacheEntryMemoryManager.cs @@ -0,0 +1,80 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Data.Caching.ObjectCache +* File: ICacheEntryMemoryManager.cs +* +* ICacheEntryMemoryManager.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; +using System.Buffers; + +namespace VNLib.Data.Caching +{ + /// <summary> + /// Provides a way to allocate and manage opaque memory hanles for a cache entry data + /// </summary> + public interface ICacheEntryMemoryManager + { + /// <summary> + /// Allocates a new handle of at-least the specified size or larger. + /// </summary> + /// <param name="size">The desired minimum size of the handle</param> + /// <returns>A referrence to the newly allocated handle</returns> + object AllocHandle(uint size); + + /// <summary> + /// Resizes the handle to the new size. Usually a larger size + /// than the current size. + /// </summary> + /// <param name="handle">A referrence to the existing handle</param> + /// <param name="newSize"></param> + void ResizeHandle(object handle, uint newSize); + + /// <summary> + /// Frees the prevously allocated handle + /// </summary> + /// <param name="handle">A referrence to the previously allocated handle</param> + void FreeHandle(object handle); + + /// <summary> + /// Pins the handle to the specified offset and returns a + /// <see cref="MemoryHandle"/> to the pinned memory block. + /// </summary> + /// <param name="handle"></param> + /// <param name="offset"></param> + /// <returns></returns> + MemoryHandle PinHandle(object handle, int offset); + + /// <summary> + /// Gets the full usable size of the memory block held by the handle + /// </summary> + /// <returns>The number of bytes available for access</returns> + uint GetHandleSize(object handle); + + /// <summary> + /// Gets a segment of the memory block held by the handle for reading/writing + /// </summary> + /// <param name="handle">A referrence to the handle object</param> + /// <param name="offset">The data offset in bytes for the start of the desired memory block</param> + /// <param name="length">The desired size of the block in bytes</param> + /// <returns>A span with the desired offset of the desired length</returns> + Span<byte> GetSpan(object handle, uint offset, uint length); + } +} diff --git a/lib/VNLib.Data.Caching.ObjectCache/src/ICacheMemoryManagerFactory.cs b/lib/VNLib.Data.Caching.ObjectCache/src/ICacheMemoryManagerFactory.cs new file mode 100644 index 0000000..99a3b66 --- /dev/null +++ b/lib/VNLib.Data.Caching.ObjectCache/src/ICacheMemoryManagerFactory.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Data.Caching.ObjectCache +* File: ICacheMemoryManagerFactory.cs +* +* ICacheMemoryManagerFactory.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/. +*/ + +namespace VNLib.Data.Caching.ObjectCache +{ + /// <summary> + /// Provides bucket-local memory managers for <see cref="IBlobCacheBucket"/>s + /// to use for their internal memory management + /// </summary> + public interface ICacheMemoryManagerFactory + { + /// <summary> + /// Creates a new <see cref="ICacheEntryMemoryManager"/> for the specified bucket + /// identified by the given <paramref name="bucketId"/> + /// </summary> + /// <param name="bucketId">The unique id of a bucket within the table</param> + /// <returns></returns> + ICacheEntryMemoryManager CreateForBucket(uint bucketId); + } +} diff --git a/lib/VNLib.Data.Caching/src/ClientExtensions.cs b/lib/VNLib.Data.Caching/src/ClientExtensions.cs index eced7fe..946c9b5 100644 --- a/lib/VNLib.Data.Caching/src/ClientExtensions.cs +++ b/lib/VNLib.Data.Caching/src/ClientExtensions.cs @@ -136,7 +136,7 @@ namespace VNLib.Data.Caching //Check ok status code, then its safe to deserialize if (status.Value.Equals(ResponseCodes.Okay, StringComparison.Ordinal)) { - return (T?)deserialzer.Deserialze(typeof(T), response.ResponseBody); + return deserialzer.Deserialze<T>(response.ResponseBody); } //Object may not exist on the server yet diff --git a/lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs b/lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs index ec3fdb6..3cdb395 100644 --- a/lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs +++ b/lib/VNLib.Data.Caching/src/ICacheObjectDeserialzer.cs @@ -36,8 +36,8 @@ namespace VNLib.Data.Caching /// object state. /// </summary> /// <param name="objectData">The buffer containing data to deserialze</param> - /// <param name="type">The type to deserialze</param> + /// <typeparam name="T"></typeparam> /// <returns>A new instance deserialzed to contain the original entity state</returns> - object? Deserialze(Type type, ReadOnlySpan<byte> objectData); + T? Deserialze<T>(ReadOnlySpan<byte> objectData); } } diff --git a/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs b/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs index eb782d9..8a857d4 100644 --- a/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs +++ b/lib/VNLib.Data.Caching/src/IGlobalCacheProvider.cs @@ -101,13 +101,11 @@ namespace VNLib.Data.Caching /// Asynchronously sets (or updates) a cached value in the backing cache store /// from the supplied raw data /// </summary> - /// <typeparam name="T"></typeparam> /// <param name="key">The key identifying the object to recover from cache</param> /// <param name="newKey">An optional key that will be changed for the new object</param> /// <param name="cancellation">A token to cancel the async operation</param> /// <param name="rawData">The raw data to store at the given key</param> - /// <param name="serialzer">The <see cref="ICacheObjectSerialzer"/> used to serialze the entity</param> /// <returns>A task that completes when the update operation has compelted</returns> - Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation); + Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation); } }
\ No newline at end of file diff --git a/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs b/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs index 7f52169..dce0bdf 100644 --- a/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs +++ b/lib/VNLib.Data.Caching/src/JsonCacheObjectSerializer.cs @@ -75,10 +75,7 @@ namespace VNLib.Data.Caching } ///<inheritdoc/> - public virtual object? Deserialze(Type type, ReadOnlySpan<byte> objectData) - { - return JsonSerializer.Deserialize(objectData, type, _options); - } + public virtual T? Deserialze<T>(ReadOnlySpan<byte> objectData) => JsonSerializer.Deserialize<T>(objectData, _options); ///<inheritdoc/> public virtual void Serialize<T>(T obj, IBufferWriter<byte> finiteWriter) diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs new file mode 100644 index 0000000..cea06a3 --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/BucketLocalManagerFactory.cs @@ -0,0 +1,154 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: ObjectCacheServer +* File: BucketLocalManagerFactory.cs +* +* BucketLocalManagerFactory.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.Buffers; +using System.Text.Json; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +using VNLib.Plugins; +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; +using VNLib.Plugins.Extensions.Loading; + +namespace VNLib.Data.Caching.ObjectCache.Server +{ + [ConfigurationName("memory_manager", Required = false)] + internal sealed class BucketLocalManagerFactory : VnDisposeable, ICacheMemoryManagerFactory + { + private readonly LinkedList<BucketLocalManager> _managers = new (); + private readonly bool _zeroAll; + + ///<inheritdoc/> + public ICacheEntryMemoryManager CreateForBucket(uint bucketId) + { + //Init a new heap for a individual bucket + IUnmangedHeap localHeap = MemoryUtil.InitializeNewHeapForProcess(); + + BucketLocalManager manager = new (localHeap, bucketId, _zeroAll); + _managers.AddLast(manager); + + return manager; + } + + /// <summary> + /// Creates a new <see cref="BucketLocalManagerFactory"/> with the specified zero all flag + /// that is not managed by a plugin instance + /// </summary> + /// <param name="zeroAll">Forces all allocations to be zeroed before being returned to callers</param> + /// <returns></returns> + public static BucketLocalManagerFactory Create(bool zeroAll) => new (zeroAll); + + private BucketLocalManagerFactory(bool zeroAll) + { + _zeroAll = zeroAll; + } + + public BucketLocalManagerFactory(PluginBase plugin) : this(plugin, null) + { } + + public BucketLocalManagerFactory(PluginBase plugin, IConfigScope? config) + { + if (config != null) + { + //Try to get the zero all flag + if (config.TryGetValue("zero_all", out JsonElement zeroEl)) + { + _zeroAll = zeroEl.GetBoolean(); + } + } + } + + protected override void Free() + { + //Free heaps on exit + foreach (BucketLocalManager manager in _managers) + { + manager.Heap.Dispose(); + } + } + + /* + * Buckets are mutually exclusive, so we can use a single heap for each bucket + * to get a little more performance on memory operations + */ + + private sealed record class BucketLocalManager(IUnmangedHeap Heap, uint BucketId, bool Zero) : ICacheEntryMemoryManager + { + + ///<inheritdoc/> + public object AllocHandle(uint size) => Heap.Alloc<byte>(size, Zero); + + ///<inheritdoc/> + public void FreeHandle(object handle) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); + + //Free the handle + _handle.Dispose(); + } + + ///<inheritdoc/> + public uint GetHandleSize(object handle) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); + + return (uint)_handle.Length; + } + + ///<inheritdoc/> + public Span<byte> GetSpan(object handle, uint offset, uint length) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); + + return _handle.GetOffsetSpan(offset, checked((int)length)); + } + + ///<inheritdoc/> + public MemoryHandle PinHandle(object handle, int offset) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); + + //Pin the handle + return _handle.Pin(offset); + } + + ///<inheritdoc/> + public void ResizeHandle(object handle, uint newSize) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + MemoryHandle<byte> _handle = Unsafe.As<object, MemoryHandle<byte>>(ref handle); + + //Resize the handle + _handle.ResizeIfSmaller(newSize); + } + } + } +} diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs index 73baa4a..363e1c9 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/EntityCacheExtensions.cs @@ -54,12 +54,12 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// <param name="cancellation">A token to cancel the operation</param> /// <returns>A task that completes when the delete operation has compelted</returns> /// <exception cref="ArgumentNullException"></exception> - public static Task DeleteAsync<T>(this IGlobalCacheProvider cache, T entity, CancellationToken cancellation) where T: class, ICacheEntity + public static Task RemoveAsync<T>(this IEntityCache<T> cache, T entity, CancellationToken cancellation) where T: class, ICacheEntity { _ = entity ?? throw new ArgumentNullException(nameof(entity)); _ = cache ?? throw new ArgumentNullException(nameof(entity)); //Delete by its id - return cache.DeleteAsync(entity.Id, cancellation); + return cache.RemoveAsync(entity.Id, cancellation); } /// <summary> @@ -71,27 +71,85 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel /// <param name="entity">The entity to set at the given key</param> /// <returns>A task that completes when the add/update operation has compelted</returns> /// <exception cref="ArgumentNullException"></exception> - public static Task AddOrUpdateAsync<T>(this IGlobalCacheProvider cache, T entity, CancellationToken cancellation) where T: class, ICacheEntity + public static Task UpsertAsync<T>(this IEntityCache<T> cache, T entity, CancellationToken cancellation) where T: class, ICacheEntity { _ = entity ?? throw new ArgumentNullException(nameof(entity)); _ = cache ?? throw new ArgumentNullException(nameof(cache)); //Add/update with its id - return cache.AddOrUpdateAsync(entity.Id, null, entity, cancellation); + return cache.UpsertAsync(entity.Id, entity, cancellation); + } + + /// <summary> + /// Creates an <see cref="IEntityCache{T}"/> wrapper using the current global cache provider. + /// Understand this will share the same cache store as other stores. Consider creating a scoped cache + /// to avoid key collisions + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="cache"></param> + /// <param name="serialier">The entity data serializer</param> + /// <param name="deserializer">The entity data deserializer</param> + /// <returns>The new <see cref="IEntityCache{T}"/> wrapper instance</returns> + /// <exception cref="ArgumentNullException"></exception> + public static IEntityCache<T> CreateEntityCache<T>(this IGlobalCacheProvider cache, ICacheObjectSerialzer serialier, ICacheObjectDeserialzer deserializer) where T: class + { + _ = cache ?? throw new ArgumentNullException(nameof(cache)); + _ = serialier ?? throw new ArgumentNullException(nameof(serialier)); + _ = deserializer ?? throw new ArgumentNullException(nameof(deserializer)); + + return new EntityCacheImpl<T>(cache, deserializer, serialier); + } + + /// <summary> + /// Creates an <see cref="IEntityCache{T}"/> wrapper using the current global cache provider, + /// with a Json serializer/deserializer + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="cache"></param> + /// <returns>The new <see cref="IEntityCache{T}"/> wrapper using json serialization</returns> + /// <exception cref="ArgumentNullException"></exception> + public static IEntityCache<T> CreateJsonEntityCache<T>(this IGlobalCacheProvider cache) where T: class + { + _ = cache ?? throw new ArgumentNullException(nameof(cache)); + JsonCacheObjectSerializer json = new(); + return CreateEntityCache<T>(cache, json, json); + } + + private sealed class EntityCacheImpl<T> : IEntityCache<T> where T : class + { + private readonly IGlobalCacheProvider _cacheProvider; + private readonly ICacheObjectDeserialzer _cacheObjectDeserialzer; + private readonly ICacheObjectSerialzer _cacheObjectSerialzer; + + public EntityCacheImpl(IGlobalCacheProvider cache, ICacheObjectDeserialzer deserializer, ICacheObjectSerialzer serializer) + { + _cacheProvider = cache; + _cacheObjectDeserialzer = deserializer; + _cacheObjectSerialzer = serializer; + } + + ///<inheritdoc/> + public Task<T?> GetAsync(string id, CancellationToken token = default) => _cacheProvider.GetAsync<T>(id, _cacheObjectDeserialzer, token); + + ///<inheritdoc/> + public Task RemoveAsync(string id, CancellationToken token = default) => _cacheProvider.DeleteAsync(id, token); + + ///<inheritdoc/> + public Task UpsertAsync(string id, T entity, CancellationToken token = default) => _cacheProvider.AddOrUpdateAsync(id, null, entity, _cacheObjectSerialzer, token); } - private sealed class ScopedCacheImpl: ScopedCache { private readonly IGlobalCacheProvider cache; + ///<inheritdoc/> public override bool IsConnected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => cache.IsConnected; } - + ///<inheritdoc/> protected override ICacheKeyGenerator KeyGen { get; } public ScopedCacheImpl(IGlobalCacheProvider cache, ICacheKeyGenerator keyGen) @@ -171,7 +229,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel } ///<inheritdoc/> - public override Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation) + public override Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) { _ = key ?? throw new ArgumentNullException(nameof(key)); @@ -181,7 +239,7 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel //If newkey exists, compute the secondary key string? secondary = newKey != null ? KeyGen.ComputedKey(newKey) : null; - return cache.AddOrUpdateAsync(primary, secondary, rawData, serialzer, cancellation); + return cache.AddOrUpdateAsync(primary, secondary, rawData, cancellation); } } } diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs new file mode 100644 index 0000000..e99591b --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/IEntityCache.cs @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.VNCache +* File: IEntityCache.cs +* +* IEntityCache.cs is part of VNLib.Plugins.Extensions.VNCache +* which is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.VNCache 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.Plugins.Extensions.VNCache 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.Threading; +using System.Threading.Tasks; + +namespace VNLib.Plugins.Extensions.VNCache.DataModel +{ + /// <summary> + /// Represents a cache that stores referrence type entities + /// </summary> + /// <typeparam name="T">The referrence entity type</typeparam> + public interface IEntityCache<T> where T : class + { + /// <summary> + /// Gets an entity from the cache by its id. Returns null if the entity is not found + /// </summary> + /// <param name="id">The id of the entity to retrieve from the store</param> + /// <param name="token">A token to cancel the operation</param> + /// <returns> The entity if found, null otherwise</returns> + Task<T?> GetAsync(string id, CancellationToken token = default); + + /// <summary> + /// Upserts an entity into the cache by its id. This updates an existing entity + /// or inserts a new one. + /// </summary> + /// <param name="id">The id of the entity to update</param> + /// <param name="entity">A referrence to the entity instance to update</param> + /// <param name="token">A token to cancel the operation</param> + /// <returns>A task that completes when the update has completed successfully</returns> + Task UpsertAsync(string id, T entity, CancellationToken token = default); + + /// <summary> + /// Removes an entity from the cache by its id + /// </summary> + /// <param name="id">The id of the item to remove</param> + /// <param name="token">A token to cancel delete opdation</param> + /// <returns>A task that completes when the item has been deleted successfully</returns> + Task RemoveAsync(string id, CancellationToken token = default); + } + +} diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs index c26fd1a..d949bde 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/DataModel/ScopedCache.cs @@ -65,6 +65,6 @@ namespace VNLib.Plugins.Extensions.VNCache.DataModel public abstract Task GetAsync(string key, IObjectData rawData, CancellationToken cancellation); ///<inheritdoc/> - public abstract Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation); + public abstract Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation); } } diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs index e4c95a4..a56529b 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCache.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Plugins.Extensions.VNCache -* File: RemoteBackedMemoryCache.cs +* File: MemoryCache.cs * -* RemoteBackedMemoryCache.cs is part of VNLib.Plugins.Extensions.VNCache +* MemoryCache.cs is part of VNLib.Plugins.Extensions.VNCache * which is part of the larger VNLib collection of libraries and utilities. * * VNLib.Plugins.Extensions.VNCache is free software: you can redistribute it and/or modify @@ -34,6 +34,7 @@ using VNLib.Utils.Memory.Diagnostics; using VNLib.Data.Caching; using VNLib.Data.Caching.ObjectCache; using VNLib.Plugins.Extensions.Loading; +using VNLib.Data.Caching.ObjectCache.Server; namespace VNLib.Plugins.Extensions.VNCache { @@ -59,6 +60,7 @@ namespace VNLib.Plugins.Extensions.VNCache private readonly ICacheObjectDeserialzer _deserialzer; private readonly IBlobCacheTable _memCache; private readonly IUnmangedHeap _bufferHeap; + private readonly BucketLocalManagerFactory _blobCacheMemManager; public MemoryCache(PluginBase pbase, IConfigScope config) :this( @@ -66,11 +68,10 @@ namespace VNLib.Plugins.Extensions.VNCache pbase.IsDebug(), pbase.Log ) - { - } + { } public MemoryCache(MemoryCacheConfig config):this(config, false, null) - {} + { } private MemoryCache(MemoryCacheConfig config, bool isDebug, ILogProvider? log) { @@ -83,7 +84,7 @@ namespace VNLib.Plugins.Extensions.VNCache IUnmangedHeap newHeap = MemoryUtil.InitializeNewHeapForProcess(); //Wrap in diag heap - _bufferHeap = new TrackedHeapWrapper(newHeap); + _bufferHeap = new TrackedHeapWrapper(newHeap, true); } else { @@ -91,8 +92,10 @@ namespace VNLib.Plugins.Extensions.VNCache _bufferHeap = MemoryUtil.InitializeNewHeapForProcess(); } + _blobCacheMemManager = BucketLocalManagerFactory.Create(config.ZeroAllAllocations); + //Setup cache table - _memCache = new BlobCacheTable(config.TableSize, config.BucketSize, _bufferHeap, null); + _memCache = new BlobCacheTable(config.TableSize, config.BucketSize, _blobCacheMemManager, null); /* * Default to json serialization by using the default @@ -124,13 +127,11 @@ namespace VNLib.Plugins.Extensions.VNCache { _memCache.Dispose(); _bufferHeap.Dispose(); + _blobCacheMemManager.Dispose(); } ///<inheritdoc/> - public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) - { - return AddOrUpdateAsync(key, newKey, value, _serialzer, cancellation); - } + public Task AddOrUpdateAsync<T>(string key, string? newKey, T value, CancellationToken cancellation) => AddOrUpdateAsync(key, newKey, value, _serialzer, cancellation); ///<inheritdoc/> public async Task AddOrUpdateAsync<T>(string key, string? newKey, T value, ICacheObjectSerialzer serialzer, CancellationToken cancellation) @@ -164,17 +165,23 @@ namespace VNLib.Plugins.Extensions.VNCache IBlobCacheBucket bucket = _memCache.GetBucket(key); - //Obtain cache handle - using (CacheBucketHandle handle = await bucket.WaitAsync(cancellation)) + //Obtain lock + IBlobCache cache = await bucket.ManualWaitAsync(cancellation); + + try { //Try to read the value - if (handle.Cache.TryGetValue(key, out CacheEntry entry)) + if (cache.TryGetValue(key, out CacheEntry entry)) { - return (T?)deserializer.Deserialze(typeof(T), entry.GetDataSegment()); + return deserializer.Deserialze<T>(entry.GetDataSegment()); } - } - return default; + return default; + } + finally + { + bucket.Release(); + } } ///<inheritdoc/> @@ -185,19 +192,26 @@ namespace VNLib.Plugins.Extensions.VNCache //Get the bucket from the desired key IBlobCacheBucket bucket = _memCache.GetBucket(key); - //Obtain cache handle - using CacheBucketHandle handle = await bucket.WaitAsync(cancellation); - - //Try to read the value - if (handle.Cache.TryGetValue(key, out CacheEntry entry)) + //Obtain lock + IBlobCache cache = await bucket.ManualWaitAsync(cancellation); + + try + { + //Try to read the value + if (cache.TryGetValue(key, out CacheEntry entry)) + { + //Set result data + rawData.SetData(entry.GetDataSegment()); + } + } + finally { - //Set result data - rawData.SetData(entry.GetDataSegment()); + bucket.Release(); } } ///<inheritdoc/> - public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation) + public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) { Check(); diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs index f34ae91..57c2793 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/MemoryCacheConfig.cs @@ -81,6 +81,12 @@ namespace VNLib.Plugins.Extensions.VNCache set => RefreshInterval = TimeSpan.FromSeconds(value); } + /// <summary> + /// Zeros all cache entry memory allocations before they are used + /// </summary> + [JsonPropertyName("zero_all")] + public bool ZeroAllAllocations { get; set; } + ///<inheritdoc/> public void Validate() { diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs index 0e92c22..2ab97b8 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/RemoteBackedMemoryCache.cs @@ -38,6 +38,7 @@ using VNLib.Data.Caching; using VNLib.Data.Caching.ObjectCache; using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.Loading.Events; +using VNLib.Data.Caching.ObjectCache.Server; namespace VNLib.Plugins.Extensions.VNCache { @@ -59,6 +60,7 @@ namespace VNLib.Plugins.Extensions.VNCache private readonly ICacheObjectSerialzer _serialzer; private readonly ICacheObjectDeserialzer _deserialzer; private readonly IBlobCacheTable _memCache; + private readonly BucketLocalManagerFactory? _bucketFactory; public RemoteBackedMemoryCache(PluginBase plugin, IConfigScope config) : base(plugin, config) { @@ -69,8 +71,10 @@ namespace VNLib.Plugins.Extensions.VNCache memCacheConfig.Validate(); + ICacheMemoryManagerFactory manager = plugin.GetOrCreateSingleton<BucketLocalManagerFactory>(); + //Setup cache table - _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, Client.Config.BufferHeap, null); + _memCache = new BlobCacheTable(memCacheConfig.TableSize, memCacheConfig.BucketSize, manager, null); _cacheConfig = memCacheConfig; @@ -92,8 +96,14 @@ namespace VNLib.Plugins.Extensions.VNCache public RemoteBackedMemoryCache(VnCacheClientConfig client, MemoryCacheConfig memCache, ILogProvider? debugLog):base(client, debugLog) { + /* + * Create a local bucket manager factory, we must handle dispal + * however, since its not managed by a plugin + */ + _bucketFactory = BucketLocalManagerFactory.Create(memCache.ZeroAllAllocations); + //Setup mem cache table - _memCache = new BlobCacheTable(memCache.TableSize, memCache.BucketSize, Client.Config.BufferHeap, null); + _memCache = new BlobCacheTable(memCache.TableSize, memCache.BucketSize, _bucketFactory, null); _cacheConfig = memCache; @@ -126,6 +136,7 @@ namespace VNLib.Plugins.Extensions.VNCache finally { _memCache.Dispose(); + _bucketFactory?.Dispose(); } } @@ -164,7 +175,7 @@ namespace VNLib.Plugins.Extensions.VNCache //Try to read the value if (handle.Cache.TryGetValue(key, out CacheEntry entry)) { - return (T?)deserializer.Deserialze(objType, entry.GetDataSegment()); + return deserializer.Deserialze<T>(entry.GetDataSegment()); } } @@ -184,7 +195,7 @@ namespace VNLib.Plugins.Extensions.VNCache await _memCache.AddOrUpdateObjectAsync(key, null, static b => b.GetData(), getBuffer, DateTime.UtcNow, CancellationToken.None); //Deserialze the entity - return (T)deserializer.Deserialze(objType, getBuffer.GetData()); + return deserializer.Deserialze<T>(getBuffer.GetData()); } ///<inheritdoc/> @@ -248,7 +259,7 @@ namespace VNLib.Plugins.Extensions.VNCache } ///<inheritdoc/> - public override async Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation) + public override async Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) { CheckConnected(); diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs index b0142a8..e2d0176 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/VnCacheClient.cs @@ -295,11 +295,11 @@ namespace VNLib.Plugins.Extensions.VNCache } ///<inheritdoc/> - public virtual Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation) + public virtual Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) { return !IsConnected ? throw new InvalidOperationException("The underlying client is not connected to a cache node") - : Client!.AddOrUpdateObjectAsync(key, newKey, rawData, serialzer, cancellation); + : Client!.AddOrUpdateObjectAsync(key, newKey, rawData, cancellation); } private sealed class AuthManager : ICacheAuthManager diff --git a/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs b/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs index 786a085..4ad6560 100644 --- a/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs +++ b/lib/VNLib.Plugins.Extensions.VNCache/src/VnGlobalCache.cs @@ -180,9 +180,9 @@ namespace VNLib.Plugins.Extensions.VNCache } ///<inheritdoc/> - public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, ICacheObjectSerialzer serialzer, CancellationToken cancellation) + public Task AddOrUpdateAsync(string key, string? newKey, IObjectData rawData, CancellationToken cancellation) { - return _client.AddOrUpdateAsync(key, newKey, rawData, serialzer, cancellation); + return _client.AddOrUpdateAsync(key, newKey, rawData, cancellation); } } }
\ No newline at end of file |