fix(sessions): expose DetachGraceSeconds in effective-config; single clock; close reconnect-vs-sweep race
- EffectiveSessionConfiguration: add DetachGraceSeconds field; GatewayConfigurationProvider forwards value.Sessions.DetachGraceSeconds (blocker fix). - GatewaySession.InvokeAsync and ReadEventsAsync: switch TouchClientActivity calls from DateTimeOffset.UtcNow to _eventStreaming.TimeProvider.GetUtcNow() so Task 12 fake-clock control works end-to-end (split-clock fix). - TOCTOU fix: add TryBeginCloseIfExpired(now, out alreadyClosing) to GatewaySession that re-checks IsLeaseExpiredCore/IsDetachGraceExpiredCore AND _activeEventSubscriberCount==0 under _syncRoot before transitioning to Closing; CloseExpiredLeasesAsync calls it before CloseSessionCoreAsync so a reattach that wins the race leaves the session Ready/usable. - Minors: lease-expiry-takes-precedence comment in CloseExpiredLeasesAsync; TOCTOU comment block; sweep-cycle latency note added to SessionOptions.DetachGraceSeconds XML doc and to GatewayConfiguration.md DetachGraceSeconds row. - New tests: TryBeginCloseIfExpired_ReattachedSubscriberWinsRace_DeclinesClose (GatewaySession), CloseExpiredLeasesAsync_DoesNotCloseSessionThatReattachedBeforeSweepCloses (SessionManager), plus IsLeaseExpiredCore/IsDetachGraceExpiredCore private helpers used by the guard.
This commit is contained in:
@@ -6,5 +6,6 @@ public sealed record EffectiveSessionConfiguration(
|
||||
int MaxPendingCommandsPerSession,
|
||||
int DefaultLeaseSeconds,
|
||||
int LeaseSweepIntervalSeconds,
|
||||
int DetachGraceSeconds,
|
||||
bool AllowMultipleEventSubscribers,
|
||||
int MaxEventSubscribersPerSession);
|
||||
|
||||
@@ -46,6 +46,7 @@ public sealed class GatewayConfigurationProvider(IOptions<GatewayOptions> option
|
||||
MaxPendingCommandsPerSession: value.Sessions.MaxPendingCommandsPerSession,
|
||||
DefaultLeaseSeconds: value.Sessions.DefaultLeaseSeconds,
|
||||
LeaseSweepIntervalSeconds: value.Sessions.LeaseSweepIntervalSeconds,
|
||||
DetachGraceSeconds: value.Sessions.DetachGraceSeconds,
|
||||
AllowMultipleEventSubscribers: value.Sessions.AllowMultipleEventSubscribers,
|
||||
MaxEventSubscribersPerSession: value.Sessions.MaxEventSubscribersPerSession),
|
||||
Events: new EffectiveEventConfiguration(
|
||||
|
||||
@@ -36,6 +36,13 @@ public sealed class SessionOptions
|
||||
/// expires. The reconnect/replay itself is implemented separately (Task 12); this
|
||||
/// option controls retention and expiry only.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The effective close happens within the next sweep cycle after the window elapses —
|
||||
/// up to <see cref="LeaseSweepIntervalSeconds"/> after expiry. Operators who want a
|
||||
/// firm minimum bound should set <c>DetachGraceSeconds</c> greater than
|
||||
/// <see cref="LeaseSweepIntervalSeconds"/>; otherwise a session whose window expires
|
||||
/// just before a sweep run may be closed within seconds of detach.
|
||||
/// </remarks>
|
||||
public int DetachGraceSeconds { get; init; } = 30;
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user