Skip to content

Commit 9846511

Browse files
HandyS11claude
andcommitted
refactor(connections): apply PR review feedback
- WatchLivenessAsync now catches the cancellation OperationCanceledException and returns ReconnectReason.Stopped, so on shutdown the WhenAny winner is a clean reason instead of a faulted task (its doc already advertised Stopped). - Reword the fake's monuments-timeout helpers/test: the rig/monuments fetch runs in the background marker poll now, not on the connect path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c6f3611 commit 9846511

3 files changed

Lines changed: 21 additions & 13 deletions

File tree

src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -658,15 +658,23 @@ private async Task<ReconnectReason> WatchLivenessAsync(
658658
IRustServerConnection connection,
659659
CancellationToken ct)
660660
{
661-
while (!ct.IsCancellationRequested)
661+
try
662662
{
663-
await Task.Delay(_options.LivenessPollInterval, ct).ConfigureAwait(false);
664-
if (!connection.IsConnected)
663+
while (!ct.IsCancellationRequested)
665664
{
666-
LogSocketDropped(logger, key.Server);
667-
return ReconnectReason.Unreachable;
665+
await Task.Delay(_options.LivenessPollInterval, ct).ConfigureAwait(false);
666+
if (!connection.IsConnected)
667+
{
668+
LogSocketDropped(logger, key.Server);
669+
return ReconnectReason.Unreachable;
670+
}
668671
}
669672
}
673+
catch (OperationCanceledException)
674+
{
675+
// The connected window is ending (stop/reconnect): Task.Delay throws on cancellation.
676+
// Return Stopped so the WhenAny winner is a clean reason rather than a faulted task.
677+
}
670678

671679
return ReconnectReason.Stopped;
672680
}

tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,17 +233,17 @@ public async Task Heartbeat_Unreachable_ReconnectsAndRecovers()
233233
}
234234

235235
/// <summary>
236-
/// A per-request timeout during connect-time setup (here the oil-rig monuments fetch) surfaces as an
237-
/// <see cref="OperationCanceledException"/> even though the connection token is not cancelled. It must
238-
/// degrade rig detection for this window, NOT terminate the whole connection loop — otherwise a
236+
/// A per-request timeout in a connected window (here the marker poll's oil-rig monuments fetch) surfaces
237+
/// as an <see cref="OperationCanceledException"/> even though the connection token is not cancelled. It
238+
/// must degrade rig detection for this window, NOT terminate the whole connection loop — otherwise a
239239
/// transient slow map endpoint permanently kills the connection with no reconnect (the real-world bug).
240240
/// </summary>
241241
[Fact]
242242
public async Task Connect_MonumentsTimeout_DoesNotKillLoop_AndStillReconnects()
243243
{
244244
var source = new FakeRustSocketSource();
245245
source.EnqueueConnect(SocketConnectOutcome.Connected);
246-
source.TimeoutOnMonumentsOnce(); // connect-time rig fetch times out on the FIRST connection only
246+
source.TimeoutOnMonumentsOnce(); // the marker poll's rig fetch times out on the FIRST connection only
247247
source.EnqueueHeartbeat(HeartbeatResult.Ok(2)); // first heartbeat -> Connected
248248
source.EnqueueHeartbeat(HeartbeatResult.Unreachable); // next heartbeat -> drop
249249
source.EnqueueConnect(SocketConnectOutcome.Connected); // reconnect

tests/RustPlusBot.Features.Connections.Tests/Fakes/FakeRustSocketSource.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public IRustServerConnection Create(string ip, int port, ulong steamId, string p
6161
connection.MonumentsResult = _pendingMonuments;
6262
_pendingMonuments = [];
6363

64-
// Transfer any pre-staged monuments timeout so the connect-time rig fetch throws for the NEXT
64+
// Transfer any pre-staged monuments timeout so the marker poll's rig fetch throws for the NEXT
6565
// connection only (mimicking a real per-request timeout, which surfaces as OperationCanceledException).
6666
connection.MonumentsTimeout = _pendingMonumentsTimeout;
6767
_pendingMonumentsTimeout = false;
@@ -126,10 +126,10 @@ public void EnqueueMarkers(IReadOnlyList<MapMarkerSnapshot> markers) =>
126126
public void SetMonuments(IReadOnlyList<MonumentSnapshot> monuments) => _pendingMonuments = monuments;
127127

128128
/// <summary>
129-
/// Makes the NEXT connection's connect-time <see cref="FakeConnection.GetMonumentsAsync"/> throw
129+
/// Makes the NEXT connection's <see cref="FakeConnection.GetMonumentsAsync"/> throw
130130
/// <see cref="OperationCanceledException"/>, simulating a per-request timeout (the outer connection
131-
/// token is NOT cancelled). Applies to the next connection only. Call before
132-
/// <see cref="EnsureConnectionAsync"/>.
131+
/// token is NOT cancelled). The supervisor issues this fetch from its background marker poll, not the
132+
/// connect path. Applies to the next connection only. Call before <see cref="EnsureConnectionAsync"/>.
133133
/// </summary>
134134
public void TimeoutOnMonumentsOnce() => _pendingMonumentsTimeout = true;
135135

0 commit comments

Comments
 (0)