Writer thread survives write faults instead of dying mid-run
A single failed port write killed the paced writer thread permanently while the port still reported open, so every later byte (ACKs, the CheckRequest handshake) queued forever: wire tap showed 2 of 3 bytes of a stray ButtonPressed at t=54ms, then 1167 unanswered CheckRequests. The trigger was com0com flow control with the game side not yet open -- the third byte blocked past WriteTimeout, threw, and the catch returned. A real UART cannot wedge; it shifts bits into the line whether or not anyone listens. On a write fault, drop the stalled byte plus the stale backlog, log the stall/recovery transition once, and keep the writer alive -- TX resumes as soon as the host drains its end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,12 @@ namespace VRio.Core.Device;
|
|||||||
/// monotonic slot deadline (<c>slot = max(prevSlot + period, now)</c>), so
|
/// monotonic slot deadline (<c>slot = max(prevSlot + period, now)</c>), so
|
||||||
/// the stream averages the true baud rate without bursting after idle.</para>
|
/// the stream averages the true baud rate without bursting after idle.</para>
|
||||||
///
|
///
|
||||||
|
/// <para>A write fault never kills the writer — a real UART streams into an
|
||||||
|
/// unterminated line rather than blocking. If the virtual wire's far side
|
||||||
|
/// stops draining (peer end closed, write timeout), the stalled byte and the
|
||||||
|
/// queued backlog are dropped and transmission resumes with the next fresh
|
||||||
|
/// packet once the host reads again.</para>
|
||||||
|
///
|
||||||
/// <para>RIOJoy pulses DTR for 50 ms when it opens its end (the board-reset
|
/// <para>RIOJoy pulses DTR for 50 ms when it opens its end (the board-reset
|
||||||
/// handshake); through a null modem that arrives here as a DSR blip, which is
|
/// handshake); through a null modem that arrives here as a DSR blip, which is
|
||||||
/// surfaced via <see cref="HostHandshake"/> so the UI can show that a host
|
/// surfaced via <see cref="HostHandshake"/> so the UI can show that a host
|
||||||
@@ -201,6 +207,7 @@ public sealed class VRioSerialService : IDisposable
|
|||||||
{
|
{
|
||||||
var one = new byte[1];
|
var one = new byte[1];
|
||||||
long slot = Stopwatch.GetTimestamp();
|
long slot = Stopwatch.GetTimestamp();
|
||||||
|
bool txHealthy = true; // log stall/recovery transitions, not every byte
|
||||||
|
|
||||||
while (_running)
|
while (_running)
|
||||||
{
|
{
|
||||||
@@ -226,12 +233,33 @@ public sealed class VRioSerialService : IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
port.Write(one, 0, 1);
|
port.Write(one, 0, 1);
|
||||||
|
if (!txHealthy)
|
||||||
|
{
|
||||||
|
txHealthy = true;
|
||||||
|
Logged?.Invoke("TX recovered — host is draining the wire again");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is IOException or InvalidOperationException or TimeoutException)
|
catch (Exception ex) when (ex is IOException or InvalidOperationException or TimeoutException)
|
||||||
{
|
{
|
||||||
if (_running)
|
if (!_running)
|
||||||
Logged?.Invoke($"Write failed: {ex.Message}");
|
return;
|
||||||
return;
|
// A real UART cannot wedge: it shifts bits onto the line whether
|
||||||
|
// or not anyone is listening. A failed write means the virtual
|
||||||
|
// wire's far side stopped draining (peer end closed), so the
|
||||||
|
// queued backlog is already stale — drop it and keep serving
|
||||||
|
// fresh traffic; writes land again once the host reads its end.
|
||||||
|
int dropped;
|
||||||
|
lock (_txGate)
|
||||||
|
{
|
||||||
|
dropped = _txQueue.Count;
|
||||||
|
_txQueue.Clear();
|
||||||
|
}
|
||||||
|
if (txHealthy)
|
||||||
|
{
|
||||||
|
txHealthy = false;
|
||||||
|
Logged?.Invoke($"TX stalled ({ex.Message.TrimEnd('.')}) — dropped {dropped + 1} stale byte(s), writer stays alive");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the wait overshot its slot, pace the next byte from the
|
// If the wait overshot its slot, pace the next byte from the
|
||||||
|
|||||||
Reference in New Issue
Block a user