Test 3: per-cycle PlaybackDoneFlag + log timeout in fire-and-forget cleanup

Stale Set() from a delayed cleanup task could short-circuit a future
PLAYBACK state; new flag per cycle avoids the cross-cycle leak. Also
log when the 2 s done.Wait timeout fires so silent PortAudio callback
faults are visible during hardware debugging.
This commit is contained in:
2026-06-12 13:38:09 +00:00
parent 5a498e9c91
commit 070cc0767a
+7 -4
View File
@@ -96,7 +96,7 @@ try
int beepFrames = 0; int beepFrames = 0;
int recordOffset = 0; int recordOffset = 0;
short[] recordBuf = null!; // assigned on BEEPING→RECORDING transition short[] recordBuf = null!; // assigned on BEEPING→RECORDING transition
var playbackDone = new PlaybackDoneFlag(); PlaybackDoneFlag? playbackDone = null;
try try
{ {
@@ -157,6 +157,7 @@ try
{ {
try try
{ {
playbackDone = new PlaybackDoneFlag();
FireAndForgetPlayback(device, recordBuf, playbackDone); FireAndForgetPlayback(device, recordBuf, playbackDone);
} }
catch (Exception ex) catch (Exception ex)
@@ -177,7 +178,7 @@ try
case State.Playback: case State.Playback:
{ {
// Discard input during playback (gate the detector). // Discard input during playback (gate the detector).
if (playbackDone.Consume()) if (playbackDone!.Consume())
{ {
model.Reset(); model.Reset();
Console.WriteLine("[state] IDLE"); Console.WriteLine("[state] IDLE");
@@ -287,7 +288,8 @@ static void FireAndForgetBeep(int device, short[] beepBuffer)
// 2 s timeout so a failed callback (which PortAudio silently swallows // 2 s timeout so a failed callback (which PortAudio silently swallows
// and would leave 'done' unset) eventually releases the stream + handle // and would leave 'done' unset) eventually releases the stream + handle
// instead of leaking them for the process lifetime. // instead of leaking them for the process lifetime.
done.Wait(TimeSpan.FromSeconds(2)); if (!done.Wait(TimeSpan.FromSeconds(2)))
Console.Error.WriteLine("[error] beep playback timed out — callback may have faulted");
Thread.Sleep(200); Thread.Sleep(200);
stream.Stop(); stream.Stop();
stream.Dispose(); stream.Dispose();
@@ -348,7 +350,8 @@ static void FireAndForgetPlayback(int device, short[] recordBuf, PlaybackDoneFla
Task.Run(() => Task.Run(() =>
{ {
// Same 2 s safety timeout as the beep cleanup. // Same 2 s safety timeout as the beep cleanup.
done.Wait(TimeSpan.FromSeconds(2)); if (!done.Wait(TimeSpan.FromSeconds(2)))
Console.Error.WriteLine("[error] recording playback timed out — callback may have faulted");
Thread.Sleep(200); Thread.Sleep(200);
stream.Stop(); stream.Stop();
stream.Dispose(); stream.Dispose();