aboutsummaryrefslogtreecommitdiff
path: root/libs/VNLib.Plugins.Sessions.Cache.Client/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/VNLib.Plugins.Sessions.Cache.Client/src')
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/MessageTooLargeException.cs51
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionStatusException.cs46
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionUpdateFailedException.cs41
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs64
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs48
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/RemoteSession.cs188
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionCacheClient.cs242
-rw-r--r--libs/VNLib.Plugins.Sessions.Cache.Client/src/VNLib.Plugins.Sessions.Cache.Client.csproj44
8 files changed, 724 insertions, 0 deletions
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/MessageTooLargeException.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/MessageTooLargeException.cs
new file mode 100644
index 0000000..5a096a6
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/MessageTooLargeException.cs
@@ -0,0 +1,51 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: MessageTooLargeException.cs
+*
+* MessageTooLargeException.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Runtime.Serialization;
+
+using VNLib.Net.Messaging.FBM;
+
+namespace VNLib.Plugins.Sessions.Cache.Client.Exceptions
+{
+ /// <summary>
+ /// Raised when a request message is too large to send to
+ /// the server and the server may close the connection.
+ /// </summary>
+ public class MessageTooLargeException : FBMException
+ {
+ ///<inheritdoc/>
+ public MessageTooLargeException()
+ { }
+ ///<inheritdoc/>
+ public MessageTooLargeException(string message) : base(message)
+ { }
+ ///<inheritdoc/>
+ public MessageTooLargeException(string message, Exception innerException) : base(message, innerException)
+ { }
+ ///<inheritdoc/>
+ protected MessageTooLargeException(SerializationInfo info, StreamingContext context) : base(info, context)
+ { }
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionStatusException.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionStatusException.cs
new file mode 100644
index 0000000..bc6b217
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionStatusException.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: SessionStatusException.cs
+*
+* SessionStatusException.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Runtime.Serialization;
+
+using VNLib.Plugins.Essentials.Sessions;
+
+namespace VNLib.Plugins.Sessions.Cache.Client.Exceptions
+{
+ /// <summary>
+ /// Raised when the status of the session is invalid and cannot be used
+ /// </summary>
+ public class SessionStatusException : SessionException
+ {
+ public SessionStatusException()
+ { }
+ public SessionStatusException(string message) : base(message)
+ { }
+ public SessionStatusException(string message, Exception innerException) : base(message, innerException)
+ { }
+ protected SessionStatusException(SerializationInfo info, StreamingContext context) : base(info, context)
+ { }
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionUpdateFailedException.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionUpdateFailedException.cs
new file mode 100644
index 0000000..6d572aa
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/Exceptions/SessionUpdateFailedException.cs
@@ -0,0 +1,41 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: SessionUpdateFailedException.cs
+*
+* SessionUpdateFailedException.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Runtime.Serialization;
+
+namespace VNLib.Plugins.Sessions.Cache.Client.Exceptions
+{
+ public class SessionUpdateFailedException : SessionStatusException
+ {
+ public SessionUpdateFailedException()
+ { }
+ public SessionUpdateFailedException(string message) : base(message)
+ { }
+ public SessionUpdateFailedException(string message, Exception innerException) : base(message, innerException)
+ { }
+ protected SessionUpdateFailedException(SerializationInfo info, StreamingContext context) : base(info, context)
+ { }
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs
new file mode 100644
index 0000000..df3c564
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs
@@ -0,0 +1,64 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: GlobalCacheStore.cs
+*
+* GlobalCacheStore.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Threading;
+using System.Threading.Tasks;
+
+using VNLib.Data.Caching;
+
+namespace VNLib.Plugins.Sessions.Cache.Client
+{
+ /// <summary>
+ /// A wrapper class to provide a <see cref="IRemoteCacheStore"/> from
+ /// a <see cref="IGlobalCacheProvider"/> client instance
+ /// </summary>
+ public sealed class GlobalCacheStore : IRemoteCacheStore
+ {
+ private readonly IGlobalCacheProvider _cache;
+
+ public GlobalCacheStore(IGlobalCacheProvider globalCache)
+ {
+ _cache = globalCache ?? throw new ArgumentNullException(nameof(globalCache));
+ }
+
+ ///<inheritdoc/>
+ public Task AddOrUpdateObjectAsync<T>(string objectId, string? newId, T obj, CancellationToken cancellationToken = default)
+ {
+ return _cache.AddOrUpdateAsync(objectId, newId, obj, cancellationToken);
+ }
+
+ ///<inheritdoc/>
+ public Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default)
+ {
+ return _cache.DeleteAsync(objectId, cancellationToken);
+ }
+
+ ///<inheritdoc/>
+ public Task<T?> GetObjectAsync<T>(string objectId, CancellationToken cancellationToken = default)
+ {
+ return _cache.GetAsync<T>(objectId, cancellationToken);
+ }
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs
new file mode 100644
index 0000000..2a8bd49
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: IRemoteCacheStore.cs
+*
+* IRemoteCacheStore.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Plugins.Sessions.Cache.Client
+{
+ /// <summary>
+ /// Represents an asynchronous interface to a remote cache store
+ /// </summary>
+ public interface IRemoteCacheStore
+ {
+ /// <summary>
+ /// Gets an object from the cache provider by key
+ /// </summary>
+ /// <typeparam name="T">The data type</typeparam>
+ /// <param name="objectId">The key/id of the object to recover</param>
+ /// <param name="cancellationToken">A token to cancel the operation</param>
+ /// <returns>A task that resolves the found object or null otherwise</returns>
+ Task<T?> GetObjectAsync<T>(string objectId, CancellationToken cancellationToken = default);
+
+ Task AddOrUpdateObjectAsync<T>(string objectId, string? newId, T obj, CancellationToken cancellationToken = default);
+
+ Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/RemoteSession.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/RemoteSession.cs
new file mode 100644
index 0000000..af2c969
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/RemoteSession.cs
@@ -0,0 +1,188 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: RemoteSession.cs
+*
+* RemoteSession.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using Microsoft.VisualStudio.Threading;
+
+using VNLib.Net.Http;
+using VNLib.Data.Caching.Exceptions;
+using VNLib.Utils.Extensions;
+using VNLib.Plugins.Essentials.Sessions;
+using VNLib.Plugins.Essentials.Extensions;
+
+namespace VNLib.Plugins.Sessions.Cache.Client
+{
+ /// <summary>
+ /// Base class for cacheable lazy initialized session entires
+ /// that exist in a remote caching server
+ /// </summary>
+ public abstract class RemoteSession : SessionBase
+ {
+ protected const string CREATED_TIME_ENTRY = "__.i.ctime";
+
+ protected IRemoteCacheStore Client { get; }
+ protected TimeSpan UpdateTimeout { get; }
+
+ private readonly AsyncLazyInitializer Initializer;
+
+ /// <summary>
+ /// The lazy loaded data-store
+ /// </summary>
+ protected Dictionary<string, string>? DataStore;
+
+ protected RemoteSession(string sessionId, IRemoteCacheStore client, TimeSpan backgroundTimeOut)
+ {
+ SessionID = sessionId;
+ UpdateTimeout = backgroundTimeOut;
+ Client = client;
+ Initializer = new(InitializeAsync, null);
+ }
+
+ /// <summary>
+ /// The data initializer, loads the data store from the connected cache server
+ /// </summary>
+ /// <returns>A task that completes when the get operation completes</returns>
+ protected virtual async Task InitializeAsync()
+ {
+ //Setup timeout cancellation for the get, to cancel it
+ using CancellationTokenSource cts = new(UpdateTimeout);
+ //get or create a new session
+ DataStore = await Client.GetObjectAsync<Dictionary<string, string>>(SessionID, cancellationToken: cts.Token);
+ }
+ /// <summary>
+ /// Updates the current sessin agaisnt the cache store
+ /// </summary>
+ /// <returns>A task that complets when the update has completed</returns>
+ protected virtual async Task ProcessUpdateAsync()
+ {
+ //Setup timeout cancellation for the update, to cancel it
+ using CancellationTokenSource cts = new(UpdateTimeout);
+ await Client.AddOrUpdateObjectAsync(SessionID, null, DataStore, cts.Token);
+ }
+ /// <summary>
+ /// Delets the current session in the remote store
+ /// </summary>
+ /// <returns>A task that completes when instance has been deleted</returns>
+ protected virtual async Task ProcessDeleteAsync()
+ {
+ //Setup timeout cancellation for the update, to cancel it
+ using CancellationTokenSource cts = new(UpdateTimeout);
+ try
+ {
+ await Client.DeleteObjectAsync(SessionID, cts.Token);
+ }
+ catch (ObjectNotFoundException)
+ {
+ //This is fine, if the object does not exist, nothing to invalidate
+ }
+ }
+
+ ///<inheritdoc/>
+ public override DateTimeOffset Created
+ {
+ get
+ {
+ //Deserialze the base32 ms
+ long unixMs = this.GetValueType<string, long>(CREATED_TIME_ENTRY);
+ //set created time from ms
+ return DateTimeOffset.FromUnixTimeMilliseconds(unixMs);
+ }
+
+ protected set => this.SetValueType(CREATED_TIME_ENTRY, value.ToUnixTimeMilliseconds());
+ }
+
+
+ ///<inheritdoc/>
+ protected override string IndexerGet(string key)
+ {
+ //Get the value at the key or an empty string as a default
+ return DataStore!.GetValueOrDefault(key, string.Empty);
+ }
+ ///<inheritdoc/>
+ protected override void IndexerSet(string key, string value)
+ {
+ //If the value is null, remove the key from the store
+ if (value == null)
+ {
+ //Set modified flag
+ IsModified |= DataStore!.Remove(key);
+ }
+ else
+ {
+ //Store the value at the specified key
+ DataStore![key] = value;
+ IsModified = true;
+ }
+ }
+
+
+ /*
+ * If the data-store is not found it means the session does not
+ * exist in cache, so its technically not dangerous to reuse,
+ * so the new mask needs to be set, but the old ID is going
+ * to be reused
+ */
+
+ /// <summary>
+ /// Waits for exclusive access to the session, and initializes
+ /// session data (loads it from the remote store)
+ /// </summary>
+ /// <param name="entity">The event to attach a session to</param>
+ /// <param name="cancellationToken">A token to cancel the operaion</param>
+ /// <returns></returns>
+ public virtual async Task WaitAndLoadAsync(IHttpEvent entity, CancellationToken cancellationToken)
+ {
+ //Wait for exclusive access
+ await base.WaitOneAsync(cancellationToken);
+ try
+ {
+ //Lazily initalize the current instance
+ await Initializer.InitializeAsync(cancellationToken);
+ //See if data-store is null (new session was created
+ if (DataStore == null)
+ {
+ //New session was created
+ DataStore = new(10);
+ //Set is-new flag
+ Flags.Set(IS_NEW_MSK);
+ //Set created time
+ Created = DateTimeOffset.UtcNow;
+ //Init ipaddress
+ UserIP = entity.Server.GetTrustedIp();
+ //Set modified flag so session will be updated
+ IsModified = true;
+ }
+ }
+ catch
+ {
+ MainLock.Release();
+ throw;
+ }
+ }
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionCacheClient.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionCacheClient.cs
new file mode 100644
index 0000000..800ad66
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionCacheClient.cs
@@ -0,0 +1,242 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Sessions.Cache.Client
+* File: SessionCacheClient.cs
+*
+* SessionCacheClient.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.
+*
+* VNLib.Plugins.Sessions.Cache.Client 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.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Net.Http;
+using VNLib.Utils.Async;
+using VNLib.Utils.Logging;
+using VNLib.Utils.Memory.Caching;
+using VNLib.Plugins.Essentials.Sessions;
+
+namespace VNLib.Plugins.Sessions.Cache.Client
+{
+
+ /// <summary>
+ /// A client that allows access to sessions located on external servers
+ /// </summary>
+ public abstract class SessionCacheClient : ICacheHolder
+ {
+ public class LRUSessionStore<T> : LRUCache<string, T>, ICacheHolder where T : ISession
+ {
+ internal AsyncQueue<T> ExpiredSessions { get; }
+
+ ///<inheritdoc/>
+ public override bool IsReadOnly => false;
+ ///<inheritdoc/>
+ protected override int MaxCapacity { get; }
+
+
+ public LRUSessionStore(int maxCapacity) : base(StringComparer.Ordinal)
+ {
+ MaxCapacity = maxCapacity;
+ ExpiredSessions = new (true, true);
+ }
+
+ ///<inheritdoc/>
+ protected override bool CacheMiss(string key, [NotNullWhen(true)] out T? value)
+ {
+ value = default;
+ return false;
+ }
+
+ ///<inheritdoc/>
+ protected override void Evicted(KeyValuePair<string, T> evicted)
+ {
+ //add to queue, the list lock should be held during this operatio
+ _ = ExpiredSessions.TryEnque(evicted.Value);
+ }
+
+ ///<inheritdoc/>
+ public void CacheClear()
+ {
+ foreach (KeyValuePair<string, T> value in List)
+ {
+ Evicted(value);
+ }
+ Clear();
+ }
+
+ ///<inheritdoc/>
+ public void CacheHardClear()
+ {
+ CacheClear();
+ }
+ }
+
+ protected readonly LRUSessionStore<RemoteSession> CacheTable;
+ protected readonly object CacheLock;
+ protected readonly int MaxLoadedEntires;
+
+ /// <summary>
+ /// The client used to communicate with the cache server
+ /// </summary>
+ protected IRemoteCacheStore Store { 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>
+ protected SessionCacheClient(IRemoteCacheStore client, int maxCacheItems)
+ {
+ MaxLoadedEntires = maxCacheItems;
+ CacheLock = new();
+ CacheTable = new(maxCacheItems);
+ Store = client;
+ }
+
+ private ulong _waitingCount;
+
+ /// <summary>
+ /// The number of pending connections waiting for results from the cache server
+ /// </summary>
+ public ulong WaitingConnections => _waitingCount;
+
+ /// <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)
+ {
+ 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
+ }
+
+ //Inc waiting count
+ Interlocked.Increment(ref _waitingCount);
+
+ try
+ {
+ //Load session-data
+ await session.WaitAndLoadAsync(entity, cancellationToken);
+ return session;
+ }
+ catch
+ {
+ //Remove the invalid cached session
+ lock (CacheLock)
+ {
+ _ = CacheTable.Remove(sessionId);
+ }
+ throw;
+ }
+ finally
+ {
+ //Dec waiting count
+ Interlocked.Decrement(ref _waitingCount);
+ }
+ }
+ 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);
+
+ /// <summary>
+ /// Begins waiting for expired sessions to be evicted from the cache table that
+ /// may have pending synchronization operations
+ /// </summary>
+ /// <param name="log"></param>
+ /// <param name="token"></param>
+ /// <returns></returns>
+ public async Task CleanupExpiredSessionsAsync(ILogProvider log, CancellationToken token)
+ {
+ while (true)
+ {
+ try
+ {
+ //Wait for expired session and dispose it
+ using RemoteSession session = await CacheTable.ExpiredSessions.DequeueAsync(token);
+
+ //Obtain lock on session
+ await session.WaitOneAsync(CancellationToken.None);
+
+ log.Verbose("Removed expired session {id}", session.SessionID);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch(Exception ex)
+ {
+ log.Error(ex);
+ }
+ }
+ }
+
+ ///<inheritdoc/>
+ public void CacheClear()
+ {
+
+ }
+ ///<inheritdoc/>
+ public void CacheHardClear()
+ {
+ //Cleanup cache when disconnected
+ lock (CacheLock)
+ {
+ CacheTable.CacheHardClear();
+ }
+ }
+ }
+}
diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/VNLib.Plugins.Sessions.Cache.Client.csproj b/libs/VNLib.Plugins.Sessions.Cache.Client/src/VNLib.Plugins.Sessions.Cache.Client.csproj
new file mode 100644
index 0000000..f41f9f8
--- /dev/null
+++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/VNLib.Plugins.Sessions.Cache.Client.csproj
@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <Authors>Vaughn Nugent</Authors>
+ <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <Version>1.0.1.1</Version>
+ <SignAssembly>True</SignAssembly>
+ <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <DocumentationFile></DocumentationFile>
+ <GenerateDocumentationFile>True</GenerateDocumentationFile>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
+ <AnalysisLevel>latest-all</AnalysisLevel>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <Deterministic>False</Deterministic>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.4.27" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\..\DataCaching\lib\VNLib.Data.Caching\src\VNLib.Data.Caching.csproj" />
+ </ItemGroup>
+
+</Project>