/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Data.Caching.ObjectCache * File: BlobCache.cs * * BlobCache.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.Diagnostics; using System.Collections.Generic; using VNLib.Utils.Memory; using VNLib.Utils.Memory.Caching; namespace VNLib.Data.Caching.ObjectCache { /// /// A general purpose binary data storage /// public sealed class BlobCache : LRUCache, IBlobCache, IMemoryCacheEntryFactory { private bool disposedValue; private IPersistantCacheStore? _persistance; /// public override bool IsReadOnly { get; } /// protected override int MaxCapacity { get; } /// 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(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; //Update the lookup table size LookupTable.EnsureCapacity(maxCapacity); } /// protected override bool CacheMiss(string key, out CacheEntry value) { 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) { 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 entry) { //Try to get the node at the current key if (LookupTable.Remove(objectId, out LinkedListNode> ? node)) { //Remove the node from the ll List.Remove(node); //Get the stored blob entry = node.ValueRef.Value; //Update the node.Value = new KeyValuePair(newId, entry); //Add to end of list List.AddLast(node); //Re-add to lookup table with new key LookupTable.Add(newId, node); return true; } 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)) { return false; } //always dispose blob using (node.ValueRef.Value) { //Remove the node from the list List.Remove(node); } return true; } /// /// Removes all cache entires and disposes their held resources /// public override void Clear() { //Start from first node LinkedListNode>? node = List.First; //Classic ll node itteration while(node != null) { //Dispose the cache entry node.ValueRef.Value.Dispose(); //Move to next node node = node.Next; } //empty all cache entires in the store base.Clear(); } /// public bool Remove(string objectId, out CacheEntry entry) { //Try to get the stored object if(TryGetValue(objectId, out entry)) { //remove the entry and bypass the disposal bool result = base.Remove(objectId); #if DEBUG Debug.Assert(result == true); #endif return true; } entry = default; return false; } /// void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { Clear(); } disposedValue = true; } } /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } /// CacheEntry IMemoryCacheEntryFactory.CreateEntry(ReadOnlySpan entryData) { //Create entry from the internal heap return CacheEntry.Create(entryData, CacheHeap); } } }