diff options
Diffstat (limited to 'Libs/VNLib.Plugins.Essentials.Sessions')
4 files changed, 56 insertions, 15 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs index a806438..d365cd2 100644 --- a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs +++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs @@ -28,23 +28,27 @@ using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Plugins.Essentials.Extensions; +using VNLib.Utils.Async; using VNLib.Net.Http; +using VNLib.Utils.Memory.Caching; using static VNLib.Plugins.Essentials.Sessions.ISessionExtensions; #nullable enable namespace VNLib.Plugins.Essentials.Sessions.Memory { - internal class MemorySession : SessionBase + internal class MemorySession : SessionBase, ICacheable { private readonly Dictionary<string, string> DataStorage; private readonly Func<IHttpEvent, string, string> OnSessionUpdate; + private readonly AsyncQueue<MemorySession> ExpiredTable; - public MemorySession(string sessionId, IPAddress ipAddress, Func<IHttpEvent, string, string> onSessionUpdate) + public MemorySession(string sessionId, IPAddress ipAddress, Func<IHttpEvent, string, string> onSessionUpdate, AsyncQueue<MemorySession> expired) { //Set the initial is-new flag DataStorage = new Dictionary<string, string>(10); + ExpiredTable = expired; OnSessionUpdate = onSessionUpdate; //Get new session id @@ -90,14 +94,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory //Memory session always completes return ValueTask.FromResult<Task?>(null); } - - protected override Task OnEvictedAsync() - { - //Clear all session data - DataStorage.Clear(); - return Task.CompletedTask; - } - + protected override string IndexerGet(string key) { return DataStorage.GetValueOrDefault(key, string.Empty); @@ -116,5 +113,18 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory } DataStorage[key] = value; } + + + DateTime ICacheable.Expires { get; set; } + + void ICacheable.Evicted() + { + DataStorage.Clear(); + //Enque cleanup + _ = ExpiredTable.TryEnque(this); + } + + bool IEquatable<ICacheable>.Equals(ICacheable? other) => other is ISession ses && SessionID.Equals(ses.SessionID, StringComparison.Ordinal); + } } diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs index 91a3a0e..f58129a 100644 --- a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs +++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs @@ -73,6 +73,9 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory _sessions = new(config); + //Begin listening for expired records + _ = plugin.DeferTask(() => _sessions.CleanupExiredAsync(localized, plugin.UnloadToken)); + //Schedule garbage collector _ = plugin.ScheduleInterval(this, TimeSpan.FromMinutes(1)); diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs index e76d7a4..1af885e 100644 --- a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs +++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs @@ -28,11 +28,12 @@ using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Net.Http; +using VNLib.Net.Http.Core; using VNLib.Utils; using VNLib.Utils.Async; +using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Extensions; -using VNLib.Net.Http.Core; #nullable enable @@ -47,12 +48,14 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory internal readonly MemorySessionConfig Config; internal readonly SessionIdFactory IdFactory; + internal readonly AsyncQueue<MemorySession> ExpiredSessions; public MemorySessionStore(MemorySessionConfig config) { Config = config; SessionsStore = new(config.MaxAllowedSessions, StringComparer.Ordinal); IdFactory = new(config.SessionIdSizeBytes, config.SessionCookieID, config.SessionTimeout); + ExpiredSessions = new(false, true); } ///<inheritdoc/> @@ -68,7 +71,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory if (IdFactory.TryGetSessionId(entity, out string? sessionId)) { //Try to get the old record or evict it - ERRNO result = SessionsStore.TryGetOrEvictRecord(sessionId, out MemorySession session); + ERRNO result = SessionsStore.TryGetOrEvictRecord(sessionId, out MemorySession? session); if(result > 0) { //Valid, now wait for exclusive access @@ -89,7 +92,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory return new(null, FileProcessArgs.VirtualSkip, null); } //Initialze a new session - session = new(sessionId, entity.Server.GetTrustedIp(), UpdateSessionId); + session = new(sessionId, entity.Server.GetTrustedIp(), UpdateSessionId, ExpiredSessions); //Increment the semaphore (session as IWaitHandle).WaitOne(); //store the session in cache while holding semaphore, and set its expiration @@ -104,6 +107,31 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory } } + public async Task CleanupExiredAsync(ILogProvider log, CancellationToken token) + { + while (true) + { + try + { + //Wait for expired session and dispose it + using MemorySession session = await 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); + } + } + } + private string UpdateSessionId(IHttpEvent entity, string oldId) { //Generate and set a new sessionid diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/VNLib.Plugins.Essentials.Sessions.Memory.csproj b/Libs/VNLib.Plugins.Essentials.Sessions/VNLib.Plugins.Essentials.Sessions.Memory.csproj index 0d3cb40..c4a65f5 100644 --- a/Libs/VNLib.Plugins.Essentials.Sessions/VNLib.Plugins.Essentials.Sessions.Memory.csproj +++ b/Libs/VNLib.Plugins.Essentials.Sessions/VNLib.Plugins.Essentials.Sessions.Memory.csproj @@ -36,8 +36,8 @@ </PackageReference> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\..\..\VNLib\Essentials\VNLib.Plugins.Essentials.csproj" /> - <ProjectReference Include="..\..\..\..\VNLib\Http\VNLib.Net.Http.csproj" /> + <ProjectReference Include="..\..\..\..\VNLib\Essentials\src\VNLib.Plugins.Essentials.csproj" /> + <ProjectReference Include="..\..\..\..\VNLib\Http\src\VNLib.Net.Http.csproj" /> <ProjectReference Include="..\..\..\Extensions\VNLib.Plugins.Extensions.Loading\VNLib.Plugins.Extensions.Loading.csproj" /> <ProjectReference Include="..\..\..\PluginBase\VNLib.Plugins.PluginBase.csproj" /> <ProjectReference Include="..\VNLib.Plugins.Essentials.Sessions.Runtime\VNLib.Plugins.Essentials.Sessions.Runtime.csproj" /> |