Session Store & Service
On this page
Namespaces: Nalix.Abstractions.Networking.Sessions, Nalix.Runtime.Sessions
Assembly: Nalix.Runtime
SessionService, InMemorySessionStore, SessionFactory, DefaultSessionPersistencePolicy, and SessionPersistenceObserver manage resumable session snapshots (SessionEntry / SessionScope) when clients disconnect and later reconnect via the 0-RTT SessionResume protocol (ControlType.RESUME_REQUEST).
Source Mapping
src/Nalix.Abstractions/Networking/Sessions/ISessionService.cssrc/Nalix.Abstractions/Networking/Sessions/ISessionStore.cssrc/Nalix.Abstractions/Networking/Sessions/ISessionFactory.cssrc/Nalix.Abstractions/Networking/Sessions/ISessionPersistencePolicy.cssrc/Nalix.Abstractions/Networking/Sessions/SessionEntry.cssrc/Nalix.Abstractions/Networking/Sessions/SessionScope.cssrc/Nalix.Runtime/Sessions/SessionService.cssrc/Nalix.Runtime/Sessions/InMemorySessionStore.cssrc/Nalix.Runtime/Sessions/SessionFactory.cssrc/Nalix.Runtime/Sessions/DefaultSessionPersistencePolicy.cssrc/Nalix.Runtime/Sessions/SessionPersistenceObserver.cssrc/Nalix.Runtime/Options/SessionStoreOptions.cs
Session Persistence & Resume Lifecycle
sequenceDiagram
participant C as Client
participant H as ConnectionHub
participant OBS as SessionPersistenceObserver
participant SSV as SessionService
participant ST as ISessionStore (InMemorySessionStore)
Note over C,H: 1. Client Disconnects
H->>H: UnregisterConnection(connection)
H-->>OBS: Raise ConnectionUnregistered(connection)
OBS->>OBS: Create SnapshotConnection (clones Attributes, Secret, Sequence Numbers)
OBS->>SSV: SaveSessionAsync(snapshot)
SSV->>SSV: ISessionPersistencePolicy.ShouldPersist(snapshot)
SSV->>ST: StoreAsync(SessionEntry)
Note over C,H: 2. Client Reconnects (0-RTT Resume)
C->>H: SessionResume (SessionToken, Nonce, Proof)
H->>SSV: ConsumeAsync(sessionToken, predicate)
SSV->>ST: ConsumeAsync(sessionToken, predicate)
ST-->>SSV: Atomic Remove -> Return SessionScope(entry)Public Contracts & Implementations
1. ISessionService & SessionService
namespace Nalix.Runtime.Sessions;
public sealed class SessionService : ISessionService, IDisposable, IReportable
{
public SessionService(
ISessionFactory? factory = null,
ISessionStore? store = null,
ISessionPersistencePolicy? policy = null);
public ValueTask SaveSessionAsync(
IConnection connection,
CancellationToken cancellationToken = default);
public ValueTask<SessionScope> ConsumeAsync(
ulong sessionToken,
Func<SessionEntry, bool>? predicate = null,
CancellationToken cancellationToken = default);
public string GenerateReport();
public void WriteReportData(Utf8JsonWriter writer);
public void Dispose();
}
- Scavenger Scheduling: When
_storeimplementsIWorker(asInMemorySessionStoredoes),SessionServiceautomatically schedules it onTaskManager.ScheduleWorker(hostedWorker)and disposes theIWorkerHandlewhenSessionService.Dispose()is called. - Pooled Safety: If
_store.StoreAsync(entry, ct)throws a non-fatal exception,SessionServiceimmediately callsentry.Return()so the pooledSessionEntryand its rentedObjectMap<AttributeKey, object>are never leaked.
2. InMemorySessionStore (ISessionStore, IWorker, IReportable)
namespace Nalix.Runtime.Sessions;
public sealed class InMemorySessionStore : ISessionStore, IWorker, IReportable
{
public const int DefaultMaxSessions = 65_536;
public int MaxSessions { get; set; }
public ValueTask StoreAsync(
SessionEntry entry,
CancellationToken cancellationToken = default);
public ValueTask<SessionScope> ConsumeAsync(
ulong sessionToken,
Func<SessionEntry, bool>? predicate = null,
CancellationToken cancellationToken = default);
public ValueTask ExecuteAsync(IWorkerContext context, CancellationToken cancellationToken);
}
- Atomic One-Shot Consumption (
SEC-33):ConsumeAsyncverifies thatentry.Snapshot.ExpiresAtUnixMilliseconds > Clock.UnixMillisecondsNow(), evaluates the optionalpredicate(entry)BEFORE removing the entry ifpredicatereturnsfalse, and uses atomic key-value removal (ICollection<KeyValuePair<ulong, SessionEntry>>.Remove) so only one concurrent caller can consume a valid token. Returns aSessionScope(scope.IsValid == truewhen consumed; disposingSessionScopereturns theSessionEntryto its pool). - Minute-Bucketed Expiration Index (
_expiryBuckets): Groups session tokens into 60-second buckets (ExpiresAtUnixMilliseconds / 60_000) so the backgroundExecuteAsyncscavenger only visits buckets that are due. - Capacity Eviction (
DefaultMaxSessions = 65,536): When_store.Count >= MaxSessions, evicts expired entries first, then oldest entries, callingentry.Return().
3. DefaultSessionPersistencePolicy & SessionPersistenceObserver
SessionPersistenceObserversubscribes toIConnectionHub.ConnectionUnregistered, captures aSnapshotConnection(cloningAttributes,Secret,Level,Algorithm, and TCP/UDP sequence counters beforeConnection.Dispose()zeroes them), and invokesSaveSessionAsyncin the background.DefaultSessionPersistencePolicy.ShouldPersist(IConnection)evaluates three rules in order:- Policy 1 (Handshake Gate): If
!connection.GetRuntimeState().HandshakeEstablished, returnsfalse. - Policy 2 (Active Cryptographic Session): If
!connection.Secret.IsZero(active 32-byte encryption key established), returnstrueimmediately. - Policy 3 (Attribute Count Fallback): Otherwise returns
connection.Attributes.Count > _options.MinAttributesForPersistence(default10).
- Policy 1 (Handshake Gate): If