aboutsummaryrefslogtreecommitdiff
path: root/Libs
diff options
context:
space:
mode:
Diffstat (limited to 'Libs')
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs12
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.OAuth/O2SessionProviderEntry.cs11
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.OAuth/OAuth2SessionProvider.cs22
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.OAuth/VNLib.Plugins.Essentials.Sessions.OAuth.csproj2
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VNLib.Plugins.Essentials.Sessions.Runtime.csproj4
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VnCacheClient.cs35
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.VNCache/VNLib.Plugins.Essentials.Sessions.VNCache.csproj2
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProvider.cs22
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProviderEntry.cs6
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs30
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs3
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs34
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/VNLib.Plugins.Essentials.Sessions.Memory.csproj4
-rw-r--r--Libs/VNLib.Plugins.Sessions.Cache.Client/Exceptions/SessionStatusException.cs3
-rw-r--r--Libs/VNLib.Plugins.Sessions.Cache.Client/RemoteSession.cs17
-rw-r--r--Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs123
-rw-r--r--Libs/VNLib.Plugins.Sessions.Cache.Client/VNLib.Plugins.Sessions.Cache.Client.csproj3
17 files changed, 223 insertions, 110 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs
index a159456..d968398 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/Endpoints/AccessTokenEndpoint.cs
@@ -30,12 +30,12 @@ using VNLib.Utils.Memory;
using VNLib.Hashing.IdentityUtility;
using VNLib.Plugins.Essentials.Oauth;
using VNLib.Plugins.Essentials.Endpoints;
+using VNLib.Plugins.Essentials.Oauth.Tokens;
using VNLib.Plugins.Essentials.Oauth.Applications;
using VNLib.Plugins.Essentials.Extensions;
using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Loading.Sql;
using VNLib.Plugins.Extensions.Validation;
-using VNLib.Plugins.Essentials.Oauth.Tokens;
namespace VNLib.Plugins.Essentials.Sessions.OAuth.Endpoints
{
@@ -56,9 +56,9 @@ namespace VNLib.Plugins.Essentials.Sessions.OAuth.Endpoints
//override protection settings to allow most connections to authenticate
protected override ProtectionSettings EndpointProtectionSettings { get; } = new()
{
- BrowsersOnly = false,
- SessionsRequired = false,
- VerifySessionCors = false
+ DisableBrowsersOnly = true,
+ DisableSessionsRequired = true,
+ DisableVerifySessionCors = true
};
public AccessTokenEndpoint(string path, PluginBase pbase, CreateTokenImpl tokenStore, Task<JsonDocument?> verificationKey)
@@ -117,7 +117,7 @@ namespace VNLib.Plugins.Essentials.Sessions.OAuth.Endpoints
secret = secret.ToLower();
//Convert secret to private string that is unreferrenced
- PrivateString secretPv = new(secret, false);
+ using PrivateString secretPv = new(secret, false);
//Get the application from apps store
UserApplication? app = await Applications.VerifyAppAsync(clientId, secretPv);
@@ -171,7 +171,7 @@ namespace VNLib.Plugins.Essentials.Sessions.OAuth.Endpoints
if (result == null)
{
- entity.CloseResponseError(HttpStatusCode.ServiceUnavailable, ErrorType.TemporarilyUnabavailable, "You have reached the maximum number of valid tokens for this application");
+ entity.CloseResponseError(HttpStatusCode.TooManyRequests, ErrorType.TemporarilyUnabavailable, "You have reached the maximum number of valid tokens for this application");
return VfReturnType.VirtualSkip;
}
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/O2SessionProviderEntry.cs b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/O2SessionProviderEntry.cs
index 07b6530..f4462a4 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/O2SessionProviderEntry.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/O2SessionProviderEntry.cs
@@ -27,16 +27,17 @@ using System.Text.Json;
using VNLib.Net.Http;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
+using VNLib.Data.Caching.Extensions;
using VNLib.Plugins.Essentials.Oauth.Tokens;
using VNLib.Plugins.Essentials.Oauth.Applications;
using VNLib.Plugins.Essentials.Sessions.OAuth;
+using VNLib.Plugins.Essentials.Sessions.Runtime;
using VNLib.Plugins.Essentials.Sessions.OAuth.Endpoints;
using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Loading.Routing;
using VNLib.Plugins.Extensions.Loading.Sql;
using VNLib.Plugins.Extensions.Loading.Events;
-using VNLib.Plugins.Essentials.Sessions.Runtime;
-using VNLib.Data.Caching.Extensions;
+
namespace VNLib.Plugins.Essentials.Sessions.Oauth
{
@@ -69,7 +70,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Oauth
//Optional application jwt token
Task<JsonDocument?> jwtTokenSecret = plugin.TryGetSecretAsync("application_token_key")
- .ContinueWith(static t => t.Result == null ? null : JsonDocument.Parse(t.Result), TaskScheduler.Default);
+ .ContinueWith(static t => t.Result == null ? null : t.Result.GetJsonDocument(), TaskScheduler.Default);
//Access token endpoint is optional
if (oauth2Config.TryGetValue("token_path", out JsonElement el))
@@ -107,7 +108,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Oauth
IReadOnlyDictionary<string, JsonElement> oauth2Config)
{
//Init cache client
- using VnCacheClient cache = new(plugin.IsDebug() ? plugin.Log : null, Utils.Memory.Memory.Shared);
+ using VnCacheClient cache = new(plugin.IsDebug() ? localized : null, Utils.Memory.Memory.Shared);
try
{
@@ -125,7 +126,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Oauth
await cache.LoadConfigAsync(plugin, cacheConfig);
//Init session provider now that client is loaded
- _sessions = new(cache.Resource!, cacheLimit, idProv, plugin.GetContextOptions());
+ _sessions = new(cache.Resource!, cacheLimit, 100, idProv, plugin.GetContextOptions());
//Schedule cleanup interval with the plugin scheduler
plugin.ScheduleInterval(_sessions, cleanupInterval);
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/OAuth2SessionProvider.cs b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/OAuth2SessionProvider.cs
index d698c81..106029f 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/OAuth2SessionProvider.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/OAuth2SessionProvider.cs
@@ -55,18 +55,20 @@ namespace VNLib.Plugins.Essentials.Sessions.OAuth
private readonly IOauthSessionIdFactory factory;
private readonly TokenStore TokenStore;
-
- public OAuth2SessionProvider(FBMClient client, int maxCacheItems, IOauthSessionIdFactory idFactory, DbContextOptions dbCtx)
+ private readonly uint MaxConnections;
+
+ public OAuth2SessionProvider(FBMClient client, int maxCacheItems, uint maxConnections, IOauthSessionIdFactory idFactory, DbContextOptions dbCtx)
: base(client, maxCacheItems)
{
factory = idFactory;
TokenStore = new(dbCtx);
+ MaxConnections = maxConnections;
}
///<inheritdoc/>
- protected override RemoteSession SessionCtor(string sessionId) => new OAuth2Session(sessionId, Client, BackgroundTimeout, InvlidatateCache);
+ protected override RemoteSession SessionCtor(string sessionId) => new OAuth2Session(sessionId, Client, BackgroundTimeout, InvalidatateCache);
- private void InvlidatateCache(OAuth2Session session)
+ private void InvalidatateCache(OAuth2Session session)
{
lock (CacheLock)
{
@@ -91,6 +93,14 @@ namespace VNLib.Plugins.Essentials.Sessions.OAuth
return SessionHandle.Empty;
}
+ //Limit max number of waiting clients
+ if (WaitingConnections > MaxConnections)
+ {
+ //Set 503 for temporary unavail
+ entity.CloseResponse(System.Net.HttpStatusCode.ServiceUnavailable);
+ return new SessionHandle(null, FileProcessArgs.VirtualSkip, null);
+ }
+
//Recover the session
RemoteSession session = await base.GetSessionAsync(entity, sessionId, cancellationToken);
@@ -174,14 +184,14 @@ namespace VNLib.Plugins.Essentials.Sessions.OAuth
/*
- * Interval for remving expired tokens
+ * Interval for removing expired tokens
*/
///<inheritdoc/>
async Task IIntervalScheduleable.OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken)
{
//Calculate valid token time
- DateTimeOffset validAfter = DateTimeOffset.UtcNow.Subtract(factory.SessionValidFor);
+ DateTime validAfter = DateTime.UtcNow.Subtract(factory.SessionValidFor);
//Remove tokens from db store
IReadOnlyCollection<ActiveToken> revoked = await TokenStore.CleanupExpiredTokensAsync(validAfter, cancellationToken);
//exception list
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/VNLib.Plugins.Essentials.Sessions.OAuth.csproj b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/VNLib.Plugins.Essentials.Sessions.OAuth.csproj
index d75a1c0..e9927b5 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/VNLib.Plugins.Essentials.Sessions.OAuth.csproj
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.OAuth/VNLib.Plugins.Essentials.Sessions.OAuth.csproj
@@ -31,7 +31,7 @@
<Exec Command="erase &quot;F:\Programming\Web Plugins\DevPlugins\RuntimeAssets\$(TargetName)&quot; /q &gt; nul" />
</Target>
<ItemGroup>
- <ProjectReference Include="..\..\..\..\VNLib\Http\VNLib.Net.Http.csproj" />
+ <ProjectReference Include="..\..\..\..\VNLib\Http\src\VNLib.Net.Http.csproj" />
<ProjectReference Include="..\..\..\..\VNLib\Plugins\src\VNLib.Plugins.csproj" />
<ProjectReference Include="..\..\..\..\VNLib\Utils\src\VNLib.Utils.csproj" />
<ProjectReference Include="..\..\..\DataCaching\VNLib.Data.Caching.Extensions\VNLib.Data.Caching.Extensions.csproj" />
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VNLib.Plugins.Essentials.Sessions.Runtime.csproj b/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VNLib.Plugins.Essentials.Sessions.Runtime.csproj
index d72d6b9..cf6d0c9 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VNLib.Plugins.Essentials.Sessions.Runtime.csproj
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VNLib.Plugins.Essentials.Sessions.Runtime.csproj
@@ -14,8 +14,8 @@
</PropertyGroup>
<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="..\..\..\DataCaching\VNLib.Data.Caching.Extensions\VNLib.Data.Caching.Extensions.csproj" />
<ProjectReference Include="..\..\..\Extensions\VNLib.Plugins.Extensions.Loading\VNLib.Plugins.Extensions.Loading.csproj" />
<ProjectReference Include="..\..\..\PluginBase\VNLib.Plugins.PluginBase.csproj" />
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VnCacheClient.cs b/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VnCacheClient.cs
index 3b348d2..2f7bdf2 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VnCacheClient.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.Runtime/VnCacheClient.cs
@@ -28,13 +28,14 @@ using System.Net.Sockets;
using System.Net.WebSockets;
using System.Security.Cryptography;
-using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
+using VNLib.Utils.Resources;
using VNLib.Utils.Extensions;
using VNLib.Data.Caching.Extensions;
using VNLib.Net.Messaging.FBM.Client;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Hashing.IdentityUtility;
namespace VNLib.Plugins.Essentials.Sessions.Runtime
{
@@ -70,7 +71,8 @@ namespace VNLib.Plugins.Essentials.Sessions.Runtime
ClientHeap = heap;
}
-
+
+ ///<inheritdoc/>
protected override void Free()
{
_client?.Dispose();
@@ -90,14 +92,20 @@ namespace VNLib.Plugins.Essentials.Sessions.Runtime
string? brokerAddress = config["broker_address"].GetString() ?? throw new KeyNotFoundException("Missing required configuration variable broker_address");
//Get keys async
- Task<string?> clientPrivTask = pbase.TryGetSecretAsync("client_private_key");
- Task<string?> brokerPubTask = pbase.TryGetSecretAsync("broker_public_key");
+ Task<SecretResult?> clientPrivTask = pbase.TryGetSecretAsync("client_private_key");
+ Task<SecretResult?> brokerPubTask = pbase.TryGetSecretAsync("broker_public_key");
+ Task<SecretResult?> cachePubTask = pbase.TryGetSecretAsync("cache_public_key");
//Wait for all tasks to complete
- string?[] keys = await Task.WhenAll(clientPrivTask, brokerPubTask);
+ _ = await Task.WhenAll(clientPrivTask, brokerPubTask, cachePubTask);
+
+ using SecretResult clientPriv = await clientPrivTask ?? throw new KeyNotFoundException("Missing required secret client_private_key");
+ using SecretResult brokerPub = await brokerPubTask ?? throw new KeyNotFoundException("Missing required secret broker_public_key");
+ using SecretResult cachePub = await cachePubTask ?? throw new KeyNotFoundException("Missing required secret cache_public_key");
- byte[] privKey = Convert.FromBase64String(keys[0] ?? throw new KeyNotFoundException("Missing required secret client_private_key"));
- byte[] brokerPub = Convert.FromBase64String(keys[1] ?? throw new KeyNotFoundException("Missing required secret broker_public_key"));
+ ReadOnlyJsonWebKey clientCert = clientPriv.GetJsonWebKey();
+ ReadOnlyJsonWebKey brokerPubKey = brokerPub.GetJsonWebKey();
+ ReadOnlyJsonWebKey cachePubKey = cachePub.GetJsonWebKey();
RetryInterval = config["retry_interval_sec"].GetTimeSpan(TimeParseType.Seconds);
@@ -107,16 +115,14 @@ namespace VNLib.Plugins.Essentials.Sessions.Runtime
FBMClientConfig conf = FBMDataCacheExtensions.GetDefaultConfig(ClientHeap ?? Memory.Shared, maxMessageSize, DebugLog);
_client = new(conf);
- //Add the configuration
+
+ //Add the configuration to the client
_client.GetCacheConfiguration()
.WithBroker(brokerUri)
- .ImportVerificationKey(brokerPub)
- .ImportSigningKey(privKey)
+ .WithVerificationKey(cachePubKey)
+ .WithSigningCertificate(clientCert)
+ .WithBrokerVerificationKey(brokerPubKey)
.WithTls(brokerUri.Scheme == Uri.UriSchemeHttps);
-
- //Zero the key memory
- Memory.InitializeBlock(privKey.AsSpan());
- Memory.InitializeBlock(brokerPub.AsSpan());
}
/// <summary>
@@ -157,6 +163,7 @@ namespace VNLib.Plugins.Essentials.Sessions.Runtime
int randomMsDelay = RandomNumberGenerator.GetInt32(1000, 2000);
await Task.Delay(randomMsDelay, cancellationToken);
}
+
if (servers?.Length == 0)
{
Log.Warn("No cluster nodes found, retrying");
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/VNLib.Plugins.Essentials.Sessions.VNCache.csproj b/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/VNLib.Plugins.Essentials.Sessions.VNCache.csproj
index 8c3e23a..dc73ed8 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/VNLib.Plugins.Essentials.Sessions.VNCache.csproj
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/VNLib.Plugins.Essentials.Sessions.VNCache.csproj
@@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\..\..\VNLib\Http\VNLib.Net.Http.csproj" />
+ <ProjectReference Include="..\..\..\..\VNLib\Http\src\VNLib.Net.Http.csproj" />
<ProjectReference Include="..\..\..\..\VNLib\Utils\src\VNLib.Utils.csproj" />
<ProjectReference Include="..\..\..\DataCaching\VNLib.Data.Caching.Extensions\VNLib.Data.Caching.Extensions.csproj" />
<ProjectReference Include="..\..\..\DataCaching\VNLib.Data.Caching\src\VNLib.Data.Caching.csproj" />
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProvider.cs b/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProvider.cs
index 057e308..5747175 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProvider.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProvider.cs
@@ -71,9 +71,6 @@ namespace VNLib.Plugins.Essentials.Sessions.VNCache
protected override RemoteSession SessionCtor(string sessionId) => new WebSession(sessionId, Client, BackgroundUpdateTimeout, UpdateSessionId);
-
- private uint _waitingCount;
-
public async ValueTask<SessionHandle> GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken)
{
//Callback to close the session when the handle is closeed
@@ -92,27 +89,15 @@ namespace VNLib.Plugins.Essentials.Sessions.VNCache
}
//Limit max number of waiting clients
- if (_waitingCount > MaxConnections)
+ if (WaitingConnections > MaxConnections)
{
//Set 503 for temporary unavail
entity.CloseResponse(System.Net.HttpStatusCode.ServiceUnavailable);
return new SessionHandle(null, FileProcessArgs.VirtualSkip, null);
}
- RemoteSession session;
-
- //Inc waiting count
- Interlocked.Increment(ref _waitingCount);
- try
- {
- //Recover the session
- session = await GetSessionAsync(entity, sessionId, cancellationToken);
- }
- finally
- {
- //Dec on exit
- Interlocked.Decrement(ref _waitingCount);
- }
+ //Get session
+ RemoteSession session = await GetSessionAsync(entity, sessionId, cancellationToken);
//If the session is new (not in cache), then overwrite the session id with a new one as user may have specified their own
if (session.IsNew)
@@ -131,6 +116,7 @@ namespace VNLib.Plugins.Essentials.Sessions.VNCache
session.Privilages = 0;
session.SetLoginToken(null);
}
+
return new SessionHandle(session, HandleClosedAsync);
}
catch (OperationCanceledException)
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProviderEntry.cs b/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProviderEntry.cs
index 382717b..8e25416 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProviderEntry.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions.VNCache/WebSessionProviderEntry.cs
@@ -84,7 +84,7 @@ namespace VNLib.Plugins.Essentials.Sessions.VNCache
IReadOnlyDictionary<string, JsonElement> webSessionConfig)
{
//Init cache client
- using VnCacheClient cache = new(plugin.IsDebug() ? plugin.Log : null, Memory.Shared);
+ using VnCacheClient cache = new(plugin.IsDebug() ? localized : null, Memory.Shared);
try
{
@@ -97,9 +97,11 @@ namespace VNLib.Plugins.Essentials.Sessions.VNCache
//Init provider
_sessions = new(cache.Resource!, cacheLimit, maxConnections, idFactory);
-
localized.Information("Session provider loaded");
+ //Listen for cache table events
+ _ = plugin.DeferTask(() => _sessions.CleanupExpiredSessionsAsync(localized, plugin.UnloadToken));
+
//Run and wait for exit
await cache.RunAsync(localized, plugin.UnloadToken);
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" />
diff --git a/Libs/VNLib.Plugins.Sessions.Cache.Client/Exceptions/SessionStatusException.cs b/Libs/VNLib.Plugins.Sessions.Cache.Client/Exceptions/SessionStatusException.cs
index b857638..bc6b217 100644
--- a/Libs/VNLib.Plugins.Sessions.Cache.Client/Exceptions/SessionStatusException.cs
+++ b/Libs/VNLib.Plugins.Sessions.Cache.Client/Exceptions/SessionStatusException.cs
@@ -29,6 +29,9 @@ 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()
diff --git a/Libs/VNLib.Plugins.Sessions.Cache.Client/RemoteSession.cs b/Libs/VNLib.Plugins.Sessions.Cache.Client/RemoteSession.cs
index d2d4200..3b61f68 100644
--- a/Libs/VNLib.Plugins.Sessions.Cache.Client/RemoteSession.cs
+++ b/Libs/VNLib.Plugins.Sessions.Cache.Client/RemoteSession.cs
@@ -37,8 +37,6 @@ using VNLib.Net.Messaging.FBM.Client;
using VNLib.Plugins.Essentials.Sessions;
using VNLib.Plugins.Essentials.Extensions;
-#nullable enable
-
namespace VNLib.Plugins.Sessions.Cache.Client
{
/// <summary>
@@ -48,9 +46,9 @@ namespace VNLib.Plugins.Sessions.Cache.Client
public abstract class RemoteSession : SessionBase
{
protected const string CREATED_TIME_ENTRY = "__.i.ctime";
-
- protected readonly FBMClient Client;
- protected readonly TimeSpan UpdateTimeout;
+
+ protected FBMClient Client { get; }
+ protected TimeSpan UpdateTimeout { get; }
private readonly AsyncLazyInitializer Initializer;
@@ -119,6 +117,8 @@ namespace VNLib.Plugins.Sessions.Cache.Client
protected set => this.SetValueType(CREATED_TIME_ENTRY, value.ToUnixTimeMilliseconds());
}
+
+
///<inheritdoc/>
protected override string IndexerGet(string key)
{
@@ -186,12 +186,5 @@ namespace VNLib.Plugins.Sessions.Cache.Client
throw;
}
}
- ///<inheritdoc/>
- protected override Task OnEvictedAsync()
- {
- //empty the dict to help the GC
- DataStore!.Clear();
- return Task.CompletedTask;
- }
}
}
diff --git a/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs b/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
index 8eed404..2e72391 100644
--- a/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
+++ b/Libs/VNLib.Plugins.Sessions.Cache.Client/SessionCacheClient.cs
@@ -29,37 +29,64 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using VNLib.Net.Http;
-using VNLib.Utils;
+using VNLib.Utils.Async;
+using VNLib.Utils.Logging;
using VNLib.Utils.Memory.Caching;
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 abstract class SessionCacheClient : ICacheHolder
{
- public class LRUSessionStore<T> : LRUCache<string, T> where T : ISession, ICacheable
+ 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;
-
+
+ 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)
{
- //Evice record
- evicted.Value.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();
}
}
@@ -67,6 +94,9 @@ namespace VNLib.Plugins.Sessions.Cache.Client
protected readonly object CacheLock;
protected readonly int MaxLoadedEntires;
+ /// <summary>
+ /// The client used to communicate with the cache server
+ /// </summary>
protected FBMClient Client { get; }
/// <summary>
@@ -80,11 +110,14 @@ namespace VNLib.Plugins.Sessions.Cache.Client
CacheLock = new();
CacheTable = new(maxCacheItems);
Client = client;
- //Listen for close events
- Client.ConnectionClosed += Client_ConnectionClosed;
}
- private void Client_ConnectionClosed(object? sender, EventArgs e) => CacheHardClear();
+ 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
@@ -96,7 +129,6 @@ namespace VNLib.Plugins.Sessions.Cache.Client
/// <exception cref="SessionException"></exception>
public virtual async ValueTask<RemoteSession> GetSessionAsync(IHttpEvent entity, string sessionId, CancellationToken cancellationToken)
{
- Check();
try
{
RemoteSession? session;
@@ -113,6 +145,10 @@ namespace VNLib.Plugins.Sessions.Cache.Client
}
//Valid entry found in cache
}
+
+ //Inc waiting count
+ Interlocked.Increment(ref _waitingCount);
+
try
{
//Load session-data
@@ -128,6 +164,11 @@ namespace VNLib.Plugins.Sessions.Cache.Client
}
throw;
}
+ finally
+ {
+ //Dec waiting count
+ Interlocked.Decrement(ref _waitingCount);
+ }
}
catch (SessionException)
{
@@ -151,6 +192,47 @@ namespace VNLib.Plugins.Sessions.Cache.Client
/// <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)
+ {
+ //Close handler
+ void OnConnectionClosed(object? sender, EventArgs e) => CacheHardClear();
+
+ //Attach event
+ Client.ConnectionClosed += OnConnectionClosed;
+
+ 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);
+ }
+ }
+
+ //remove handler
+ Client.ConnectionClosed -= OnConnectionClosed;
+ }
///<inheritdoc/>
public void CacheClear()
@@ -163,21 +245,8 @@ namespace VNLib.Plugins.Sessions.Cache.Client
//Cleanup cache when disconnected
lock (CacheLock)
{
- CacheTable.Clear();
- foreach (RemoteSession session in (IEnumerable<RemoteSession>)CacheTable)
- {
- session.Evicted();
- }
- CacheTable.Clear();
+ CacheTable.CacheHardClear();
}
}
-
- protected override void Free()
- {
- //Unsub from events
- Client.ConnectionClosed -= Client_ConnectionClosed;
- //Clear all cached sessions
- CacheHardClear();
- }
}
}
diff --git a/Libs/VNLib.Plugins.Sessions.Cache.Client/VNLib.Plugins.Sessions.Cache.Client.csproj b/Libs/VNLib.Plugins.Sessions.Cache.Client/VNLib.Plugins.Sessions.Cache.Client.csproj
index fc99bbf..a75ebe3 100644
--- a/Libs/VNLib.Plugins.Sessions.Cache.Client/VNLib.Plugins.Sessions.Cache.Client.csproj
+++ b/Libs/VNLib.Plugins.Sessions.Cache.Client/VNLib.Plugins.Sessions.Cache.Client.csproj
@@ -12,6 +12,7 @@
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
<AnalysisLevel>latest-all</AnalysisLevel>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -36,7 +37,7 @@
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\..\..\VNLib\Essentials\VNLib.Plugins.Essentials.csproj" />
+ <ProjectReference Include="..\..\..\..\VNLib\Essentials\src\VNLib.Plugins.Essentials.csproj" />
<ProjectReference Include="..\..\..\DataCaching\VNLib.Data.Caching\src\VNLib.Data.Caching.csproj" />
</ItemGroup>