namespace NATS.Server.JetStream.Consumers; /// /// 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. /// public sealed class DeliveryInterestTracker { private readonly TimeSpan _inactiveTimeout; private int _subscriberCount; private DateTime? _lastUnsubscribeUtc; public DeliveryInterestTracker(TimeSpan? inactiveTimeout = null) { _inactiveTimeout = inactiveTimeout ?? TimeSpan.FromSeconds(30); } /// True when at least one subscriber exists on the delivery subject. public bool HasInterest => Volatile.Read(ref _subscriberCount) > 0; /// Current subscriber count. public int SubscriberCount => Volatile.Read(ref _subscriberCount); /// /// 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. /// public bool ShouldDelete { get { if (HasInterest) return false; if (_lastUnsubscribeUtc == null) return false; return DateTime.UtcNow - _lastUnsubscribeUtc.Value >= _inactiveTimeout; } } /// Records a new subscriber on the delivery subject. public void OnSubscribe() { Interlocked.Increment(ref _subscriberCount); _lastUnsubscribeUtc = null; // Reset the inactivity timer } /// Records removal of a subscriber from the delivery subject. public void OnUnsubscribe() { var count = Interlocked.Decrement(ref _subscriberCount); if (count <= 0) { Interlocked.Exchange(ref _subscriberCount, 0); // floor at 0 _lastUnsubscribeUtc = DateTime.UtcNow; } } /// Resets the tracker to initial state. public void Reset() { Interlocked.Exchange(ref _subscriberCount, 0); _lastUnsubscribeUtc = null; } }