prevent deadlocks during event stream teardown - #292
Merged
Conversation
DulioCagg
approved these changes
Aug 14, 2026
previous to this change, `Libvirt.SubscribeQEMUEvents` sometimes failed to shut down on context cancellation due to a race between two incorrectly-ordered defers: ```go ctx, cancel := context.WithCancel(ctx) defer cancel() defer l.unsubscribeQEMUEvents(stream) defer stream.Shutdown() ``` the deferred `stream.Shutdown` would run first, allowing a small window where the stream was closed but still registered to receive events. if an event arrived in that window, the shared socket-reader goroutine (which routes every incoming event and RPC response for the connection) would block forever inside `Stream.Push`, since nothing was left to drain the stream's queue. because that goroutine is shared across the whole connection, blocking it wedged every other in-flight RPC too - including the deregister call `l.unsubscribeQEMUEvents(stream)` was waiting on, and `Disconnect`'s own close call. this change reorders those defers so the stream is always unsubscribed before its local reader is shut down, closing the window. furthermore, this also adds a guard in `Stream.Push` that prevents hanging if the stream has already been shut down via context cancellation. Co-authored-by: Nicholas Kwan <ready725@gmail.com>
mjdwitt
force-pushed
the
fix-qemu-event-teardown-race
branch
from
August 14, 2026 18:41
29e0e59 to
5166039
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
previous to this change,
Libvirt.SubscribeQEMUEventssometimes failedto shut down on context cancellation due to a race between two
incorrectly-ordered defers:
the deferred
stream.Shutdownwould run first, allowing a small windowwhere the stream was closed but still registered to receive events. if an
event arrived in that window, the shared socket-reader goroutine (which
routes every incoming event and RPC response for the connection) would
block forever inside
Stream.Push, since nothing was left to drain thestream's queue. because that goroutine is shared across the whole
connection, blocking it wedged every other in-flight RPC too - including
the deregister call
l.unsubscribeQEMUEvents(stream)was waiting on, andDisconnect's own close call. this change reorders those defers so thestream is always unsubscribed before its local reader is shut down,
closing the window.
furthermore, this also adds a guard in
Stream.Pushthat preventshanging if the stream has already been shut down via context
cancellation.