feat(ab-transport): process-wide open-attempt budget so the differential never retries an open (Task 18 review r1)
This commit is contained in:
@@ -106,6 +106,35 @@ public sealed class CipConnectionTests
|
||||
target.ForwardOpens.ShouldBe(2); // Large, then exactly one regular
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_spent_open_attempt_budget_refuses_the_reconnect_before_any_io()
|
||||
{
|
||||
// Task 18 review r1 (C1): the differential harness caps the process at one open attempt, so a press
|
||||
// that refuses the Forward Open sees one TCP connect and one Forward Open, never one per remaining tag.
|
||||
await using var target = new FakeCipTarget { ForwardOpenReply = FakeCipTarget.Refuse(0x01, 0x0100) };
|
||||
var time = new ManualTimeProvider();
|
||||
await using var conn = Create(target, Options(size: 504), time);
|
||||
var before = Volatile.Read(ref CipConnection.OpenAttempts);
|
||||
Volatile.Write(ref CipConnection.OpenAttemptBudget, before + 1);
|
||||
try
|
||||
{
|
||||
await Should.ThrowAsync<AbTransportException>(() => conn.SendAsync(ReadTag, TestCt));
|
||||
var retry = conn.SendAsync(ReadTag, TestCt);
|
||||
await time.WaitForTimerDueAtAsync(time.Start + TimeSpan.FromSeconds(1), Guard); // parked in the throttle
|
||||
time.Advance(TimeSpan.FromSeconds(1));
|
||||
var refused = await Should.ThrowAsync<AbTransportException>(() => retry.WaitAsync(Guard, TestCt));
|
||||
|
||||
refused.Status.ShouldBe(AbStatus.Abort);
|
||||
refused.Message.ShouldContain("budget");
|
||||
target.Server.Connections.ShouldBe(1);
|
||||
target.ForwardOpens.ShouldBe(1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Volatile.Write(ref CipConnection.OpenAttemptBudget, -1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Connection_in_use_does_not_fall_back()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user