Re: [RESEND PATCH V10 02/15] iothread: introduce iothread_ref/unref to track attached devices

Zhang Chen <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <CAK3tnvJWhQUBy5rLL9q9KdaA_Cf=Jo6ndkX89Gh6w7f0-j=Gxg@mail.gmail.com>
On Tue, Jul 21, 2026 at 8:20 PM Markus Armbruster <[email protected]> wrote:
>
> Zhang Chen <[email protected]> writes:
>
> > Currently, IOThreads do not maintain a record of which devices are
> > associated with them.
>
> It's not just devices, it's QOM objects and block exports, as far as I
> can tell.
>
> >                       This makes it difficult to monitor the
> > workload distribution of IOThreads, especially in complex
> > hotplug scenarios involving multiple virtio-blk or virtio-scsi devices.
> >
> > This patch introduces a reference counting and tracking mechanism
> > within the IOThread object:
> >
> > - iothread_ref(): Prepends the device's IOThreadHolder to a list.
> > - iothread_unref(): Searches for the IOThreadHolder using a
>
> Search what?
>
> >   string comparison (strcmp), releases the associated memory
> >   upon a successful match.
>
> More implementation detail than I'd use here.
>
> > - holders: A IOThreadHolderList storing the IOThreadHolder
> >   of attached devices for runtime introspection.
>
> Is this the list mentioned for iothread_ref()?  If yes, I'd put it
> first, then refer to it, perhaps like this:
>
>   - holders: a list describing what is "holding" the I/O thread
>   - iothread_ref(): add a "holder" to this list
>   - iothread_unref(): remove a "holder" from this list
>
> And maybe
>
>   A "holder" can be a QOM object or a block export.
>
> > A later commit will add QMP commands to let management applications
> > query the attachment status of IOThreads.
> >
> > Signed-off-by: Zhang Chen <[email protected]>
> > Reviewed-by: Stefan Hajnoczi <[email protected]>
> > ---
> >  include/system/iothread.h | 11 ++++++
> >  iothread.c                | 70 +++++++++++++++++++++++++++++++++++++++
> >  qapi/misc.json            | 59 +++++++++++++++++++++++++++++++++
> >  3 files changed, 140 insertions(+)
> >
> > diff --git a/include/system/iothread.h b/include/system/iothread.h
> > index a1ef7696cb..ef0b2f9648 100644
> > --- a/include/system/iothread.h
> > +++ b/include/system/iothread.h
> > @@ -38,6 +38,9 @@
> >  #define IOTHREAD_POLL_WEIGHT_DEFAULT 0ULL
> >  #endif
> >
> > +typedef struct IOThreadHolder IOThreadHolder;
> > +typedef struct IOThreadHolderList IOThreadHolderList;
>
> These are is already defined in generated qapi/qapi-types-misc.h.  Why
> not include it instead?
>
> > +
> >  struct IOThread {
> >      EventLoopBase parent_obj;
> >
> > @@ -50,6 +53,11 @@ struct IOThread {
> >      bool stopping;              /* has iothread_stop() been called? */
> >      bool running;               /* should iothread_run() continue? */
> >      int thread_id;
> > +    /*
> > +     * The list elements are of type IOThreadHolder, which can
> > +     * represent either a QOM path or a block export name.
> > +     */
>
> Back when holders was a GList, explaining the element type in a comment
> was useful.  Now it's not.
>
> > +    IOThreadHolderList *holders;
> >
> >      /* AioContext poll parameters */
> >      int64_t poll_max_ns;
> > @@ -82,4 +90,7 @@ void iothread_destroy(IOThread *iothread);
> >   */
> >  bool qemu_in_iothread(void);
> >
> > +void iothread_ref(IOThread *iothread, const IOThreadHolder *holder);
> > +void iothread_unref(IOThread *iothread, const IOThreadHolder *holder);
> > +
> >  #endif /* IOTHREAD_H */
> > diff --git a/iothread.c b/iothread.c
> > index 3558535b40..38f273c0e9 100644
> > --- a/iothread.c
> > +++ b/iothread.c
> > @@ -21,10 +21,78 @@
> >  #include "system/iothread.h"
> >  #include "qapi/error.h"
> >  #include "qapi/qapi-commands-misc.h"
> > +#include "qapi/clone-visitor.h"
> > +#include "qapi/qapi-visit-misc.h"
> >  #include "qemu/error-report.h"
> >  #include "qemu/rcu.h"
> >  #include "qemu/main-loop.h"
> >
> > +/*
> > + * iothread_ref:
> > + * @iothread: the iothread to track
> > + * @holder: the IOThreadHolder object initialized by the caller
> > + *
> > + * Add the @holder to the iothread's tracking list.
> > + */
>
> This is confusing.  What's tracking what?
>
> Here's my attempt:
>
>    /*
>     * Add a deep copy of @holder to @iothread's list of holders.
>     */
>
> If you really want to use "tracking list", you should define the term,
> say with a comment next to @holder in struct IOThread.
>
> > +void iothread_ref(IOThread *iothread, const IOThreadHolder *holder)
> > +{
> > +    assert(holder);
> > +
> > +    QAPI_LIST_PREPEND(iothread->holders, QAPI_CLONE(IOThreadHolder, holder));
> > +}
> > +
> > +static int iothread_holder_compare(const IOThreadHolder *holder_a,
> > +                                   const IOThreadHolder *holder_b)
> > +{
> > +    const char *name_a, *name_b;
> > +
> > +    if (holder_a->type != holder_b->type) {
> > +        return holder_b->type - holder_a->type;
> > +    }
> > +
> > +    switch (holder_a->type) {
> > +    case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
> > +        name_a = holder_a->u.qom_object.qom_path;
> > +        name_b = holder_b->u.qom_object.qom_path;
> > +        break;
> > +    case IO_THREAD_HOLDER_KIND_BLOCK_EXPORT:
> > +        name_a = holder_a->u.block_export.export_name;
> > +        name_b = holder_b->u.block_export.export_name;
> > +        break;
> > +    default:
> > +        g_assert_not_reached();
> > +    }
> > +
> > +    return strcmp(name_a, name_b);
> > +}
> > +
> > +/*
> > + * This function removes the @holder from the @iothread's tracking list.
>
> Imperative mood, please:
>
>     * Remove @holder from @iothread's list of holders.
>
> > + * The @holder must match the one used previously in iothread_ref().
>
> I believe the next sentence makes this one redundant.
>
> > + * It is a programming error to call this with a @holder that is not
> > + * currently associated with the @iothread.
> > + */
> > +void iothread_unref(IOThread *iothread, const IOThreadHolder *holder)
> > +{
> > +    IOThreadHolderList **prev = &iothread->holders;
> > +    IOThreadHolderList *curr;
> > +
> > +    assert(holder);
> > +
> > +    while (*prev) {
> > +        curr = *prev;
> > +        if (iothread_holder_compare(curr->value, holder) == 0) {
> > +            *prev = curr->next;
> > +            curr->next = NULL;
> > +            qapi_free_IOThreadHolderList(curr);
> > +            return;
> > +        }
> > +        prev = &curr->next;
> > +    }
> > +
> > +    g_assert_not_reached();
> > +}
> > +
> >  static void *iothread_run(void *opaque)
> >  {
> >      IOThread *iothread = opaque;
> > @@ -129,6 +197,7 @@ static void iothread_instance_finalize(Object *obj)
> >          iothread->main_loop = NULL;
> >      }
> >      qemu_sem_destroy(&iothread->init_done_sem);
> > +    qapi_free_IOThreadHolderList(iothread->holders);
> >  }
> >
> >  static void iothread_init_gcontext(IOThread *iothread, const char *thread_name)
> > @@ -373,6 +442,7 @@ static int query_one_iothread(Object *object, void *opaque)
> >      info = g_new0(IOThreadInfo, 1);
> >      info->id = iothread_get_id(iothread);
> >      info->thread_id = iothread->thread_id;
> > +    info->holders = QAPI_CLONE(IOThreadHolderList, iothread->holders);
> >      info->poll_max_ns = iothread->poll_max_ns;
> >      info->poll_grow = iothread->poll_grow;
> >      info->poll_shrink = iothread->poll_shrink;
> > diff --git a/qapi/misc.json b/qapi/misc.json
> > index c71a5fe657..096e418b7a 100644
> > --- a/qapi/misc.json
> > +++ b/qapi/misc.json
> > @@ -67,6 +67,56 @@
> >  ##
> >  { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
> >
> > +
> > +##
> > +# @IOThreadHolderBlockExport:
> > +#
> > +# @export-name: Name of the block export.
>
> What's a block export name?
>
> Is it BlockExportOptions member @id?
>
> Is it BlockExportOptions member @node-name?
>
> Something else?
>
> > +#
> > +# Since: 11.1
>
> By now 11.2.
>
> > +#
> > +##
> > +{ 'struct': 'IOThreadHolderBlockExport',
> > +  'data': { 'export-name': 'str' } }
> > +
> > +##
> > +# @IOThreadHolderQomObject:
> > +#
> > +# @qom-path: Path to the object in the QOM tree.
> > +#
> > +# Since: 11.1
> > +#
> > +##
> > +{ 'struct': 'IOThreadHolderQomObject',
> > +  'data': { 'qom-path': 'str' } }
> > +
> > +##
> > +# @IOThreadHolderKind:
> > +#
> > +# @block-export: A block export.
> > +# @qom-object: A QOM Object.
> > +#
> > +# Since: 11.1
> > +##
> > +{ 'enum': 'IOThreadHolderKind',
> > +  'data': [ 'block-export', 'qom-object' ] }
> > +
> > +##
> > +# @IOThreadHolder:
> > +#
> > +# The block export or QOM object holding the I/O thread.
> > +#
> > +# @type: the kind of I/O thread holder.
>
> We should use "iothread" for consistency (even though I/O thread is
> nicer).
>
> > +#
> > +# Since: 11.1
> > +##
> > +{ 'union': 'IOThreadHolder',
> > +  'base': { 'type': 'IOThreadHolderKind' },
> > +  'discriminator': 'type',
> > +  'data': {
> > +    'block-export': 'IOThreadHolderBlockExport',
> > +    'qom-object': 'IOThreadHolderQomObject' } }
> > +
> >  ##
> >  # @IOThreadInfo:
> >  #
> > @@ -76,6 +126,11 @@
> >  #
> >  # @thread-id: ID of the underlying host thread
> >  #
> > +# @holders: the QOM objects or block nodes currently
> > +#     associated with this iothread.  When an associated component is
> > +#     detached or destroyed, it is removed from this list.
> > +#     (Since 11.1)
>
> Wrap lines more nicely, please:
>
>    # @holders: the QOM objects or block nodes currently associated with
>    #     this iothread.  When an associated component is detached or
>    #     destroyed, it is removed from this list.  (Since 11.1)
>
>

Thanks. I will update here and the commit message to describe holders
and remove the implementation details.

The current block-export holder value is actually the block node name.
I will change it to BlockExportOptions.id and expose it as "export-id",
so the holder identifies the block export rather than its backing node.

And rethink for the patch 14, I will drop it and keep the original name
"iothread_ref_and_get_aio_context" to avoid misunderstanding.

Thanks

Chen

> > +#
> >  # @poll-max-ns: maximum polling time in ns, 0 means polling is
> >  #     disabled (since 2.9)
> >  #
> > @@ -98,6 +153,7 @@
> >  { 'struct': 'IOThreadInfo',
> >    'data': {'id': 'str',
> >             'thread-id': 'int',
> > +           'holders': ['IOThreadHolder'],
> >             'poll-max-ns': 'int',
> >             'poll-grow': 'int',
> >             'poll-shrink': 'int',
> > @@ -124,6 +180,8 @@
> >  #              {
> >  #                 "id":"iothread0",
> >  #                 "thread-id":3134,
> > +#                 "holders":[{"qom-path": "/machine/peripheral/blk1/virtio-backend", "type": "qom-object"},
> > +#                            {"qom-path": "/machine/peripheral/blk2/virtio-backend", "type": "qom-object"}],
> >  #                 "poll-max-ns":32768,
> >  #                 "poll-grow":0,
> >  #                 "poll-shrink":0,
> > @@ -132,6 +190,7 @@
> >  #              {
> >  #                 "id":"iothread1",
> >  #                 "thread-id":3135,
> > +#                 "holders":[{"export-name": "fmt_qcow2", "type": "block-export"}],
> >  #                 "poll-max-ns":32768,
> >  #                 "poll-grow":0,
> >  #                 "poll-shrink":0,
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.