Skip to content

Wait for a cancelled HID read to actually end - #56

Merged
corgan2222 merged 1 commit into
mainfrom
fix/hid-cancel-waits
Aug 12, 2026
Merged

Wait for a cancelled HID read to actually end#56
corgan2222 merged 1 commit into
mainfrom
fix/hid-cancel-waits

Conversation

@corgan2222

Copy link
Copy Markdown
Owner

Fixes #55.

What was wrong

CancelIo only requests cancellation and returns immediately. The driver could still complete the read afterwards, writing into buf and into overlapped — but overlapped is a local that dies with the return, and the caller reuses buf for the next read. The kernel therefore wrote into memory the Go runtime had already handed to something else.

That is heap corruption. It surfaces nowhere near the cause: seventeen crash reports since 1.9.4, most of them fatal error: selectgo: bad wakeup with a stack pointing at a select in the cooling poll loop that has nothing to do with HID at all.

A second defect in the same two lines: CancelIo cancels only what the calling thread issued, and a goroutine does not stay on one thread. It could cancel nothing and still report success.

The fix

CancelIoEx, then GetOverlappedResult with wait=true. The waiting is the point — it is what makes the operation over before the buffers go back. ERROR_OPERATION_ABORTED is the expected outcome of a cancelled read and is not reported.

Scope

Only reachable with the special-hardware option on, which is off by default. It went from rare to constant in 1.9.4, when the cooling source started polling once a second instead of reading on the measurement tick — the timeout path went from occasional to continuous.

On testing

There is no unit test. The failure needs a real HID device, a read that times out, and the allocator to reuse the buffer before the driver writes — a race that cannot be staged in a test without faking the whole overlapped I/O layer, at which point the test would only assert the shape of the code it was written from. build.ps1 -Check passes; the real verification is a machine with a Kraken running for a day without a crash report.

CancelIo only requests cancellation and returns at once, so the driver could
still complete the read afterwards — writing into buf and into overlapped after
both had been handed back. overlapped is a local that dies with the return, and
the caller reuses buf immediately, so the kernel wrote into memory the runtime
had given to somebody else.

That is heap corruption, and it surfaced far from here: seventeen crash reports
since 1.9.4, most of them "fatal error: selectgo: bad wakeup" pointing at a
select in the cooling poll loop that has nothing to do with any of it.

CancelIoEx, because CancelIo only cancels what the calling thread started and a
goroutine does not stay on one thread — it could cancel nothing and still
report success. Then GetOverlappedResult with wait=true, which is what makes
the operation over before the buffers go back.

Only reachable with the special-hardware option on, which is off by default. It
went from rare to constant in 1.9.4, when the cooling source began polling once
a second instead of reading on the measurement tick.

Fixes #55
@github-actions github-actions Bot added the fix label Aug 12, 2026
@corgan2222
corgan2222 merged commit f1b314a into main Aug 12, 2026
7 checks passed
@corgan2222
corgan2222 deleted the fix/hid-cancel-waits branch August 12, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash in 1.10.0: fatal error: selectgo: bad wakeup

1 participant