Adds MaxDeliver property, exceeded sequence tracking, DrainExceeded, DeliveryExceededPolicy enum, and ScheduleRedelivery enforcement to AckProcessor; 10 new tests in MaxDeliveriesTests.cs.
185 lines
6.6 KiB
C#
185 lines
6.6 KiB
C#
// Go: consumer.go maxDeliver config — max delivery enforcement and advisory generation
|
|
using NATS.Server.JetStream.Consumers;
|
|
|
|
namespace NATS.Server.Tests.JetStream.Consumers;
|
|
|
|
public class MaxDeliveriesTests
|
|
{
|
|
// Test 1: MaxDeliver=0 means unlimited; many redeliveries do not terminate
|
|
[Fact]
|
|
public void MaxDeliver_zero_means_unlimited()
|
|
{
|
|
// Go: consumer.go — maxDeliver=0 disables the limit entirely
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 0;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
for (var i = 0; i < 100; i++)
|
|
ack.ScheduleRedelivery(1, delayMs: 1);
|
|
|
|
ack.PendingCount.ShouldBe(1);
|
|
ack.ExceededCount.ShouldBe(0);
|
|
ack.TerminatedCount.ShouldBe(0);
|
|
}
|
|
|
|
// Test 2: MaxDeliver=3 terminates on the 4th redelivery attempt
|
|
[Fact]
|
|
public void MaxDeliver_terminates_when_exceeded()
|
|
{
|
|
// Go: consumer.go — maxDeliver enforced in ScheduleRedelivery; Deliveries > maxDeliver → terminate
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 3;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
// Deliveries starts at 1 after Register; three more bumps → Deliveries reaches 4
|
|
ack.ScheduleRedelivery(1, delayMs: 1); // Deliveries = 2 (ok)
|
|
ack.ScheduleRedelivery(1, delayMs: 1); // Deliveries = 3 (ok, at limit)
|
|
ack.ScheduleRedelivery(1, delayMs: 1); // Deliveries = 4 (exceeded)
|
|
|
|
ack.PendingCount.ShouldBe(0);
|
|
ack.TerminatedCount.ShouldBe(1);
|
|
}
|
|
|
|
// Test 3: Exceeded sequence is added to the exceeded list
|
|
[Fact]
|
|
public void Exceeded_sequence_added_to_list()
|
|
{
|
|
// Go: consumer.go — exceeded sequences collected for advisory events
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 1;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
// Deliveries starts at 1; next call makes it 2 which exceeds maxDeliver=1
|
|
ack.ScheduleRedelivery(1, delayMs: 1);
|
|
|
|
ack.ExceededCount.ShouldBe(1);
|
|
ack.GetExceededSequences().ShouldContain((ulong)1);
|
|
}
|
|
|
|
// Test 4: DrainExceeded clears the list after returning sequences
|
|
[Fact]
|
|
public void DrainExceeded_clears_list()
|
|
{
|
|
// Go: consumer.go — drain after sending advisories to avoid duplicate events
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 1;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
ack.ScheduleRedelivery(1, delayMs: 1);
|
|
|
|
ack.ExceededCount.ShouldBe(1);
|
|
|
|
ack.DrainExceeded();
|
|
|
|
ack.ExceededCount.ShouldBe(0);
|
|
ack.GetExceededSequences().ShouldBeEmpty();
|
|
}
|
|
|
|
// Test 5: Exceeded sequence is moved to the terminated set
|
|
[Fact]
|
|
public void Exceeded_message_is_terminated()
|
|
{
|
|
// Go: consumer.go — exceeded sequence enters terminated set; cannot be redelivered
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 2;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
ack.ScheduleRedelivery(1, delayMs: 1); // Deliveries = 2 (at limit)
|
|
ack.ScheduleRedelivery(1, delayMs: 1); // Deliveries = 3 (exceeded)
|
|
|
|
// Sequence removed from pending and not redeliverable
|
|
ack.PendingCount.ShouldBe(0);
|
|
ack.TerminatedCount.ShouldBe(1);
|
|
}
|
|
|
|
// Test 6: ProcessNak triggers redelivery check through ScheduleRedelivery
|
|
[Fact]
|
|
public void ProcessNak_triggers_redelivery_check()
|
|
{
|
|
// Go: consumer.go — processNak calls ScheduleRedelivery which enforces maxDeliver
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 2;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
ack.ProcessNak(1); // Deliveries = 2 (at limit)
|
|
ack.PendingCount.ShouldBe(1);
|
|
ack.ExceededCount.ShouldBe(0);
|
|
|
|
ack.ProcessNak(1); // Deliveries = 3 (exceeded)
|
|
ack.PendingCount.ShouldBe(0);
|
|
ack.ExceededCount.ShouldBe(1);
|
|
}
|
|
|
|
// Test 7: ScheduleRedelivery enforces max deliver regardless of whether it was called from expiry path
|
|
[Fact]
|
|
public void TryGetExpired_respects_max_deliver()
|
|
{
|
|
// Go: consumer.go — ScheduleRedelivery checks maxDeliver regardless of call site;
|
|
// the expiry redelivery loop calls ScheduleRedelivery so maxDeliver is enforced there too.
|
|
// We verify this by directly calling ScheduleRedelivery after the deadline would have passed,
|
|
// without relying on wall-clock time.
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 1;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
// Deliveries starts at 1; calling ScheduleRedelivery makes it 2 which exceeds maxDeliver=1
|
|
// This is exactly what the expiry dispatch loop does: read expired seq, call ScheduleRedelivery
|
|
ack.ScheduleRedelivery(1, delayMs: 1);
|
|
|
|
ack.PendingCount.ShouldBe(0);
|
|
ack.ExceededCount.ShouldBe(1);
|
|
}
|
|
|
|
// Test 8: MaxDeliver=-1 is treated as unlimited (negative values clamped to 0)
|
|
[Fact]
|
|
public void MaxDeliver_negative_treated_as_unlimited()
|
|
{
|
|
// Go: consumer.go — any non-positive maxDeliver is treated as unlimited
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = -1;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
|
|
for (var i = 0; i < 50; i++)
|
|
ack.ScheduleRedelivery(1, delayMs: 1);
|
|
|
|
ack.PendingCount.ShouldBe(1);
|
|
ack.ExceededCount.ShouldBe(0);
|
|
ack.MaxDeliver.ShouldBe(0); // -1 clamped to 0
|
|
}
|
|
|
|
// Test 9: ExceededPolicy defaults to Drop
|
|
[Fact]
|
|
public void DeliveryExceededPolicy_defaults_to_drop()
|
|
{
|
|
// Go: consumer.go — default behavior for exceeded messages is to drop them
|
|
var ack = new AckProcessor();
|
|
|
|
ack.ExceededPolicy.ShouldBe(DeliveryExceededPolicy.Drop);
|
|
}
|
|
|
|
// Test 10: Multiple independent sequences can each exceed max deliveries
|
|
[Fact]
|
|
public void Multiple_sequences_can_exceed()
|
|
{
|
|
// Go: consumer.go — each sequence tracked independently; multiple can exceed in same window
|
|
var ack = new AckProcessor();
|
|
ack.MaxDeliver = 1;
|
|
ack.Register(1, ackWaitMs: 5000);
|
|
ack.Register(2, ackWaitMs: 5000);
|
|
ack.Register(3, ackWaitMs: 5000);
|
|
|
|
// Each sequence starts at Deliveries=1; one call makes each exceed maxDeliver=1
|
|
ack.ScheduleRedelivery(1, delayMs: 1);
|
|
ack.ScheduleRedelivery(2, delayMs: 1);
|
|
ack.ScheduleRedelivery(3, delayMs: 1);
|
|
|
|
ack.ExceededCount.ShouldBe(3);
|
|
ack.PendingCount.ShouldBe(0);
|
|
ack.TerminatedCount.ShouldBe(3);
|
|
|
|
var exceeded = ack.GetExceededSequences();
|
|
exceeded.ShouldContain((ulong)1);
|
|
exceeded.ShouldContain((ulong)2);
|
|
exceeded.ShouldContain((ulong)3);
|
|
}
|
|
}
|