oto: fix a uint32 underflow of WASAPI write frames - #297
Conversation
bufferFrames and paddingFrames are uint32, so when the reported padding transiently exceeded the buffer size, bufferFrames - paddingFrames wrapped around to a huge value. The 'frames <= 0' guard only caught the exact zero case, and GetBuffer then failed with the wrapped count, killing the context instead of skipping the write. Subtract in signed ints and convert at the API boundaries.
|
Two suggestions to address in this PR:
These both concern the change itself and can be handled here. Comment authored by Codex (OpenAI), on behalf of @hajimehoshi. |
|
Thanks for the review! Both points are addressed:
PTAL. |
What issue is this addressing?
No issue filed yet. Found during an audit of the WASAPI driver.
What type of issue is this addressing?
bug
What this PR does | solves
In
(*wasapiContext).writeOnRenderThread(driver_wasapi_windows.go), both operands of the free-space calculation areuint32:GetCurrentPaddingis sampled asynchronously from the audio thread and can transiently report more padding than the buffer size (e.g. right after a device switch or while the event handler is delayed). Onuint32,bufferFrames - paddingFramesthen wraps around:The
frames <= 0guard cannot catch this, andGetBufferis called with ~4 billion frames. WASAPI rejects it withAUDCLNT_E_BUFFER_TOO_LARGE,writeOnRenderThreadreturns the error,loopOnRenderThreadexits, and the whole context dies with a fatal error — instead of simply skipping one write.Reproduction
The wrap-around itself is plain Go semantics:
That a padding value larger than the buffer size is possible follows from the API contract:
GetCurrentPaddingis only documented as "the number of frames of padding" at the moment of the call, with no guarantee it is ≤ the buffer size obtained earlier (GetBufferSizeis only read once at initialization), and the two calls are not atomic with the device state.The fix
Subtract in signed integers so that the existing
frames <= 0guard actually works, and convert at the API boundaries:This makes the driver skip the write (as it already does for the
frames == 0case) and recover on the next sample-ready event instead of tearing the context down.