aboutsummaryrefslogtreecommitdiff
path: root/VNLib.Data.Caching.Global/GlobalDataCache.cs
blob: 91afafec647df8c644d1d78d7dac2d3b1ee2dd68 (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
/*
* Copyright (c) 2022 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Data.Caching.Global
* File: GlobalDataCache.cs 
*
* GlobalDataCache.cs is part of VNLib.Data.Caching.Global which is part of the larger 
* VNLib collection of libraries and utilities.
*
* VNLib.Data.Caching.Global is free software: you can redistribute it and/or modify 
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Data.Caching.Global 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 
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License 
* along with VNLib.Data.Caching.Global. If not, see http://www.gnu.org/licenses/.
*/

using VNLib.Data.Caching.Global.Exceptions;

namespace VNLib.Data.Caching.Global
{
    /// <summary>
    /// A static library for caching data in-process or a remote data 
    /// cache
    /// </summary>
    public static class GlobalDataCache
    {

        private static IGlobalCacheProvider? CacheProvider;
        private static CancellationTokenRegistration _reg;

        private static readonly object CacheLock = new();
        private static readonly Dictionary<string, WeakReference<object>> LocalCache = new();

        /// <summary>
        /// Gets a value that indicates if global cache is available
        /// </summary>
        public static bool IsAvailable => CacheProvider != null && CacheProvider.IsConnected;

        /// <summary>
        /// Sets the backing cache provider for the process-wide global cache
        /// </summary>
        /// <param name="cacheProvider">The cache provider instance</param>
        /// <param name="statusToken">A token that represents the store's validity</param>
        public static void SetProvider(IGlobalCacheProvider cacheProvider, CancellationToken statusToken)
        {
            CacheProvider = cacheProvider ?? throw new ArgumentNullException(nameof(cacheProvider));
            //Remove cache provider when cache provider is no longer valid
            _reg = statusToken.Register(Cleanup);
        }

        private static void Cleanup()
        {
            CacheProvider = null;
            //Clear local cache
            lock (CacheLock)
            {
                LocalCache.Clear();
            }
            _reg.Dispose();
        }

        /// <summary>
        /// Asynchronously gets a value from the global cache provider 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key identifying the object to recover from cache</param>
        /// <returns>The value if found, or null if it does not exist in the store</returns>
        /// <exception cref="GlobalCacheException"></exception>
        /// <exception cref="CacheNotLoadedException"></exception>
        public static async Task<T?> GetAsync<T>(string key) where T: class
        {            
            //Check local cache first
            lock (CacheLock)
            {
                if (LocalCache.TryGetValue(key, out WeakReference<object>? wr))
                {
                    //Value is found
                    if(wr.TryGetTarget(out object? value))
                    {
                        //Value exists and is loaded to local cache
                        return (T)value;
                    }
                    //Value has been collected
                    else
                    {
                        //Remove the key from the table
                        LocalCache.Remove(key);
                    }
                }
            }
            //get ref to local cache provider
            IGlobalCacheProvider? prov = CacheProvider;
            if(prov == null)
            {
                throw new CacheNotLoadedException("Global cache provider was not found");
            }
            //get the value from the store
            T? val = await prov.GetAsync<T>(key);
            //Only store the value if it was successfully found
            if (val != null)
            {
                //Store in local cache
                lock (CacheLock)
                {
                    LocalCache[key] = new WeakReference<object>(val);
                }
            }
            return val;
        }

        /// <summary>
        /// Asynchronously sets (or updates) a cached value in the global cache
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">The key identifying the object to recover from cache</param>
        /// <param name="value">The value to set at the given key</param>
        /// <returns>A task that completes when the update operation has compelted</returns>
        /// <exception cref="GlobalCacheException"></exception>
        /// <exception cref="CacheNotLoadedException"></exception>
        public static async Task SetAsync<T>(string key, T value) where T : class
        {
            //Local record is stale, allow it to be loaded from cache next call to get
            lock (CacheLock)
            {
                LocalCache.Remove(key);
            }
            //get ref to local cache provider
            IGlobalCacheProvider? prov = CacheProvider;
            if (prov == null)
            {
                throw new CacheNotLoadedException("Global cache provider was not found");
            }
            //set the value in the store
            await prov.SetAsync<T>(key, value);
        }

        /// <summary>
        /// Asynchronously deletes an item from cache by its key
        /// </summary>
        /// <param name="key"></param>
        /// <returns>A task that completes when the delete operation has compelted</returns>
        /// <exception cref="GlobalCacheException"></exception>
        /// <exception cref="CacheNotLoadedException"></exception>
        public static async Task DeleteAsync(string key)
        {
            //Delete from local cache 
            lock (CacheLock)
            {
                LocalCache.Remove(key);
            }
            //get ref to local cache provider
            IGlobalCacheProvider? prov = CacheProvider;
            if (prov == null)
            {
                throw new CacheNotLoadedException("Global cache provider was not found");
            }
            //Delete value from store
            await prov.DeleteAsync(key);
        }
    }
}