From: "Daniel P. Berrangé" <berrange@redhat.com>
To: Kevin Wolf <kwolf@redhat.com>
Cc: Eric Blake <eblake@redhat.com>,
qemu-devel@nongnu.org, qemu-block@nongnu.org,
stefanha@redhat.com
Subject: Re: [PATCH 6/8] qio: Hoist ref of listener outside loop
Date: Tue, 11 Nov 2025 16:07:12 +0000 [thread overview]
Message-ID: <aRNfMNc-XJfCl_4E@redhat.com> (raw)
In-Reply-To: <aRNatBbLB29clhPk@redhat.com>
On Tue, Nov 11, 2025 at 04:48:04PM +0100, Kevin Wolf wrote:
> Am 11.11.2025 um 15:43 hat Daniel P. Berrangé geschrieben:
> > On Wed, Nov 05, 2025 at 03:57:29PM -0600, Eric Blake wrote:
> > > On Tue, Nov 04, 2025 at 11:13:48AM +0000, Daniel P. Berrangé wrote:
> > > > On Mon, Nov 03, 2025 at 02:10:57PM -0600, Eric Blake wrote:
> > > > > The point of QIONetListener is to allow a server to listen to more
> > > > > than one socket address at a time, and respond to clients in a
> > > > > first-come-first-serve order across any of those addresses. While
> > > > > some servers (like NBD) really do want to serve multiple simultaneous
> > > > > clients, many other servers only care about the first client to
> > > > > connect, and will immediately deregister the callback, possibly by
> > > > > dropping their reference to the QIONetListener. The existing code
> > > > > ensures that all other pending callbacks remain safe once the first
> > > > > callback drops the listener, by adding an extra reference to the
> > > > > listener for each GSource created, where those references pair to the
> > > > > eventual teardown of each GSource after a given callbacks has been
> > > > > serviced or aborted. But it is equally acceptable to hoist the
> > > > > reference to the listener outside the loop - as long as there is a
> > > > > callback function registered, it is sufficient to have a single
> > > > > reference live for the entire array of sioc, rather than one reference
> > > > > per sioc in the array.
> > > > >
> > > > > Hoisting the reference like this will make it easier for an upcoming
> > > > > patch to still ensure the listener cannot be prematurely garbage
> > > > > collected during the user's callback, even when the callback no longer
> > > > > uses a per-sioc GSource.
> > > >
> > > > It isn't quite this simple. Glib reference counts the callback
> > > > func / data, holding a reference when dispatching the callback.
> > > >
> > > > IOW, even if the GSource is unrefed, the callback 'notify'
> > > > function won't be called if the main loop is in the process
> > > > of dispatching.
snip
> > That appears to work ok, however, there's still a race window that is
> > not solved. Between the time thread 2 sees POLLIN, and when it calls
> > the dispatch(sock) function, it is possible that thread 1 will drop
> > the last reference:
> >
> >
> >
> > Thread 1:
> > qio_net_listener_set_client_func(lstnr, f, ...);
> > => object_ref(listener)
> > => foreach sock: socket
> > => sock_src = qio_channel_socket_add_watch_source(sock, ...., lstnr, NULL);
> >
> > Thread 2:
> > poll()
> > => event POLLIN on socket
> >
> > Thread 1:
> > qio_net_listener_set_client_func(lstnr, NULL, ...);
> > => foreach sock: socket
> > => g_source_unref(sock_src)
> > => object_unref(listener)
> > unref(lstnr) (still 1 reference left)
>
> Is what you're worried about that there is still a reference left in
> the opaque pointer of an fd handler, but it's not in the refcount and
> therefore this already frees the listener while thread 2 will still
> access it?
Yes, exactly.
>
> >
> > Thread 2:
> > => call dispatch(sock)
> > => ref(lstnr)
> > ...do stuff..
> > => unref(lstnr) (the final reference)
> > => finalize(lstnr)
> > => return dispatch(sock)
> > => unref(GSourceCallback)
> >
> >
> > I don't see a way to solve this without synchronization with the event
> > loop for releasing the reference on the opaque data for the dispatcher
> > callback. That's what the current code does, but I'm seeing no way for
> > the AioContext event loop callbacks to have anything equivalent. This
> > feels like a gap in the AioContext design.
>
> I think the way you would normally do this is schedule a BH in thread 2
> to do the critical work. If you delete the fd handler and unref the
> listener in thread 2, then there is no race.
Yes, using a BH would be safe, provided you put the BH in the right
loop, given that we have choice of the main event loop, or a non-default
GMainContext, or the special AioContext that NBD is relying on.
> But maybe adding a callback for node deletion in AioHandler wouldn't
> hurt because the opaque pointer pretty much always references something
> and doing an unref when deleting the AioHandler should be a pretty
> common pattern.
That would likely make the scenario less error-prone, compared to
remembering to use a BH to synchronize.
> > This is admittedly an incredibly hard to trigger race condition. It would
> > need a client to be calling a QMP command that tears down the NBD server,
> > at the exact same time as a new NBD client was incoming. Or the same kind
> > of scenario for other pieces of QEMU code using QIONetListener. This still
> > makes me worried though, as rare races have a habit of hitting QEMU
> > eventually.
>
> Aren't both QMP and incoming NBD connections always handled in the main
> thread? I'm not sure if I know a case where we would actually get this
> pattern with two different threads today. Of course, that doesn't mean
> that we couldn't get it in the future.
Yeah, I believe we're probably safe in todays usage. My concern was
primarily about any surprises in the conceptual design that might
impact us in future.
I guess if NBD is the only thing using AioContext for QIONetListener
today, we could hoist the ref/unref only when using a AioContext,
and keep the GDestroyNotify usage for the GMainContext code path
to significantly limit the exposure. Avoid needing to do anything
extra for AioHandlers right before release.
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
next prev parent reply other threads:[~2025-11-11 16:08 UTC|newest]
Thread overview: 45+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-03 20:10 [PATCH 0/8] Fix deadlock with bdrv_open of self-served NBD Eric Blake
2025-11-03 20:10 ` [PATCH 1/8] qio: Add trace points to net_listener Eric Blake
2025-11-04 10:43 ` Daniel P. Berrangé
2025-11-04 11:08 ` Kevin Wolf
2025-11-05 17:18 ` Eric Blake
2025-11-06 12:20 ` Kevin Wolf
2025-11-03 20:10 ` [PATCH 2/8] qio: Minor optimization when callback function is unchanged Eric Blake
2025-11-04 10:44 ` Daniel P. Berrangé
2025-11-04 11:13 ` Kevin Wolf
2025-11-05 17:23 ` Eric Blake
2025-11-03 20:10 ` [PATCH 3/8] qio: Remember context of qio_net_listener_set_client_func_full Eric Blake
2025-11-04 10:50 ` Daniel P. Berrangé
2025-11-04 11:25 ` Kevin Wolf
2025-11-05 19:18 ` Eric Blake
2025-11-03 20:10 ` [PATCH 4/8] qio: Factor out helpers qio_net_listener_[un]watch Eric Blake
2025-11-04 11:03 ` Daniel P. Berrangé
2025-11-04 13:15 ` Kevin Wolf
2025-11-05 19:22 ` Eric Blake
2025-11-04 12:37 ` Kevin Wolf
2025-11-04 13:10 ` Daniel P. Berrangé
2025-11-05 19:32 ` Eric Blake
2025-11-03 20:10 ` [PATCH 5/8] qio: Let listening sockets remember their owning QIONetListener Eric Blake
2025-11-05 20:06 ` Eric Blake
2025-11-06 18:35 ` Eric Blake
2025-11-07 8:50 ` Daniel P. Berrangé
2025-11-07 13:47 ` Eric Blake
2025-11-03 20:10 ` [PATCH 6/8] qio: Hoist ref of listener outside loop Eric Blake
2025-11-04 11:13 ` Daniel P. Berrangé
2025-11-05 21:57 ` Eric Blake
2025-11-11 14:43 ` Daniel P. Berrangé
2025-11-11 15:48 ` Kevin Wolf
2025-11-11 16:07 ` Daniel P. Berrangé [this message]
2025-11-11 19:09 ` Eric Blake
2025-11-11 20:07 ` Eric Blake
2025-11-12 10:31 ` Daniel P. Berrangé
2025-11-12 10:20 ` Daniel P. Berrangé
2025-11-03 20:10 ` [PATCH 7/8] qio: Use AioContext for default-context QIONetListener Eric Blake
2025-11-04 11:37 ` Daniel P. Berrangé
2025-11-05 22:06 ` Eric Blake
2025-11-04 15:14 ` Kevin Wolf
2025-11-03 20:10 ` [PATCH 8/8] iotests: Add coverage of recent NBD qio deadlock fix Eric Blake
2025-11-04 11:38 ` Vladimir Sementsov-Ogievskiy
2025-11-05 22:10 ` Eric Blake
2025-11-06 8:20 ` Vladimir Sementsov-Ogievskiy
2025-11-06 12:26 ` Kevin Wolf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aRNfMNc-XJfCl_4E@redhat.com \
--to=berrange@redhat.com \
--cc=eblake@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.