diff options
Diffstat (limited to 'plugins/ObjectCacheServer')
-rw-r--r-- | plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs | 242 | ||||
-rw-r--r-- | plugins/ObjectCacheServer/src/Endpoints/ConnectEndpoint.cs | 27 |
2 files changed, 259 insertions, 10 deletions
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"; + + /// <summary> + /// Loads the <see cref="IBlobCacheTable"/> implementation (dynamic or default) into the process + /// and initializes it and it's backing store. + /// </summary> + /// <param name="plugin"></param> + /// <param name="config">The configuration object that contains loading variables</param> + /// <param name="heap">The heap for memory cache table to allocate buffers from</param> + /// <param name="cacheConf">The cache configuration object</param> + /// <returns>The loaded <see cref="IBlobCacheTable"/> implementation</returns> + /// <exception cref="FileNotFoundException"></exception> + 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<IBlobCacheTable> customTable = plugin.LoadAssembly<IBlobCacheTable>(asmName); + + try + { + //Try get onload method and pass the persistant cache instance + Action<PluginBase, IPersistantCacheStore?>? onLoad = customTable.TryGetMethod<Action<PluginBase, IPersistantCacheStore?>>(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<IPersistantCacheStore> loader = plugin.LoadAssembly<IPersistantCacheStore>(asmName); + try + { + //Call the OnLoad method + Action<PluginBase, IConfigScope>? loadMethod = loader.TryGetMethod<Action<PluginBase, IConfigScope>>(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<IBlobCacheTable> loader) + { + OnDetatch = loader.TryGetMethod<Action>(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(); + } + + + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IBlobCacheBucket IBlobCacheTable.GetBucket(ReadOnlySpan<char> objectId) => _table.GetBucket(objectId); + + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerator<IBlobCacheBucket> GetEnumerator() => _table.GetEnumerator(); + + ///<inheritdoc/> + [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<uint>? InitMethod; + private readonly Action? OnServiceDetatch; + + public PersistantCacheManager(AssemblyLoader<IPersistantCacheStore> loader) + { + //Try to get the Initialize method + InitMethod = loader.TryGetMethod<Action<uint>>(INITIALIZE_METHOD_NAME); + + //Get the optional detatch method + OnServiceDetatch = loader.TryGetMethod<Action>(TEARDOWN_METHOD_NAME); + + store = loader.Resource; + } + + /// <summary> + /// Optionally initializes the backing store by publishing the table's bucket + /// id's so it's made aware of the memory cache bucket system. + /// </summary> + /// <param name="table">The table containing buckets to publish</param> + 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<string, AsyncQueue<ChangeEvent>> 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); } + /// <summary> /// Gets the configured cache store /// </summary> @@ -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; } |