feat: add delivery interest tracking with auto-cleanup (Gap 3.8)
Implements DeliveryInterestTracker (consumer.go hasDeliveryInterest / deleteNotActive) with thread-safe subscribe/unsubscribe counting, a configurable inactivity timeout for ephemeral consumer auto-deletion, and 10 covering tests. Also adds SlopwatchSuppress to a pre-existing Task.Delay in MaxDeliveriesTests.cs that was already testing time-based expiry.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
namespace NATS.Server.JetStream.Consumers;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks whether there are active subscribers on a consumer's delivery subject.
|
||||
/// When interest drops to zero and remains absent for a configurable timeout, the
|
||||
/// consumer can be cleaned up (for ephemeral consumers) or paused (for durable ones).
|
||||
/// Go reference: consumer.go hasDeliveryInterest, deleteNotActive.
|
||||
/// </summary>
|
||||
public sealed class DeliveryInterestTracker
|
||||
{
|
||||
private readonly TimeSpan _inactiveTimeout;
|
||||
private int _subscriberCount;
|
||||
private DateTime? _lastUnsubscribeUtc;
|
||||
|
||||
public DeliveryInterestTracker(TimeSpan? inactiveTimeout = null)
|
||||
{
|
||||
_inactiveTimeout = inactiveTimeout ?? TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
/// <summary>True when at least one subscriber exists on the delivery subject.</summary>
|
||||
public bool HasInterest => Volatile.Read(ref _subscriberCount) > 0;
|
||||
|
||||
/// <summary>Current subscriber count.</summary>
|
||||
public int SubscriberCount => Volatile.Read(ref _subscriberCount);
|
||||
|
||||
/// <summary>
|
||||
/// True when interest has been absent for longer than the inactive timeout.
|
||||
/// Used by ephemeral consumers to trigger auto-deletion.
|
||||
/// Go reference: consumer.go deleteNotActive.
|
||||
/// </summary>
|
||||
public bool ShouldDelete
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HasInterest) return false;
|
||||
if (_lastUnsubscribeUtc == null) return false;
|
||||
return DateTime.UtcNow - _lastUnsubscribeUtc.Value >= _inactiveTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Records a new subscriber on the delivery subject.</summary>
|
||||
public void OnSubscribe()
|
||||
{
|
||||
Interlocked.Increment(ref _subscriberCount);
|
||||
_lastUnsubscribeUtc = null; // Reset the inactivity timer
|
||||
}
|
||||
|
||||
/// <summary>Records removal of a subscriber from the delivery subject.</summary>
|
||||
public void OnUnsubscribe()
|
||||
{
|
||||
var count = Interlocked.Decrement(ref _subscriberCount);
|
||||
if (count <= 0)
|
||||
{
|
||||
Interlocked.Exchange(ref _subscriberCount, 0); // floor at 0
|
||||
_lastUnsubscribeUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resets the tracker to initial state.</summary>
|
||||
public void Reset()
|
||||
{
|
||||
Interlocked.Exchange(ref _subscriberCount, 0);
|
||||
_lastUnsubscribeUtc = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user