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;
}
}