Repository navigation
windows: keep Close's reply channel when an error cannot be delivered - #9
Conversation
Close queues a reply channel on done and waits for the reader to send on it after its cleanup. sendError takes that channel when the watcher is closing and drops it, so the reader never sees the shutdown request and Close blocks forever. These tests pin the channel handoff and the end-to-end Close; they fail before the fix.
sendError received the reply channel that Close queued on done and discarded it, unlike sendEvent, which puts it back. When nobody reads Errors any more and the backend then reports an error (an overflow, a failed CancelIo or CloseHandle), the reader handled the wakeup without the shutdown request, skipped the cleanup of its watches and the completion port, and Close waited on the reply forever. sendError now puts the channel back, as sendEvent does; TestSendErrorKeepsCloseReply and TestCloseAfterUnreadError fail without this change on windows-latest and pass with it.
PR SummaryMedium Risk Overview
Reviewed by Cursor Bugbot for commit 17a6ca9. Bugbot is set up for automated code reviews on this repo. Configure here. |
Close read closed under the lock, released it, and set it under a second acquisition, so two concurrent calls could both proceed and queue two reply channels. done holds one, so the second Close blocked sending, and with an undeliverable error between them the reader, now keeping the first reply, could not drain the second: both calls and the reader hung. The check and the set now happen under one lock, so a second Close returns at once. TestConcurrentClose runs eight concurrent Close calls under a timeout and checks that no reply is left on done; the interleaving is a race, so it guards rather than reproduces.
What
On Windows,
Closecan block forever once the error consumer has gone away and the backend then reports an error.Closeputs a reply channel ondone, wakes the reader, and waits on that channel.sendErrortakes the channel fromdonewhen the watcher is closing and drops it, wheresendEventputs it back. Its callers ignore thefalsereturn, so the reader handles the wakeup without seeing a shutdown request. It then skips the cleanup of its watches and of the completion port, andClosenever returns.This happens whenever nobody reads
Errorsany more and an error arrives during shutdown: an overflow (ErrEventOverflow), a failedCancelIoorCloseHandleinremWatch, or aGetQueuedCompletionStatusfailure.Removereaches the samesendErrorcalls.Fix
sendErrorputs the reply channel back ondone, exactly assendEventdoes, and still returnsfalse. The reader takes the channel on its next wakeup and runs its normal shutdown.Tests (Windows)
TestSendErrorKeepsCloseReply: with a reply queued and nobody readingErrors,sendErrorreturnsfalseand leaves the same channel ondone.TestCloseAfterUnreadError: a real watcher. Close's reply is queued, an error meets an unreadErrorschannel, and the reader is woken. The reply must arrive within 10 s.The first commit adds only the tests, and its CI run on
windows-latestis the failing baseline. The second commit is the fix.On Linux, the existing
TestAdd/permission_deniedfails when the tests run as root, before and after this change. It is unrelated.CI
c6f96512c9(run 39993141422) only the two new tests fail. At the fix head73a141ee4feverything passes.backend_windows*.go.c6f96512c9and was cancelled at73a141ee4f.After merge, please tag
v0.0.2. belt's envd pins this commit's pseudo-version until then.