Bench falsified the v1 retransmit (testlogs/riomash-patched-62500-retx): resends tracked NAKs 1:1 (321/321) yet lamps still stuck/missed — under mash bursts the NAK arrives after a newer command is already "latest", so the wrong packet was resent; and total shreds never NAK at all. Now commands are stop-and-wait: ONE in flight (_commandGate), resolved by ACK, NAK, or AckTimeout (50ms); NAK/timeout retransmits THE SAME packet up to CommandRetransmitLimit (2), then drops (idempotent - the next state update supersedes). Attribution is exact by construction and timeouts catch silent shreds. Control-byte replies bypass the gate so board traffic is never delayed; a request's own reply (analog/version/ check) also resolves the wait, so request/reply exchanges never burn the timeout even if the board sends no explicit ACK. 7 tests (same-packet resend, timeout retry+drop, ACK completion, serialization, reply-resolves-request, stray-NAK no-op, disable); 282 green. Mash summary now reports NAK/timeout resends. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
33 lines
1.3 KiB
C#
33 lines
1.3 KiB
C#
namespace RioJoy.Core.Tests;
|
|
|
|
/// <summary>
|
|
/// net48 test-only polyfills. Visible to all tests via enclosing-namespace scope.
|
|
/// </summary>
|
|
internal static class TaskTestExtensions
|
|
{
|
|
/// <summary>
|
|
/// Polyfill for Task<T>.WaitAsync(TimeSpan) (net6+), absent on net48.
|
|
/// Throws <see cref="TimeoutException"/> if the task does not complete in time.
|
|
/// </summary>
|
|
public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout)
|
|
{
|
|
using var cts = new CancellationTokenSource();
|
|
Task completed = await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false);
|
|
if (completed != task)
|
|
throw new TimeoutException($"Task did not complete within {timeout}.");
|
|
cts.Cancel(); // stop the delay timer
|
|
return await task.ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>Non-generic counterpart of <see cref="WaitAsync{T}"/>.</summary>
|
|
public static async Task WaitAsync(this Task task, TimeSpan timeout)
|
|
{
|
|
using var cts = new CancellationTokenSource();
|
|
Task completed = await Task.WhenAny(task, Task.Delay(timeout, cts.Token)).ConfigureAwait(false);
|
|
if (completed != task)
|
|
throw new TimeoutException($"Task did not complete within {timeout}.");
|
|
cts.Cancel();
|
|
await task.ConfigureAwait(false);
|
|
}
|
|
}
|