serial write hardening: bound the overlapped-write wait (500ms + CancelIo + 3s sick window) -- a dropped USB-serial link parked the game forever in GetOverlappedResult (ALPHA-MR minidump: SendPacket <- RIO ctor); txTO= counter joins the [rio] health line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-10 13:30:41 -05:00
co-authored by Claude Fable 5
parent ab1b3439f4
commit 716e10e3fa
2 changed files with 52 additions and 4 deletions
+49 -4
View File
@@ -410,6 +410,20 @@ int PCSerialPacket::ReceivePacket(BYTE *destPtr) // returns len (zero if none)
// Returns:
// void
//-------------------------------------------------------------------------
//
// Serial-write health (see the bounded wait below): after a write timeout
// the link is SICK for 3 s and writes fast-drop -- one stall, not one per
// poll. BTPcspakTxTimeouts feeds the [rio] telemetry.
//
static unsigned gPcspakTxTimeout = 0;
static DWORD gPcspakTxSickUntil = 0;
extern "C" unsigned
BTPcspakTxTimeouts()
{
return gPcspakTxTimeout;
}
void PCSerialPacket::SendPacket(BYTE *srcPtr) //sends packet
{
//--------------------------------------------------------------
@@ -418,6 +432,11 @@ void PCSerialPacket::SendPacket(BYTE *srcPtr) //sends packet
if(!enabled) //if disabled, don't even try
return;
if (gPcspakTxSickUntil != 0 && GetTickCount() < gPcspakTxSickUntil)
{
return; // adapter sick: drop, don't stall
}
BYTE cmd = *srcPtr; //get the command byte
cmd &= 0x7F; //remove MSB
int length = txLengthPtr[cmd]; //get the length in al
@@ -459,11 +478,37 @@ void PCSerialPacket::SendPacket(BYTE *srcPtr) //sends packet
return;
}
}
DWORD bytesWritten;
GetOverlappedResult(hComm, &overlapSend, &bytesWritten, TRUE);
if(bytesWritten != length + 1)
//
// BOUNDED completion wait (2026-08-10, ALPHA-MR field minidump). The
// RIO's "COM1" is a USB-serial adapter; when its USB link drops with a
// write in flight the overlapped write NEVER completes, and the original
// GetOverlappedResult(..., TRUE) parked the whole game at 0 CPU forever
// (cdb-proven: NtWaitForSingleObject <- GetOverlappedResult <-
// SendPacket <- RIO ctor). Now: 500 ms bound (9600 baud needs ~21 ms),
// CancelIo on expiry, and a 3 s SICK window that fast-drops writes so a
// dead adapter costs one stall, not one per 50 ms poll -- the game stays
// at frame rate with a degraded RIO and the keyboard fallback alive.
// gPcspakTxTimeout feeds the [rio] health line (txTO=).
//
DWORD bytesWritten = 0;
DWORD wait_result = WaitForSingleObject(overlapSend.hEvent, 500);
if (wait_result == WAIT_OBJECT_0)
{
DEBUG_STREAM << "Not all data was written to serial port!\n" << std::flush;
GetOverlappedResult(hComm, &overlapSend, &bytesWritten, FALSE);
gPcspakTxSickUntil = 0;
if (bytesWritten != (DWORD)(length + 1))
{
DEBUG_STREAM << "Not all data was written to serial port!\n"
<< std::flush;
}
}
else
{
// No print here -- serial-path discipline; the counter surfaces in
// the [rio] health line (txTO=) from the glass-tick printer.
CancelIo(hComm);
++gPcspakTxTimeout;
gPcspakTxSickUntil = GetTickCount() + 3000;
}
delete buffer;
}