From 8b4fb26473256da5eaa89f3e9d2ac5d44f1e9b88 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 15 Jul 2023 13:06:00 -0400 Subject: Latest working draft --- .../ObjectCacheServer/src/Cache/CacheSystemUtil.cs | 242 +++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 plugins/ObjectCacheServer/src/Cache/CacheSystemUtil.cs (limited to 'plugins/ObjectCacheServer/src/Cache/CacheSystemUtil.cs') diff --git a/plugins/ObjectCacheServer/src/Cache/CacheSystemUtil.cs b/plugins/ObjectCacheServer/src/Cache/CacheSystemUtil.cs new file mode 100644 index 0000000..2071d2b --- /dev/null +++ b/plugins/ObjectCacheServer/src/Cache/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.Cache +{ + 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); + } + } +} -- cgit