aboutsummaryrefslogtreecommitdiff
path: root/plugins/ObjectCacheServer/src/Endpoints/CacheSystemUtil.cs
blob: 669b84f1864c81e375b4434cbe518493e07ef581 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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);
        }
    }
}