Adds SlowConsumerTracker class for per-ClientKind slow consumer counting with configurable threshold callbacks, and extends Account with atomic IncrementSlowConsumers/SlowConsumerCount/ResetSlowConsumerCount members. Includes 10 unit tests covering concurrency, threshold firing, and reset.
154 lines
4.7 KiB
C#
154 lines
4.7 KiB
C#
using NATS.Server;
|
|
using NATS.Server.Auth;
|
|
using Shouldly;
|
|
|
|
namespace NATS.Server.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="SlowConsumerTracker"/> and <see cref="Account"/> slow-consumer counters.
|
|
/// Go reference: server/client.go — handleSlowConsumer, markConnAsSlow.
|
|
/// </summary>
|
|
public class SlowConsumerStallGateTests
|
|
{
|
|
// ── SlowConsumerTracker ────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void RecordSlowConsumer_increments_total_count()
|
|
{
|
|
var tracker = new SlowConsumerTracker();
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Router);
|
|
|
|
tracker.TotalCount.ShouldBe(3L);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordSlowConsumer_increments_per_kind_count()
|
|
{
|
|
var tracker = new SlowConsumerTracker();
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Gateway);
|
|
|
|
tracker.GetCount(ClientKind.Client).ShouldBe(2L);
|
|
tracker.GetCount(ClientKind.Gateway).ShouldBe(1L);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCount_returns_zero_for_unrecorded_kind()
|
|
{
|
|
var tracker = new SlowConsumerTracker();
|
|
|
|
tracker.GetCount(ClientKind.Leaf).ShouldBe(0L);
|
|
}
|
|
|
|
[Fact]
|
|
public void Multiple_kinds_tracked_independently()
|
|
{
|
|
var tracker = new SlowConsumerTracker();
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Router);
|
|
tracker.RecordSlowConsumer(ClientKind.Gateway);
|
|
tracker.RecordSlowConsumer(ClientKind.Leaf);
|
|
|
|
tracker.GetCount(ClientKind.Client).ShouldBe(1L);
|
|
tracker.GetCount(ClientKind.Router).ShouldBe(1L);
|
|
tracker.GetCount(ClientKind.Gateway).ShouldBe(1L);
|
|
tracker.GetCount(ClientKind.Leaf).ShouldBe(1L);
|
|
tracker.GetCount(ClientKind.System).ShouldBe(0L);
|
|
tracker.TotalCount.ShouldBe(4L);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnThresholdExceeded_fires_when_threshold_reached()
|
|
{
|
|
var tracker = new SlowConsumerTracker(threshold: 3);
|
|
ClientKind? firedKind = null;
|
|
tracker.OnThresholdExceeded(k => firedKind = k);
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
firedKind.ShouldBeNull();
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
firedKind.ShouldBeNull();
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Router);
|
|
firedKind.ShouldBe(ClientKind.Router);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnThresholdExceeded_not_fired_below_threshold()
|
|
{
|
|
var tracker = new SlowConsumerTracker(threshold: 5);
|
|
var fired = false;
|
|
tracker.OnThresholdExceeded(_ => fired = true);
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
|
|
fired.ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Reset_clears_all_counters()
|
|
{
|
|
var tracker = new SlowConsumerTracker();
|
|
|
|
tracker.RecordSlowConsumer(ClientKind.Client);
|
|
tracker.RecordSlowConsumer(ClientKind.Router);
|
|
tracker.TotalCount.ShouldBe(2L);
|
|
|
|
tracker.Reset();
|
|
|
|
tracker.TotalCount.ShouldBe(0L);
|
|
tracker.GetCount(ClientKind.Client).ShouldBe(0L);
|
|
tracker.GetCount(ClientKind.Router).ShouldBe(0L);
|
|
}
|
|
|
|
// ── Account slow-consumer counters ────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Account_IncrementSlowConsumers_tracks_count()
|
|
{
|
|
var account = new Account("test-account");
|
|
|
|
account.SlowConsumerCount.ShouldBe(0L);
|
|
|
|
account.IncrementSlowConsumers();
|
|
account.IncrementSlowConsumers();
|
|
account.IncrementSlowConsumers();
|
|
|
|
account.SlowConsumerCount.ShouldBe(3L);
|
|
}
|
|
|
|
[Fact]
|
|
public void Account_ResetSlowConsumerCount_clears()
|
|
{
|
|
var account = new Account("test-account");
|
|
|
|
account.IncrementSlowConsumers();
|
|
account.IncrementSlowConsumers();
|
|
account.SlowConsumerCount.ShouldBe(2L);
|
|
|
|
account.ResetSlowConsumerCount();
|
|
|
|
account.SlowConsumerCount.ShouldBe(0L);
|
|
}
|
|
|
|
[Fact]
|
|
public void Thread_safety_concurrent_increments()
|
|
{
|
|
var tracker = new SlowConsumerTracker();
|
|
|
|
Parallel.For(0, 1000, _ => tracker.RecordSlowConsumer(ClientKind.Client));
|
|
|
|
tracker.TotalCount.ShouldBe(1000L);
|
|
tracker.GetCount(ClientKind.Client).ShouldBe(1000L);
|
|
}
|
|
}
|