feat(sessions): multi-subscriber cap enforcement + mode-gated FailFast
This commit is contained in:
@@ -419,7 +419,8 @@ public sealed class GatewaySession
|
||||
eventOptions.ReplayRetentionSeconds,
|
||||
_eventStreaming.DistributorLogger,
|
||||
_eventStreaming.TimeProvider,
|
||||
CreateOverflowHandler(eventOptions.BackpressurePolicy));
|
||||
CreateOverflowHandler(eventOptions.BackpressurePolicy),
|
||||
singleSubscriberMode: !_eventStreaming.AllowMultipleEventSubscribers);
|
||||
}
|
||||
|
||||
startNow = false;
|
||||
@@ -680,14 +681,36 @@ public sealed class GatewaySession
|
||||
/// <summary>
|
||||
/// Attaches an event subscriber and returns a lease whose
|
||||
/// <see cref="IEventSubscriberLease.Reader"/> reads the fanned public
|
||||
/// <see cref="MxEvent"/>s for this subscriber. The single-subscriber guard
|
||||
/// (Tasks 7/8 relax it) is unchanged: with multi-subscriber disabled a second
|
||||
/// attach is rejected. The returned lease, when disposed, unregisters the
|
||||
/// distributor subscriber AND decrements the active-subscriber count.
|
||||
/// <see cref="MxEvent"/>s for this subscriber. The returned lease, when disposed,
|
||||
/// unregisters the distributor subscriber AND decrements the active-subscriber count.
|
||||
/// </summary>
|
||||
/// <param name="allowMultipleSubscribers">If true, allows multiple concurrent event subscribers.</param>
|
||||
public IEventSubscriberLease AttachEventSubscriber(bool allowMultipleSubscribers)
|
||||
/// <param name="allowMultipleSubscribers">
|
||||
/// When <see langword="false"/>, single-subscriber mode: a second concurrent EXTERNAL
|
||||
/// subscriber is rejected with <see cref="SessionManagerErrorCode.EventSubscriberAlreadyActive"/>.
|
||||
/// When <see langword="true"/>, multi-subscriber mode: up to
|
||||
/// <paramref name="maxSubscribers"/> concurrent EXTERNAL subscribers are allowed; the
|
||||
/// next attach is rejected with
|
||||
/// <see cref="SessionManagerErrorCode.EventSubscriberLimitReached"/>.
|
||||
/// </param>
|
||||
/// <param name="maxSubscribers">
|
||||
/// Maximum concurrent external subscribers in multi-subscriber mode
|
||||
/// (<c>MxGateway:Sessions:MaxEventSubscribersPerSession</c>). Ignored when
|
||||
/// <paramref name="allowMultipleSubscribers"/> is <see langword="false"/> (the effective
|
||||
/// cap is then 1). The gateway-owned internal dashboard subscriber is registered
|
||||
/// directly on the distributor and is NOT counted here, so it never consumes cap budget.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// The count-check-and-increment runs atomically under <c>_syncRoot</c>, so two
|
||||
/// concurrent attaches racing toward the cap can never both succeed past it. On
|
||||
/// distributor-register failure the count is rolled back (see the catch below).
|
||||
/// </remarks>
|
||||
public IEventSubscriberLease AttachEventSubscriber(bool allowMultipleSubscribers, int maxSubscribers)
|
||||
{
|
||||
// Effective cap: 1 in single-subscriber mode, otherwise the configured maximum
|
||||
// (clamped to at least 1 so a misconfigured non-positive value can never deadlock
|
||||
// attaches in multi-subscriber mode).
|
||||
int effectiveCap = allowMultipleSubscribers ? Math.Max(1, maxSubscribers) : 1;
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_state != SessionState.Ready || _workerClient?.State != WorkerClientState.Ready)
|
||||
@@ -697,11 +720,15 @@ public sealed class GatewaySession
|
||||
$"Session {SessionId} is not ready for event streaming. Current state is {_state}.");
|
||||
}
|
||||
|
||||
if (!allowMultipleSubscribers && _activeEventSubscriberCount > 0)
|
||||
if (_activeEventSubscriberCount >= effectiveCap)
|
||||
{
|
||||
throw new SessionManagerException(
|
||||
SessionManagerErrorCode.EventSubscriberAlreadyActive,
|
||||
$"Session {SessionId} already has an active event stream subscriber.");
|
||||
throw allowMultipleSubscribers
|
||||
? new SessionManagerException(
|
||||
SessionManagerErrorCode.EventSubscriberLimitReached,
|
||||
$"Session {SessionId} has reached its maximum of {effectiveCap} concurrent event stream subscribers.")
|
||||
: new SessionManagerException(
|
||||
SessionManagerErrorCode.EventSubscriberAlreadyActive,
|
||||
$"Session {SessionId} already has an active event stream subscriber.");
|
||||
}
|
||||
|
||||
_activeEventSubscriberCount++;
|
||||
|
||||
Reference in New Issue
Block a user