aboutsummaryrefslogtreecommitdiff
path: root/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
diff options
context:
space:
mode:
authorLibravatar vman <public@vaughnnugent.com>2022-10-30 02:28:12 -0400
committerLibravatar vman <public@vaughnnugent.com>2022-10-30 02:28:12 -0400
commita8510fb835dcc5e1142d700164ce5a4bd44e1a25 (patch)
tree28caab320f777a384cb6883b68dd999cdc8c0a3f /Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
Add project files.
Diffstat (limited to 'Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs')
-rw-r--r--Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs159
1 files changed, 159 insertions, 0 deletions
diff --git a/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs b/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
new file mode 100644
index 0000000..de0e370
--- /dev/null
+++ b/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils;
+using VNLib.Utils.Memory.Caching;
+using VNLib.Net.Http;
+using VNLib.Net.Messaging.FBM.Client;
+using VNLib.Plugins.Essentials.Sessions;
+
+#nullable enable
+
+namespace VNLib.Plugins.Sessions.Cache.Client
+{
+
+ /// <summary>
+ /// A client that allows access to sessions located on external servers
+ /// </summary>
+ public abstract class SessionCacheClient : VnDisposeable, ICacheHolder
+ {
+ public class LRUSessionStore<T> : LRUCache<string, T> where T : ISession, ICacheable
+ {
+ public override bool IsReadOnly => false;
+ protected override int MaxCapacity { get; }
+
+ public LRUSessionStore(int maxCapacity) : base(StringComparer.Ordinal) => MaxCapacity = maxCapacity;
+
+ protected override bool CacheMiss(string key, [NotNullWhen(true)] out T? value)
+ {
+ value = default;
+ return false;
+ }
+ protected override void Evicted(KeyValuePair<string, T> evicted)
+ {
+ //Evice record
+ evicted.Value.Evicted();
+ }
+ }
+
+ protected readonly LRUSessionStore<RemoteSession> CacheTable;
+ protected readonly object CacheLock;
+ protected readonly int MaxLoadedEntires;
+
+ protected FBMClient Client { get; }
+
+ /// <summary>
+ /// Initializes a new <see cref="SessionCacheClient"/>
+ /// </summary>
+ /// <param name="client"></param>
+ /// <param name="maxCacheItems">The maximum number of sessions to keep in memory</param>
+ public SessionCacheClient(FBMClient client, int maxCacheItems)
+ {
+ MaxLoadedEntires = maxCacheItems;
+ CacheLock = new();
+ CacheTable = new(maxCacheItems);
+ Client = client;
+ //Listen for close events
+ Client.ConnectionClosed += Client_ConnectionClosed;
+ }
+
+ private void Client_ConnectionClosed(object? sender, EventArgs e) => CacheHardClear();
+
+ /// <summary>
+ /// Attempts to get a session from the cache identified by its sessionId asynchronously
+ /// </summary>
+ /// <param name="entity">The connection/request to attach the session to</param>
+ /// <param name="sessionId">The ID of the session to retrieve</param>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A <see cref="ValueTask"/> that resolves the remote session</returns>
+ /// <exception cref="SessionException"></exception>
+ public virtual async ValueTask<RemoteSession> GetSessionAsync(IHttpEvent entity, string sessionId, CancellationToken cancellationToken)
+ {
+ Check();
+ try
+ {
+ RemoteSession? session;
+ //Aquire lock on cache
+ lock (CacheLock)
+ {
+ //See if session is loaded into cache
+ if (!CacheTable.TryGetValue(sessionId, out session))
+ {
+ //Init new record
+ session = SessionCtor(sessionId);
+ //Add to cache
+ CacheTable.Add(session.SessionID, session);
+ }
+ //Valid entry found in cache
+ }
+ try
+ {
+ //Load session-data
+ await session.WaitAndLoadAsync(entity, cancellationToken);
+ return session;
+ }
+ catch
+ {
+ //Remove the invalid cached session
+ lock (CacheLock)
+ {
+ _ = CacheTable.Remove(sessionId);
+ }
+ throw;
+ }
+ }
+ catch (SessionException)
+ {
+ throw;
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ //Wrap exceptions
+ catch (Exception ex)
+ {
+ throw new SessionException("An unhandled exception was raised", ex);
+ }
+ }
+
+ /// <summary>
+ /// Gets a new <see cref="RemoteSession"/> instances for the given sessionId,
+ /// and places it a the head of internal cache
+ /// </summary>
+ /// <param name="sessionId">The session identifier</param>
+ /// <returns>The new session for the given ID</returns>
+ protected abstract RemoteSession SessionCtor(string sessionId);
+
+ ///<inheritdoc/>
+ public void CacheClear()
+ {
+
+ }
+ ///<inheritdoc/>
+ public void CacheHardClear()
+ {
+ //Cleanup cache when disconnected
+ lock (CacheLock)
+ {
+ CacheTable.Clear();
+ foreach (RemoteSession session in (IEnumerable<RemoteSession>)CacheTable)
+ {
+ session.Evicted();
+ }
+ CacheTable.Clear();
+ }
+ }
+
+ protected override void Free()
+ {
+ //Unsub from events
+ Client.ConnectionClosed -= Client_ConnectionClosed;
+ //Clear all cached sessions
+ CacheHardClear();
+ }
+ }
+}