|
|
|
@@ -39,7 +39,7 @@ public sealed class GatewaySession
|
|
|
|
|
private bool _everHadEventSubscriber;
|
|
|
|
|
private SessionEventDistributor? _eventDistributor;
|
|
|
|
|
private bool _eventDistributorStarted;
|
|
|
|
|
// GWC-34: set under _syncRoot when DisposeAsync nulls _eventDistributor, making the
|
|
|
|
|
// Set under _syncRoot when DisposeAsync nulls _eventDistributor, making the
|
|
|
|
|
// "one distributor per session, never reset" lifecycle symmetric at both ends. Without it
|
|
|
|
|
// EnsureDistributorCreated reads the null as "not created yet" and builds a REPLACEMENT
|
|
|
|
|
// distributor after teardown — one whose pump can never start (_eventDistributorStarted is a
|
|
|
|
@@ -163,7 +163,7 @@ public sealed class GatewaySession
|
|
|
|
|
/// is unit-testable.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <param name="ownerDashboardTags">
|
|
|
|
|
/// Dashboard event-visibility tags inherited from the owning API key (SEC-25). Copied into
|
|
|
|
|
/// Dashboard event-visibility tags inherited from the owning API key. Copied into
|
|
|
|
|
/// the immutable <see cref="Tags"/> set; <see langword="null"/> or empty means untagged.
|
|
|
|
|
/// The tags come from the owner key, never from the client's wire request, so a client
|
|
|
|
|
/// cannot label its own session with another tenant's tag.
|
|
|
|
@@ -265,8 +265,8 @@ public sealed class GatewaySession
|
|
|
|
|
public string? OwnerKeyId { get; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the dashboard event-visibility tags this session inherited from its owning API key
|
|
|
|
|
/// (SEC-25). An empty set means untagged.
|
|
|
|
|
/// Gets the dashboard event-visibility tags this session inherited from its owning API key.
|
|
|
|
|
/// An empty set means untagged.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Immutable for the session's life — assigned once at construction from the owner key's
|
|
|
|
@@ -541,7 +541,7 @@ public sealed class GatewaySession
|
|
|
|
|
// concurrent callers (e.g. MarkReady's dashboard mirror and a racing first
|
|
|
|
|
// AttachEventSubscriber) agree on a single distributor and a single start.
|
|
|
|
|
//
|
|
|
|
|
// GWC-34: after DisposeAsync the null distributor means "gone", not "not built yet", so this
|
|
|
|
|
// After DisposeAsync the null distributor means "gone", not "not built yet", so this
|
|
|
|
|
// throws instead of constructing a replacement nothing will ever pump. The attach paths already
|
|
|
|
|
// roll back and surface the exception, which is the same SessionNotReady the readiness gates
|
|
|
|
|
// raise — a caller that loses the race to teardown now fails immediately instead of hanging.
|
|
|
|
@@ -610,7 +610,7 @@ public sealed class GatewaySession
|
|
|
|
|
/// </exception>
|
|
|
|
|
public IEventSubscriberLease AttachInternalEventSubscriber()
|
|
|
|
|
{
|
|
|
|
|
// Readiness gate, mirroring AttachEventSubscriber (GWC-27). It must run BEFORE
|
|
|
|
|
// Readiness gate, mirroring AttachEventSubscriber. It must run BEFORE
|
|
|
|
|
// EnsureDistributorCreated: a premature attach would construct the distributor and start
|
|
|
|
|
// its pump against a not-yet-Ready worker, the pump source would throw SessionNotReady,
|
|
|
|
|
// PumpAsync would complete every subscriber with that error and latch _completed, and
|
|
|
|
@@ -694,7 +694,7 @@ public sealed class GatewaySession
|
|
|
|
|
// These three calls are OUTSIDE _syncRoot to avoid holding it across
|
|
|
|
|
// EnsureDistributorCreated's own lock and StartAsync's Task.Run.
|
|
|
|
|
//
|
|
|
|
|
// GWC-34: the state check above can be overtaken by a close+dispose before we get here, and
|
|
|
|
|
// The state check above can be overtaken by a close+dispose before we get here, and
|
|
|
|
|
// EnsureDistributorCreated now says so instead of building an orphan distributor. The
|
|
|
|
|
// dashboard mirror is a best-effort observability attachment on a session that is already
|
|
|
|
|
// gone, so it is abandoned here rather than propagated out of MarkReady — the mirror-started
|
|
|
|
@@ -1918,7 +1918,7 @@ public sealed class GatewaySession
|
|
|
|
|
{
|
|
|
|
|
distributor = _eventDistributor;
|
|
|
|
|
_eventDistributor = null;
|
|
|
|
|
// GWC-34: latch the null-out so EnsureDistributorCreated cannot read it as
|
|
|
|
|
// Latch the null-out so EnsureDistributorCreated cannot read it as
|
|
|
|
|
// "not created yet" and hand a racing attach a distributor that will never pump.
|
|
|
|
|
_eventDistributorDisposed = true;
|
|
|
|
|
}
|
|
|
|
|