fix(dashboard): generation-scoped idle gate in UnsubscribeAsync — no zero-subscriber pump survives
This commit is contained in:
@@ -14,8 +14,9 @@ namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The pump is idle-gated: it starts when the first subscriber arrives and is cancelled and
|
||||
/// awaited when the last one leaves, so an unwatched gateway runs no timer and builds no
|
||||
/// snapshots. Successive pumps are chained through <c>_pumpTask</c>, so a rapid
|
||||
/// awaited when the last subscriber <em>of the live generation</em> leaves, so an unwatched
|
||||
/// gateway runs no timer and builds no snapshots. Successive pumps are chained through
|
||||
/// <c>_pumpTask</c>, so a rapid
|
||||
/// unsubscribe/resubscribe restarts a fresh pump without ever running two enumerations at
|
||||
/// once.
|
||||
/// </para>
|
||||
@@ -116,10 +117,25 @@ public sealed class DashboardSnapshotFeed : IDashboardSnapshotFeed
|
||||
Task pump;
|
||||
lock (_gate)
|
||||
{
|
||||
if (!_subscribers.Remove(subscription) || _subscribers.Count != 0)
|
||||
if (!_subscribers.Remove(subscription))
|
||||
{
|
||||
// Either the pump already detached this subscription (it completed or
|
||||
// faulted), or other viewers are still watching.
|
||||
// The pump already detached this subscription (it completed or faulted).
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscription.Generation != _generation)
|
||||
{
|
||||
// This viewer belonged to a generation that has already ended. The live
|
||||
// pump — if there is one — serves other viewers and must not be cancelled
|
||||
// on their behalf; the dying pump is stopping under its own steam.
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasSubscribersLocked(_generation))
|
||||
{
|
||||
// Other viewers are still watching the live generation. Counting the whole
|
||||
// list here would be wrong: subscribers of an ending generation linger in it
|
||||
// until that pump's Reset runs, and they must not hold the idle gate open.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,6 +165,22 @@ public sealed class DashboardSnapshotFeed : IDashboardSnapshotFeed
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reports whether any subscriber is still being served by a generation.</summary>
|
||||
/// <param name="generation">The generation to look for.</param>
|
||||
/// <returns>True when at least one subscriber carries that generation.</returns>
|
||||
private bool HasSubscribersLocked(long generation)
|
||||
{
|
||||
foreach (Subscription subscriber in _subscribers)
|
||||
{
|
||||
if (subscriber.Generation == generation)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a pump generation. Must be called while holding <c>_gate</c>; the caller adds
|
||||
/// the subscribers that belong to the returned generation.
|
||||
|
||||
Reference in New Issue
Block a user