/* * 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); } } }