* [PATCH V6 01/14] qapi/misc: Fix missed query-iothreads items
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 02/14] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
` (12 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen, qemu-stable
The example is incomplete: it misses members @poll-max-ns, @poll-grow,
@poll-shrink, @aio-max-batch. Messed up in commit 5fc00480ab1
(monitor: add poll-* properties into query-iothreads result) and
commit 1793ad0247c (iothread: add aio-max-batch parameter).
cc: qemu-stable@nongnu.org
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
qapi/misc.json | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/qapi/misc.json b/qapi/misc.json
index 28c641fe2f..1f5062df2a 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -117,11 +117,19 @@
# <- { "return": [
# {
# "id":"iothread0",
-# "thread-id":3134
+# "thread-id":3134,
+# "poll-max-ns":32768,
+# "poll-grow":0,
+# "poll-shrink":0,
+# "aio-max-batch":0
# },
# {
# "id":"iothread1",
-# "thread-id":3135
+# "thread-id":3135,
+# "poll-max-ns":32768,
+# "poll-grow":0,
+# "poll-shrink":0,
+# "aio-max-batch":0
# }
# ]
# }
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 02/14] iothread: introduce iothread_ref/unref to track attached devices
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
2026-04-10 15:04 ` [PATCH V6 01/14] qapi/misc: Fix missed query-iothreads items Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-21 14:15 ` Markus Armbruster
2026-04-10 15:04 ` [PATCH V6 03/14] iothread: tracking iothread users with holder name Zhang Chen
` (11 subsequent siblings)
13 siblings, 1 reply; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Currently, IOThreads do not maintain a record of which devices are
associated with them. 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 QOM path to a list.
- iothread_unref(): Searches for the device path using a custom
string comparison (g_strcmp0), releases the associated memory
upon a successful match.
- holders: A GList storing the QOM paths of attached devices
for runtime introspection.
This infrastructure allows management tools and QMP commands to
query the attachment status of IOThreads.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
include/system/iothread.h | 1 +
iothread.c | 56 +++++++++++++++++++++++++++++++++++++++
qapi/misc.json | 48 +++++++++++++++++++++++++++++++++
3 files changed, 105 insertions(+)
diff --git a/include/system/iothread.h b/include/system/iothread.h
index e26d13c6c7..21a76bd70d 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -33,6 +33,7 @@ struct IOThread {
bool stopping; /* has iothread_stop() been called? */
bool running; /* should iothread_run() continue? */
int thread_id;
+ GList *holders; /* an array of QOM paths for attached devices */
/* AioContext poll parameters */
int64_t poll_max_ns;
diff --git a/iothread.c b/iothread.c
index caf68e0764..c43266b191 100644
--- a/iothread.c
+++ b/iothread.c
@@ -36,6 +36,55 @@
#define IOTHREAD_POLL_MAX_NS_DEFAULT 0ULL
#endif
+/*
+ * Add holder device path to the list.
+ */
+static void iothread_ref(IOThread *iothread, const char *holder)
+{
+ IoThreadHolder *h = g_new0(IoThreadHolder, 1);
+
+ if (holder && holder[0] == '/') {
+ h->type = IO_THREAD_HOLDER_KIND_QOM_OBJECT;
+ h->u.qom_object.data = g_strdup(holder);
+ } else {
+ h->type = IO_THREAD_HOLDER_KIND_BLOCK_NODE;
+ h->u.block_node.data = g_strdup(holder ? holder : "unknown");
+ }
+
+ iothread->holders = g_list_prepend(iothread->holders, h);
+}
+
+static int iothread_holder_compare(gconstpointer a, gconstpointer b)
+{
+ const IoThreadHolder *holder_node = a;
+ const char *target_name = b;
+ const char *current_name = NULL;
+
+ if (holder_node->type == IO_THREAD_HOLDER_KIND_QOM_OBJECT) {
+ current_name = holder_node->u.qom_object.data;
+ } else if (holder_node->type == IO_THREAD_HOLDER_KIND_BLOCK_NODE) {
+ current_name = holder_node->u.block_node.data;
+ }
+
+ return g_strcmp0(current_name, target_name);
+}
+
+/*
+ * Delete holder device path from the list.
+ */
+static void iothread_unref(IOThread *iothread, const char *holder)
+{
+ GList *link = g_list_find_custom(iothread->holders, holder,
+ (GCompareFunc)iothread_holder_compare);
+
+ /* We don't support unref without a link */
+ assert(link);
+
+ IoThreadHolder *h = (IoThreadHolder *)link->data;
+ qapi_free_IoThreadHolder(h);
+ iothread->holders = g_list_delete_link(iothread->holders, link);
+}
+
static void *iothread_run(void *opaque)
{
IOThread *iothread = opaque;
@@ -115,6 +164,9 @@ static void iothread_instance_finalize(Object *obj)
iothread_stop(iothread);
+ /* We don't support finalize without holders */
+ assert(iothread->holders == NULL);
+
/*
* Before glib2 2.33.10, there is a glib2 bug that GSource context
* pointer may not be cleared even if the context has already been
@@ -336,6 +388,10 @@ char *iothread_get_id(IOThread *iothread)
AioContext *iothread_get_aio_context(IOThread *iothread)
{
+ /* Remove in next patch for build */
+ iothread_ref(iothread, "tmp");
+ iothread_unref(iothread, "tmp");
+
return iothread->ctx;
}
diff --git a/qapi/misc.json b/qapi/misc.json
index 1f5062df2a..d65d8012b2 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -67,6 +67,54 @@
##
{ 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
+
+##
+# @IoThreadHolderBlockNode:
+#
+# @data: Block node name.
+#
+# Since: 11.0
+#
+##
+{ 'struct': 'IoThreadHolderBlockNode',
+ 'data': { 'data': 'str' } }
+
+##
+# @IoThreadHolderQomObject:
+#
+# @data: QOM path.
+#
+# Since: 11.0
+#
+##
+{ 'struct': 'IoThreadHolderQomObject',
+ 'data': { 'data': 'str' } }
+
+##
+# @IoThreadHolderKind:
+#
+# @block-node: Block node name.
+# @qom-object: Standard QOM path.
+#
+# Since: 11.0
+##
+{ 'enum': 'IoThreadHolderKind',
+ 'data': [ 'block-node', 'qom-object' ] }
+
+##
+# @IoThreadHolder:
+#
+# @type: Current IoThread holder type support QOM path and Block node.
+#
+# Since: 11.0
+##
+{ 'union': 'IoThreadHolder',
+ 'base': { 'type': 'IoThreadHolderKind' },
+ 'discriminator': 'type',
+ 'data': {
+ 'block-node': 'IoThreadHolderBlockNode',
+ 'qom-object': 'IoThreadHolderQomObject' } }
+
##
# @IOThreadInfo:
#
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH V6 02/14] iothread: introduce iothread_ref/unref to track attached devices
2026-04-10 15:04 ` [PATCH V6 02/14] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
@ 2026-04-21 14:15 ` Markus Armbruster
2026-04-27 14:55 ` Zhang Chen
0 siblings, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2026-04-21 14:15 UTC (permalink / raw)
To: Zhang Chen
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
Zhang Chen <zhangckid@gmail.com> writes:
> Currently, IOThreads do not maintain a record of which devices are
> associated with them. 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 QOM path to a list.
> - iothread_unref(): Searches for the device path using a custom
> string comparison (g_strcmp0), releases the associated memory
> upon a successful match.
> - holders: A GList storing the QOM paths of attached devices
> for runtime introspection.
>
> This infrastructure allows management tools and QMP commands to
> query the attachment status of IOThreads.
Suggest something like
A later commit will add QMP commands to let management applications
query the attachment status of IOThreads.
>
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
> include/system/iothread.h | 1 +
> iothread.c | 56 +++++++++++++++++++++++++++++++++++++++
> qapi/misc.json | 48 +++++++++++++++++++++++++++++++++
> 3 files changed, 105 insertions(+)
>
> diff --git a/include/system/iothread.h b/include/system/iothread.h
> index e26d13c6c7..21a76bd70d 100644
> --- a/include/system/iothread.h
> +++ b/include/system/iothread.h
> @@ -33,6 +33,7 @@ struct IOThread {
> bool stopping; /* has iothread_stop() been called? */
> bool running; /* should iothread_run() continue? */
> int thread_id;
> + GList *holders; /* an array of QOM paths for attached devices */
The comment is mostly wrong. A GList is not an array. The list
elements are of type IoThreadHolder, which can represent either a QOM
path or a block node name. Suggest
GList *holders; /* list of IoThreadHolder */
>
> /* AioContext poll parameters */
> int64_t poll_max_ns;
> diff --git a/iothread.c b/iothread.c
> index caf68e0764..c43266b191 100644
> --- a/iothread.c
> +++ b/iothread.c
> @@ -36,6 +36,55 @@
> #define IOTHREAD_POLL_MAX_NS_DEFAULT 0ULL
> #endif
>
> +/*
> + * Add holder device path to the list.
Please explain semantics of parameter @holder here, it's not obvious
that @holder is a QOM path if it starts with '/', else a block node
name.
Restriction: only absolute QOM paths work. Relative ones get
misinterpreted as block node names.
> + */
> +static void iothread_ref(IOThread *iothread, const char *holder)
> +{
> + IoThreadHolder *h = g_new0(IoThreadHolder, 1);
> +
> + if (holder && holder[0] == '/') {
> + h->type = IO_THREAD_HOLDER_KIND_QOM_OBJECT;
> + h->u.qom_object.data = g_strdup(holder);
> + } else {
> + h->type = IO_THREAD_HOLDER_KIND_BLOCK_NODE;
> + h->u.block_node.data = g_strdup(holder ? holder : "unknown");
No. Nothing stops a user from naming a block node "unknown".
How can !holder happen? If it's not supposed to happen, assert() it
doesn't!
> + }
> +
> + iothread->holders = g_list_prepend(iothread->holders, h);
> +}
> +
> +static int iothread_holder_compare(gconstpointer a, gconstpointer b)
> +{
> + const IoThreadHolder *holder_node = a;
> + const char *target_name = b;
> + const char *current_name = NULL;
Superfluous initializer.
> +
> + if (holder_node->type == IO_THREAD_HOLDER_KIND_QOM_OBJECT) {
> + current_name = holder_node->u.qom_object.data;
> + } else if (holder_node->type == IO_THREAD_HOLDER_KIND_BLOCK_NODE) {
> + current_name = holder_node->u.block_node.data;
> + }
> +
> + return g_strcmp0(current_name, target_name);
Why g_strcmp0()? How can any of the arguments be null?
> +}
> +
> +/*
> + * Delete holder device path from the list.
Please explain semantics of @holder as for iothread_ref(), and state
that a it must be one of @io_thread's holders.
> + */
> +static void iothread_unref(IOThread *iothread, const char *holder)
> +{
> + GList *link = g_list_find_custom(iothread->holders, holder,
> + (GCompareFunc)iothread_holder_compare);
> +
> + /* We don't support unref without a link */
I doubt this comment is useful.
> + assert(link);
> +
> + IoThreadHolder *h = (IoThreadHolder *)link->data;
> + qapi_free_IoThreadHolder(h);
> + iothread->holders = g_list_delete_link(iothread->holders, link);
> +}
> +
> static void *iothread_run(void *opaque)
> {
> IOThread *iothread = opaque;
> @@ -115,6 +164,9 @@ static void iothread_instance_finalize(Object *obj)
>
> iothread_stop(iothread);
>
> + /* We don't support finalize without holders */
Why?
> + assert(iothread->holders == NULL);
> +
> /*
> * Before glib2 2.33.10, there is a glib2 bug that GSource context
> * pointer may not be cleared even if the context has already been
> @@ -336,6 +388,10 @@ char *iothread_get_id(IOThread *iothread)
>
> AioContext *iothread_get_aio_context(IOThread *iothread)
> {
> + /* Remove in next patch for build */
> + iothread_ref(iothread, "tmp");
> + iothread_unref(iothread, "tmp");
> +
> return iothread->ctx;
> }
>
> diff --git a/qapi/misc.json b/qapi/misc.json
> index 1f5062df2a..d65d8012b2 100644
> --- a/qapi/misc.json
> +++ b/qapi/misc.json
> @@ -67,6 +67,54 @@
> ##
> { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
>
> +
> +##
> +# @IoThreadHolderBlockNode:
> +#
> +# @data: Block node name.
These are commonly named @node-name.
> +#
> +# Since: 11.0
> +#
> +##
> +{ 'struct': 'IoThreadHolderBlockNode',
> + 'data': { 'data': 'str' } }
> +
> +##
> +# @IoThreadHolderQomObject:
> +#
> +# @data: QOM path.
These are commonly named @qom-path.
Should we use "Absolute QOM path" here? Relative ones don't actually
work.
> +#
> +# Since: 11.0
> +#
> +##
> +{ 'struct': 'IoThreadHolderQomObject',
> + 'data': { 'data': 'str' } }
> +
> +##
> +# @IoThreadHolderKind:
> +#
> +# @block-node: Block node name.
The block node name is not the holder, the block node is.
> +# @qom-object: Standard QOM path.
What do you mean by "standard"?
> +#
> +# Since: 11.0
> +##
> +{ 'enum': 'IoThreadHolderKind',
> + 'data': [ 'block-node', 'qom-object' ] }
> +
> +##
> +# @IoThreadHolder:
> +#
> +# @type: Current IoThread holder type support QOM path and Block node.
Awkward English :)
# @type: the kind of I/O thread holder
isn't exactly great, either. But I'm out of time for today.
> +#
> +# Since: 11.0
> +##
> +{ 'union': 'IoThreadHolder',
> + 'base': { 'type': 'IoThreadHolderKind' },
> + 'discriminator': 'type',
> + 'data': {
> + 'block-node': 'IoThreadHolderBlockNode',
> + 'qom-object': 'IoThreadHolderQomObject' } }
> +
> ##
> # @IOThreadInfo:
> #
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH V6 02/14] iothread: introduce iothread_ref/unref to track attached devices
2026-04-21 14:15 ` Markus Armbruster
@ 2026-04-27 14:55 ` Zhang Chen
0 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-27 14:55 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
On Tue, Apr 21, 2026 at 10:15 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Currently, IOThreads do not maintain a record of which devices are
> > associated with them. 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 QOM path to a list.
> > - iothread_unref(): Searches for the device path using a custom
> > string comparison (g_strcmp0), releases the associated memory
> > upon a successful match.
> > - holders: A GList storing the QOM paths of attached devices
> > for runtime introspection.
> >
> > This infrastructure allows management tools and QMP commands to
> > query the attachment status of IOThreads.
>
> Suggest something like
>
> A later commit will add QMP commands to let management applications
> query the attachment status of IOThreads.
Sorry for the late reply, I originally intended to wait for stefan's
feedback so I could reply to everything at once.
For the comments, it looks better, will replace it in the next version.
>
> >
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> > ---
> > include/system/iothread.h | 1 +
> > iothread.c | 56 +++++++++++++++++++++++++++++++++++++++
> > qapi/misc.json | 48 +++++++++++++++++++++++++++++++++
> > 3 files changed, 105 insertions(+)
> >
> > diff --git a/include/system/iothread.h b/include/system/iothread.h
> > index e26d13c6c7..21a76bd70d 100644
> > --- a/include/system/iothread.h
> > +++ b/include/system/iothread.h
> > @@ -33,6 +33,7 @@ struct IOThread {
> > bool stopping; /* has iothread_stop() been called? */
> > bool running; /* should iothread_run() continue? */
> > int thread_id;
> > + GList *holders; /* an array of QOM paths for attached devices */
>
> The comment is mostly wrong. A GList is not an array. The list
> elements are of type IoThreadHolder, which can represent either a QOM
> path or a block node name. Suggest
Good catch, forgot to update it after we defined the type IoThreadHolder.
>
> GList *holders; /* list of IoThreadHolder */
>
> >
> > /* AioContext poll parameters */
> > int64_t poll_max_ns;
> > diff --git a/iothread.c b/iothread.c
> > index caf68e0764..c43266b191 100644
> > --- a/iothread.c
> > +++ b/iothread.c
> > @@ -36,6 +36,55 @@
> > #define IOTHREAD_POLL_MAX_NS_DEFAULT 0ULL
> > #endif
> >
> > +/*
> > + * Add holder device path to the list.
>
> Please explain semantics of parameter @holder here, it's not obvious
> that @holder is a QOM path if it starts with '/', else a block node
> name.
>
> Restriction: only absolute QOM paths work. Relative ones get
> misinterpreted as block node names.
OK.
>
> > + */
> > +static void iothread_ref(IOThread *iothread, const char *holder)
> > +{
> > + IoThreadHolder *h = g_new0(IoThreadHolder, 1);
> > +
> > + if (holder && holder[0] == '/') {
> > + h->type = IO_THREAD_HOLDER_KIND_QOM_OBJECT;
> > + h->u.qom_object.data = g_strdup(holder);
> > + } else {
> > + h->type = IO_THREAD_HOLDER_KIND_BLOCK_NODE;
> > + h->u.block_node.data = g_strdup(holder ? holder : "unknown");
>
> No. Nothing stops a user from naming a block node "unknown".
>
> How can !holder happen? If it's not supposed to happen, assert() it
> doesn't!
OK.
>
> > + }
> > +
> > + iothread->holders = g_list_prepend(iothread->holders, h);
> > +}
> > +
> > +static int iothread_holder_compare(gconstpointer a, gconstpointer b)
> > +{
> > + const IoThreadHolder *holder_node = a;
> > + const char *target_name = b;
> > + const char *current_name = NULL;
>
> Superfluous initializer.
OK, will remove it.
>
> > +
> > + if (holder_node->type == IO_THREAD_HOLDER_KIND_QOM_OBJECT) {
> > + current_name = holder_node->u.qom_object.data;
> > + } else if (holder_node->type == IO_THREAD_HOLDER_KIND_BLOCK_NODE) {
> > + current_name = holder_node->u.block_node.data;
> > + }
> > +
> > + return g_strcmp0(current_name, target_name);
>
> Why g_strcmp0()? How can any of the arguments be null?
Using g_strcmp0 provides a defensive check against cases
where a holder name might be uninitialized or the holder type
is unrecognized, avoiding a potential NULL pointer dereference.
>
> > +}
> > +
> > +/*
> > + * Delete holder device path from the list.
>
> Please explain semantics of @holder as for iothread_ref(), and state
> that a it must be one of @io_thread's holders.
OK.
>
> > + */
> > +static void iothread_unref(IOThread *iothread, const char *holder)
> > +{
> > + GList *link = g_list_find_custom(iothread->holders, holder,
> > + (GCompareFunc)iothread_holder_compare);
> > +
> > + /* We don't support unref without a link */
>
> I doubt this comment is useful.
Will remove it.
>
> > + assert(link);
> > +
> > + IoThreadHolder *h = (IoThreadHolder *)link->data;
> > + qapi_free_IoThreadHolder(h);
> > + iothread->holders = g_list_delete_link(iothread->holders, link);
> > +}
> > +
> > static void *iothread_run(void *opaque)
> > {
> > IOThread *iothread = opaque;
> > @@ -115,6 +164,9 @@ static void iothread_instance_finalize(Object *obj)
> >
> > iothread_stop(iothread);
> >
> > + /* We don't support finalize without holders */
>
> Why?
If I understand correctly, Stefan's design is to manage the iothread
lifecycle using paired ref/unref.
The iothread ref will give a holder for the iothread. So, any using
iothread must have a holder here.
Another scenario is that an iothread was added but was not assigned to
any device.
In this case, yes, no need assert the holders here.
I will remove it next version.
>
> > + assert(iothread->holders == NULL);
> > +
> > /*
> > * Before glib2 2.33.10, there is a glib2 bug that GSource context
> > * pointer may not be cleared even if the context has already been
> > @@ -336,6 +388,10 @@ char *iothread_get_id(IOThread *iothread)
> >
> > AioContext *iothread_get_aio_context(IOThread *iothread)
> > {
> > + /* Remove in next patch for build */
> > + iothread_ref(iothread, "tmp");
> > + iothread_unref(iothread, "tmp");
> > +
> > return iothread->ctx;
> > }
> >
> > diff --git a/qapi/misc.json b/qapi/misc.json
> > index 1f5062df2a..d65d8012b2 100644
> > --- a/qapi/misc.json
> > +++ b/qapi/misc.json
> > @@ -67,6 +67,54 @@
> > ##
> > { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
> >
> > +
> > +##
> > +# @IoThreadHolderBlockNode:
> > +#
> > +# @data: Block node name.
>
> These are commonly named @node-name.
OK.
>
> > +#
> > +# Since: 11.0
> > +#
> > +##
> > +{ 'struct': 'IoThreadHolderBlockNode',
> > + 'data': { 'data': 'str' } }
> > +
> > +##
> > +# @IoThreadHolderQomObject:
> > +#
> > +# @data: QOM path.
>
> These are commonly named @qom-path.
>
> Should we use "Absolute QOM path" here? Relative ones don't actually
> work.
Yes, good idea.
>
> > +#
> > +# Since: 11.0
> > +#
> > +##
> > +{ 'struct': 'IoThreadHolderQomObject',
> > + 'data': { 'data': 'str' } }
> > +
> > +##
> > +# @IoThreadHolderKind:
> > +#
> > +# @block-node: Block node name.
>
> The block node name is not the holder, the block node is.
>
> > +# @qom-object: Standard QOM path.
>
> What do you mean by "standard"?
I will change it to "Absolute QOM path".
>
> > +#
> > +# Since: 11.0
> > +##
> > +{ 'enum': 'IoThreadHolderKind',
> > + 'data': [ 'block-node', 'qom-object' ] }
> > +
> > +##
> > +# @IoThreadHolder:
> > +#
> > +# @type: Current IoThread holder type support QOM path and Block node.
>
> Awkward English :)
>
> # @type: the kind of I/O thread holder
>
> isn't exactly great, either. But I'm out of time for today.
OK.
Thanks
chen
>
> > +#
> > +# Since: 11.0
> > +##
> > +{ 'union': 'IoThreadHolder',
> > + 'base': { 'type': 'IoThreadHolderKind' },
> > + 'discriminator': 'type',
> > + 'data': {
> > + 'block-node': 'IoThreadHolderBlockNode',
> > + 'qom-object': 'IoThreadHolderQomObject' } }
> > +
> > ##
> > # @IOThreadInfo:
> > #
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH V6 03/14] iothread: tracking iothread users with holder name
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
2026-04-10 15:04 ` [PATCH V6 01/14] qapi/misc: Fix missed query-iothreads items Zhang Chen
2026-04-10 15:04 ` [PATCH V6 02/14] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 04/14] blockdev: Update " Zhang Chen
` (10 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Introduce iothread_get_aio_context() with a 'holder' argument and its
counterpart iothread_put_aio_context().
Previously, users of an IOThread's AioContext did not explicitly
record their identity, making it difficult to debug which devices or
subsystems were pinning an IOThread.
This patch enhances the reference counting mechanism by:
1. Automatically incrementing the object reference count when a context
is retrieved.
2. Tracking holders by name using iothread_ref() and iothread_unref().
In iothread_instance_finalize(), we now retrieve the source name from
the GMainContext to correctly unref the initial internal holder.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
include/system/iothread.h | 3 +++
iothread.c | 33 +++++++++++++++++++++++++++++----
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/include/system/iothread.h b/include/system/iothread.h
index 21a76bd70d..dbada8249c 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -48,6 +48,9 @@ DECLARE_INSTANCE_CHECKER(IOThread, IOTHREAD,
char *iothread_get_id(IOThread *iothread);
IOThread *iothread_by_id(const char *id);
AioContext *iothread_get_aio_context(IOThread *iothread);
+AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
+ const char *holder);
+void iothread_put_aio_context(IOThread *iothread, const char *holder);
GMainContext *iothread_get_g_main_context(IOThread *iothread);
/*
diff --git a/iothread.c b/iothread.c
index c43266b191..60a024f770 100644
--- a/iothread.c
+++ b/iothread.c
@@ -52,6 +52,12 @@ static void iothread_ref(IOThread *iothread, const char *holder)
}
iothread->holders = g_list_prepend(iothread->holders, h);
+
+ /*
+ * This guarantees that the IOThread and its AioContext remain alive
+ * as long as there is a holder.
+ */
+ object_ref(OBJECT(iothread));
}
static int iothread_holder_compare(gconstpointer a, gconstpointer b)
@@ -83,6 +89,8 @@ static void iothread_unref(IOThread *iothread, const char *holder)
IoThreadHolder *h = (IoThreadHolder *)link->data;
qapi_free_IoThreadHolder(h);
iothread->holders = g_list_delete_link(iothread->holders, link);
+
+ object_unref(OBJECT(iothread));
}
static void *iothread_run(void *opaque)
@@ -196,7 +204,7 @@ static void iothread_init_gcontext(IOThread *iothread, const char *thread_name)
g_autofree char *name = g_strdup_printf("%s aio-context", thread_name);
iothread->worker_context = g_main_context_new();
- source = aio_get_g_source(iothread_get_aio_context(iothread));
+ source = aio_get_g_source(iothread->ctx);
g_source_set_name(source, name);
g_source_attach(source, iothread->worker_context);
g_source_unref(source);
@@ -388,13 +396,30 @@ char *iothread_get_id(IOThread *iothread)
AioContext *iothread_get_aio_context(IOThread *iothread)
{
- /* Remove in next patch for build */
- iothread_ref(iothread, "tmp");
- iothread_unref(iothread, "tmp");
+ return iothread->ctx;
+}
+
+AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
+ const char *holder)
+{
+ /*
+ * In some cases, iothread user need the ctx to clearup other resource.
+ * When holder is empty, back to the legacy way.
+ */
+ if (holder) {
+ /* Add holder device path to the list */
+ iothread_ref(iothread, holder);
+ }
return iothread->ctx;
}
+void iothread_put_aio_context(IOThread *iothread, const char *holder)
+{
+ /* Delete holder device path from the list */
+ iothread_unref(iothread, holder);
+}
+
static int query_one_iothread(Object *object, void *opaque)
{
IOThreadInfoList ***tail = opaque;
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 04/14] blockdev: Update tracking iothread users with holder name
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (2 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 03/14] iothread: tracking iothread users with holder name Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 05/14] block/export: track IOThread reference in BlockExport Zhang Chen
` (9 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Currently, x-blockdev-set-iothread changes the AioContext but does not
track the IOThread object itself within the block layer. This makes it
difficult to manage IOThread reference counting (put/get) during
dynamic context switching.
Introduce an 'iothread' field to BlockDriverState to store the current
assigned IOThread. Update qmp_x_blockdev_set_iothread to perform
proper reference counting using iothread_ref/put when moving nodes
between an IOThread and the main loop.
This ensures 'info iothreads' (holders) accurately reflects the block
device attachment state after dynamic migrations.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
blockdev.c | 6 +++++-
include/block/block_int-common.h | 5 +++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/blockdev.c b/blockdev.c
index 6e86c6262f..6e20579187 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3683,8 +3683,12 @@ void qmp_x_blockdev_set_iothread(const char *node_name, StrOrNull *iothread,
goto out;
}
- new_context = iothread_get_aio_context(obj);
+ new_context = iothread_ref_and_get_aio_context(obj, node_name);
+ bs->iothread = obj;
} else {
+ if (bs->iothread) {
+ iothread_put_aio_context(bs->iothread, node_name);
+ }
new_context = qemu_get_aio_context();
}
diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
index 147c08155f..6edaf53377 100644
--- a/include/block/block_int-common.h
+++ b/include/block/block_int-common.h
@@ -1101,6 +1101,8 @@ typedef struct BdrvBlockStatusCache {
int64_t data_end;
} BdrvBlockStatusCache;
+typedef struct IOThread IOThread;
+
struct BlockDriverState {
/*
* Protected by big QEMU lock or read-only after opening. No special
@@ -1289,6 +1291,9 @@ struct BlockDriverState {
/* array of write pointers' location of each zone in the zoned device. */
BlockZoneWps *wps;
+
+ /* Track the iothread for detach aio context*/
+ IOThread *iothread;
};
struct BlockBackendRootState {
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 05/14] block/export: track IOThread reference in BlockExport
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (3 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 04/14] blockdev: Update " Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 06/14] monitor: Update tracking iothread users with holder name Zhang Chen
` (8 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Users currently lack visibility into which block exports
are utilizing specific IOThreads. This patch integrates IOThread
referencing into the BlockExport lifecycle.
- Add iothreads array and holder_name to BlockExport struct.
- Use iothread_ref_and_get_aio_context during export creation.
- Implement proper cleanup in blk_exp_add fail path and blk_exp_delete_bh.
- Support both single and multi-iothread export configurations.
This ensures IOThread 'holders' status correctly reflects active block
exports for better debugging and resource tracking.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
block/export/export.c | 44 +++++++++++++++++++++++++++++++++---------
include/block/export.h | 6 ++++++
2 files changed, 41 insertions(+), 9 deletions(-)
diff --git a/block/export/export.c b/block/export/export.c
index b733f269f3..636633c324 100644
--- a/block/export/export.c
+++ b/block/export/export.c
@@ -15,7 +15,6 @@
#include "block/block.h"
#include "system/block-backend.h"
-#include "system/iothread.h"
#include "block/export.h"
#include "block/fuse.h"
#include "block/nbd.h"
@@ -85,6 +84,8 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
AioContext *ctx;
AioContext **multithread_ctxs = NULL;
size_t multithread_count = 0;
+ g_autofree IOThread **local_iothreads = NULL;
+ const char *holder_name = NULL;
uint64_t perm;
int ret;
@@ -139,7 +140,11 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
goto fail;
}
- new_ctx = iothread_get_aio_context(iothread);
+ holder_name = bdrv_get_node_name(bs);
+ new_ctx = iothread_ref_and_get_aio_context(iothread, holder_name);
+ multithread_count = 1;
+ local_iothreads = g_new0(IOThread *, 1);
+ local_iothreads[0] = iothread;
/* Ignore errors with fixed-iothread=false */
set_context_errp = fixed_iothread ? errp : NULL;
@@ -163,8 +168,10 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
return NULL;
}
+ local_iothreads = g_new0(IOThread *, multithread_count);
multithread_ctxs = g_new(AioContext *, multithread_count);
i = 0;
+ holder_name = bdrv_get_node_name(bs);
for (strList *e = iothread_list; e; e = e->next) {
IOThread *iothread = iothread_by_id(e->value);
@@ -172,7 +179,9 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
error_setg(errp, "iothread \"%s\" not found", e->value);
goto fail;
}
- multithread_ctxs[i++] = iothread_get_aio_context(iothread);
+ local_iothreads[i] = iothread;
+ multithread_ctxs[i++] = iothread_ref_and_get_aio_context(iothread,
+ holder_name);
}
assert(i == multithread_count);
}
@@ -225,12 +234,15 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
assert(drv->instance_size >= sizeof(BlockExport));
exp = g_malloc0(drv->instance_size);
*exp = (BlockExport) {
- .drv = drv,
- .refcount = 1,
- .user_owned = true,
- .id = g_strdup(export->id),
- .ctx = ctx,
- .blk = blk,
+ .drv = drv,
+ .refcount = 1,
+ .user_owned = true,
+ .id = g_strdup(export->id),
+ .ctx = ctx,
+ .blk = blk,
+ .iothreads = g_steal_pointer(&local_iothreads),
+ .iothread_count = multithread_count,
+ .iothread_holder_name = g_strdup(holder_name),
};
ret = drv->create(exp, export, multithread_ctxs, multithread_count, errp);
@@ -253,6 +265,13 @@ fail:
g_free(exp->id);
g_free(exp);
}
+ if (local_iothreads) {
+ for (size_t j = 0; j < multithread_count; j++) {
+ if (local_iothreads[j]) {
+ iothread_put_aio_context(local_iothreads[j], holder_name);
+ }
+ }
+ }
g_free(multithread_ctxs);
return NULL;
}
@@ -269,6 +288,13 @@ static void blk_exp_delete_bh(void *opaque)
BlockExport *exp = opaque;
assert(exp->refcount == 0);
+ if (exp->iothreads) {
+ for (size_t i = 0; i < exp->iothread_count; i++) {
+ iothread_put_aio_context(exp->iothreads[i],
+ exp->iothread_holder_name);
+ }
+ g_free(exp->iothreads);
+ }
QLIST_REMOVE(exp, next);
exp->drv->delete(exp);
blk_set_dev_ops(exp->blk, NULL, NULL);
diff --git a/include/block/export.h b/include/block/export.h
index ca45da928c..2bb98aae31 100644
--- a/include/block/export.h
+++ b/include/block/export.h
@@ -16,6 +16,7 @@
#include "qapi/qapi-types-block-export.h"
#include "qemu/queue.h"
+#include "system/iothread.h"
typedef struct BlockExport BlockExport;
@@ -89,6 +90,11 @@ struct BlockExport {
/* List entry for block_exports */
QLIST_ENTRY(BlockExport) next;
+
+ /* The iothreads list for block_exports */
+ IOThread **iothreads;
+ size_t iothread_count;
+ char *iothread_holder_name;
};
BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 06/14] monitor: Update tracking iothread users with holder name
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (4 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 05/14] block/export: track IOThread reference in BlockExport Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-23 12:44 ` Markus Armbruster
2026-04-10 15:04 ` [PATCH V6 07/14] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
` (7 subsequent siblings)
13 siblings, 1 reply; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Since the Monitor struct is not a QOM Object, we cannot use
object_get_canonical_path(). Instead, this patch uses the monitor's
name (or a default type-based name) as the holder identifier.
Cache the AioContext in the Monitor struct to avoid repeated calls to
iothread_get_aio_context() and ensure symmetrical ref/unref during
monitor lifecycle.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
monitor/monitor-internal.h | 3 +++
monitor/monitor.c | 24 ++++++++++++++++++++++--
monitor/qmp.c | 3 ++-
3 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index feca111ae3..51cedf90e4 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -116,6 +116,9 @@ struct Monitor {
guint out_watch;
int mux_out;
int reset_seen;
+
+ /* iothread context */
+ AioContext *ctx;
};
struct MonitorHMP {
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 00b93ed612..b6efe776d6 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -529,7 +529,7 @@ int monitor_suspend(Monitor *mon)
* Kick I/O thread to make sure this takes effect. It'll be
* evaluated again in prepare() of the watch object.
*/
- aio_notify(iothread_get_aio_context(mon_iothread));
+ aio_notify(mon->ctx);
}
trace_monitor_suspend(mon, 1);
@@ -564,7 +564,7 @@ void monitor_resume(Monitor *mon)
AioContext *ctx;
if (mon->use_io_thread) {
- ctx = iothread_get_aio_context(mon_iothread);
+ ctx = mon->ctx;
} else {
ctx = qemu_get_aio_context();
}
@@ -612,6 +612,18 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
{
if (use_io_thread && !mon_iothread) {
monitor_iothread_init();
+ /*
+ * Because of current Monitor is not a QOM Object,
+ * so using OBJECT(mon) is undefined behavior and may crash.
+ * Try using a hard-coded future implementation of the qom path instead.
+ * (Like the name of the "mon_iothread").
+ * long-term solution would be making Monitor QOM, after that change
+ * here to:
+ * g_autofree path = object_get_canonical_path(OBJECT(mon));
+ */
+ g_autofree char *path = g_strdup(is_qmp ? "/monitor/qmp_mon0" :
+ "/monitor/hmp_mon0");
+ mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, path);
}
qemu_mutex_init(&mon->mon_lock);
mon->is_qmp = is_qmp;
@@ -631,6 +643,14 @@ void monitor_data_destroy(Monitor *mon)
}
g_string_free(mon->outbuf, true);
qemu_mutex_destroy(&mon->mon_lock);
+
+ if (mon->ctx && mon_iothread) {
+ g_autofree char *path = g_strdup(monitor_is_qmp(mon) ?
+ "/monitor/qmp_mon0" :
+ "/monitor/hmp_mon0");
+ iothread_put_aio_context(mon_iothread, path);
+ mon->ctx = NULL;
+ }
}
void monitor_cleanup(void)
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 687019811f..8e4a775fac 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -549,7 +549,8 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
* since chardev might be running in the monitor I/O
* thread. Schedule a bottom half.
*/
- aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
+ Monitor *mon_p = &mon->common;
+ aio_bh_schedule_oneshot(mon_p->ctx,
monitor_qmp_setup_handlers_bh, mon);
/* The bottom half will add @mon to @mon_list */
} else {
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH V6 06/14] monitor: Update tracking iothread users with holder name
2026-04-10 15:04 ` [PATCH V6 06/14] monitor: Update tracking iothread users with holder name Zhang Chen
@ 2026-04-23 12:44 ` Markus Armbruster
2026-04-27 14:55 ` Zhang Chen
0 siblings, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2026-04-23 12:44 UTC (permalink / raw)
To: Zhang Chen
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
Zhang Chen <zhangckid@gmail.com> writes:
> Since the Monitor struct is not a QOM Object, we cannot use
> object_get_canonical_path(). Instead, this patch uses the monitor's
> name (or a default type-based name) as the holder identifier.
>
> Cache the AioContext in the Monitor struct to avoid repeated calls to
> iothread_get_aio_context() and ensure symmetrical ref/unref during
> monitor lifecycle.
>
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
> monitor/monitor-internal.h | 3 +++
> monitor/monitor.c | 24 ++++++++++++++++++++++--
> monitor/qmp.c | 3 ++-
> 3 files changed, 27 insertions(+), 3 deletions(-)
>
> diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> index feca111ae3..51cedf90e4 100644
> --- a/monitor/monitor-internal.h
> +++ b/monitor/monitor-internal.h
> @@ -116,6 +116,9 @@ struct Monitor {
> guint out_watch;
> int mux_out;
> int reset_seen;
> +
> + /* iothread context */
> + AioContext *ctx;
> };
>
> struct MonitorHMP {
> diff --git a/monitor/monitor.c b/monitor/monitor.c
> index 00b93ed612..b6efe776d6 100644
> --- a/monitor/monitor.c
> +++ b/monitor/monitor.c
> @@ -529,7 +529,7 @@ int monitor_suspend(Monitor *mon)
> * Kick I/O thread to make sure this takes effect. It'll be
> * evaluated again in prepare() of the watch object.
> */
> - aio_notify(iothread_get_aio_context(mon_iothread));
> + aio_notify(mon->ctx);
> }
>
> trace_monitor_suspend(mon, 1);
> @@ -564,7 +564,7 @@ void monitor_resume(Monitor *mon)
> AioContext *ctx;
>
> if (mon->use_io_thread) {
> - ctx = iothread_get_aio_context(mon_iothread);
> + ctx = mon->ctx;
> } else {
> ctx = qemu_get_aio_context();
> }
> @@ -612,6 +612,18 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
> {
> if (use_io_thread && !mon_iothread) {
> monitor_iothread_init();
> + /*
> + * Because of current Monitor is not a QOM Object,
> + * so using OBJECT(mon) is undefined behavior and may crash.
> + * Try using a hard-coded future implementation of the qom path instead.
> + * (Like the name of the "mon_iothread").
> + * long-term solution would be making Monitor QOM, after that change
> + * here to:
> + * g_autofree path = object_get_canonical_path(OBJECT(mon));
> + */
> + g_autofree char *path = g_strdup(is_qmp ? "/monitor/qmp_mon0" :
> + "/monitor/hmp_mon0");
> + mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, path);
Oh.
PATCH 02 defines QAPI type IoThreadHolder as a union with two branches
for the two kinds of holders: one for block node holder, and one for QOM
object holder. Both branches use a string to identify the holder.
The C code in PATCH 02 uses just a string to identify the holder. This
silently assumes that the strings for block nodes are distinct from the
strings for QOM objects. True as long as we only use absolute QOM
paths: these start with '/', and block nodes can't.
This patch shows me there's actually a third kind: monitor. You use the
QOM object branch for it. That's confusing; a monitor is not a QOM
object. Moreover, you make yet another silent assumption: absolute QOM
paths do not start with "/monitor/".
This is too much for me.
Please use a separate IoThreadHolderKind value and IoThreadHolder branch
for each kind of holder. The kinds I've seen so far are block-node, QOM
object, monitor.
If Daniel Berrangé's "[PATCH RFC 00/17] monitor: turn QMP and HMP into
QOM objects" gets merged, kind monitor can go away.
Use of just a string to identify the holder requires strings for
different kinds to be distinct. The argument why they are must be
written down, simple, and likely to remain true.
But I'd prefer to use IoThreadHolder instead of string. No assumptions
necessary then.
If this IoThreadHolder arguments make the calls ugly or overly verbose,
consider thin wrappers for each kind, i.e.
new_ctx = iothread_get_aio_context_block(iothread, bs);
instead of
holder_name = bdrv_get_node_name(bs);
new_ctx = iothread_get_aio_context(iothread, holder_name);
and so forth.
Questions?
> }
> qemu_mutex_init(&mon->mon_lock);
> mon->is_qmp = is_qmp;
> @@ -631,6 +643,14 @@ void monitor_data_destroy(Monitor *mon)
> }
> g_string_free(mon->outbuf, true);
> qemu_mutex_destroy(&mon->mon_lock);
> +
> + if (mon->ctx && mon_iothread) {
> + g_autofree char *path = g_strdup(monitor_is_qmp(mon) ?
> + "/monitor/qmp_mon0" :
> + "/monitor/hmp_mon0");
> + iothread_put_aio_context(mon_iothread, path);
> + mon->ctx = NULL;
> + }
> }
>
> void monitor_cleanup(void)
> diff --git a/monitor/qmp.c b/monitor/qmp.c
> index 687019811f..8e4a775fac 100644
> --- a/monitor/qmp.c
> +++ b/monitor/qmp.c
> @@ -549,7 +549,8 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
> * since chardev might be running in the monitor I/O
> * thread. Schedule a bottom half.
> */
> - aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
> + Monitor *mon_p = &mon->common;
> + aio_bh_schedule_oneshot(mon_p->ctx,
> monitor_qmp_setup_handlers_bh, mon);
> /* The bottom half will add @mon to @mon_list */
> } else {
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH V6 06/14] monitor: Update tracking iothread users with holder name
2026-04-23 12:44 ` Markus Armbruster
@ 2026-04-27 14:55 ` Zhang Chen
2026-04-28 7:26 ` Markus Armbruster
0 siblings, 1 reply; 23+ messages in thread
From: Zhang Chen @ 2026-04-27 14:55 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
On Thu, Apr 23, 2026 at 8:44 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Since the Monitor struct is not a QOM Object, we cannot use
> > object_get_canonical_path(). Instead, this patch uses the monitor's
> > name (or a default type-based name) as the holder identifier.
> >
> > Cache the AioContext in the Monitor struct to avoid repeated calls to
> > iothread_get_aio_context() and ensure symmetrical ref/unref during
> > monitor lifecycle.
> >
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> > ---
> > monitor/monitor-internal.h | 3 +++
> > monitor/monitor.c | 24 ++++++++++++++++++++++--
> > monitor/qmp.c | 3 ++-
> > 3 files changed, 27 insertions(+), 3 deletions(-)
> >
> > diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> > index feca111ae3..51cedf90e4 100644
> > --- a/monitor/monitor-internal.h
> > +++ b/monitor/monitor-internal.h
> > @@ -116,6 +116,9 @@ struct Monitor {
> > guint out_watch;
> > int mux_out;
> > int reset_seen;
> > +
> > + /* iothread context */
> > + AioContext *ctx;
> > };
> >
> > struct MonitorHMP {
> > diff --git a/monitor/monitor.c b/monitor/monitor.c
> > index 00b93ed612..b6efe776d6 100644
> > --- a/monitor/monitor.c
> > +++ b/monitor/monitor.c
> > @@ -529,7 +529,7 @@ int monitor_suspend(Monitor *mon)
> > * Kick I/O thread to make sure this takes effect. It'll be
> > * evaluated again in prepare() of the watch object.
> > */
> > - aio_notify(iothread_get_aio_context(mon_iothread));
> > + aio_notify(mon->ctx);
> > }
> >
> > trace_monitor_suspend(mon, 1);
> > @@ -564,7 +564,7 @@ void monitor_resume(Monitor *mon)
> > AioContext *ctx;
> >
> > if (mon->use_io_thread) {
> > - ctx = iothread_get_aio_context(mon_iothread);
> > + ctx = mon->ctx;
> > } else {
> > ctx = qemu_get_aio_context();
> > }
> > @@ -612,6 +612,18 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
> > {
> > if (use_io_thread && !mon_iothread) {
> > monitor_iothread_init();
> > + /*
> > + * Because of current Monitor is not a QOM Object,
> > + * so using OBJECT(mon) is undefined behavior and may crash.
> > + * Try using a hard-coded future implementation of the qom path instead.
> > + * (Like the name of the "mon_iothread").
> > + * long-term solution would be making Monitor QOM, after that change
> > + * here to:
> > + * g_autofree path = object_get_canonical_path(OBJECT(mon));
> > + */
> > + g_autofree char *path = g_strdup(is_qmp ? "/monitor/qmp_mon0" :
> > + "/monitor/hmp_mon0");
> > + mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, path);
>
> Oh.
>
> PATCH 02 defines QAPI type IoThreadHolder as a union with two branches
> for the two kinds of holders: one for block node holder, and one for QOM
> object holder. Both branches use a string to identify the holder.
>
> The C code in PATCH 02 uses just a string to identify the holder. This
> silently assumes that the strings for block nodes are distinct from the
> strings for QOM objects. True as long as we only use absolute QOM
> paths: these start with '/', and block nodes can't.
>
> This patch shows me there's actually a third kind: monitor. You use the
> QOM object branch for it. That's confusing; a monitor is not a QOM
> object. Moreover, you make yet another silent assumption: absolute QOM
> paths do not start with "/monitor/".
>
> This is too much for me.
>
> Please use a separate IoThreadHolderKind value and IoThreadHolder branch
> for each kind of holder. The kinds I've seen so far are block-node, QOM
> object, monitor.
>
> If Daniel Berrangé's "[PATCH RFC 00/17] monitor: turn QMP and HMP into
> QOM objects" gets merged, kind monitor can go away.
>
> Use of just a string to identify the holder requires strings for
> different kinds to be distinct. The argument why they are must be
> written down, simple, and likely to remain true.
>
> But I'd prefer to use IoThreadHolder instead of string. No assumptions
> necessary then.
>
> If this IoThreadHolder arguments make the calls ugly or overly verbose,
> consider thin wrappers for each kind, i.e.
>
> new_ctx = iothread_get_aio_context_block(iothread, bs);
>
> instead of
>
> holder_name = bdrv_get_node_name(bs);
> new_ctx = iothread_get_aio_context(iothread, holder_name);
>
> and so forth.
>
> Questions?
Thank you for the detailed explanation.
You are right, in this patch I silent assumption: absolute QOM paths
do not start with "/monitor/".
Because the Daniel Berrangé's RFC patch and the monitor implementation
is very special,
it creates the iothread for itself. The iothread lifecycle is same
with the monitor.
It is not a general case like QOM and block node.
Maybe no need for this case add a third kind here?
Thanks
Chen
>
> > }
> > qemu_mutex_init(&mon->mon_lock);
> > mon->is_qmp = is_qmp;
> > @@ -631,6 +643,14 @@ void monitor_data_destroy(Monitor *mon)
> > }
> > g_string_free(mon->outbuf, true);
> > qemu_mutex_destroy(&mon->mon_lock);
> > +
> > + if (mon->ctx && mon_iothread) {
> > + g_autofree char *path = g_strdup(monitor_is_qmp(mon) ?
> > + "/monitor/qmp_mon0" :
> > + "/monitor/hmp_mon0");
> > + iothread_put_aio_context(mon_iothread, path);
> > + mon->ctx = NULL;
> > + }
> > }
> >
> > void monitor_cleanup(void)
> > diff --git a/monitor/qmp.c b/monitor/qmp.c
> > index 687019811f..8e4a775fac 100644
> > --- a/monitor/qmp.c
> > +++ b/monitor/qmp.c
> > @@ -549,7 +549,8 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
> > * since chardev might be running in the monitor I/O
> > * thread. Schedule a bottom half.
> > */
> > - aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
> > + Monitor *mon_p = &mon->common;
> > + aio_bh_schedule_oneshot(mon_p->ctx,
> > monitor_qmp_setup_handlers_bh, mon);
> > /* The bottom half will add @mon to @mon_list */
> > } else {
>
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH V6 06/14] monitor: Update tracking iothread users with holder name
2026-04-27 14:55 ` Zhang Chen
@ 2026-04-28 7:26 ` Markus Armbruster
2026-04-29 5:34 ` Zhang Chen
0 siblings, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2026-04-28 7:26 UTC (permalink / raw)
To: Zhang Chen
Cc: Markus Armbruster, qemu-devel, Dr . David Alan Gilbert,
Eric Blake, Michael S . Tsirkin, Stefan Hajnoczi
Zhang Chen <zhangckid@gmail.com> writes:
> On Thu, Apr 23, 2026 at 8:44 PM Markus Armbruster <armbru@redhat.com> wrote:
>> Oh.
>>
>> PATCH 02 defines QAPI type IoThreadHolder as a union with two branches
>> for the two kinds of holders: one for block node holder, and one for QOM
>> object holder. Both branches use a string to identify the holder.
>>
>> The C code in PATCH 02 uses just a string to identify the holder. This
>> silently assumes that the strings for block nodes are distinct from the
>> strings for QOM objects. True as long as we only use absolute QOM
>> paths: these start with '/', and block nodes can't.
>>
>> This patch shows me there's actually a third kind: monitor. You use the
>> QOM object branch for it. That's confusing; a monitor is not a QOM
>> object. Moreover, you make yet another silent assumption: absolute QOM
>> paths do not start with "/monitor/".
>>
>> This is too much for me.
>>
>> Please use a separate IoThreadHolderKind value and IoThreadHolder branch
>> for each kind of holder. The kinds I've seen so far are block-node, QOM
>> object, monitor.
>>
>> If Daniel Berrangé's "[PATCH RFC 00/17] monitor: turn QMP and HMP into
>> QOM objects" gets merged, kind monitor can go away.
>>
>> Use of just a string to identify the holder requires strings for
>> different kinds to be distinct. The argument why they are must be
>> written down, simple, and likely to remain true.
>>
>> But I'd prefer to use IoThreadHolder instead of string. No assumptions
>> necessary then.
>>
>> If this IoThreadHolder arguments make the calls ugly or overly verbose,
>> consider thin wrappers for each kind, i.e.
>>
>> new_ctx = iothread_get_aio_context_block(iothread, bs);
>>
>> instead of
>>
>> holder_name = bdrv_get_node_name(bs);
>> new_ctx = iothread_get_aio_context(iothread, holder_name);
>>
>> and so forth.
>>
>> Questions?
>
> Thank you for the detailed explanation.
> You are right, in this patch I silent assumption: absolute QOM paths
> do not start with "/monitor/".
> Because the Daniel Berrangé's RFC patch and the monitor implementation
> is very special,
> it creates the iothread for itself. The iothread lifecycle is same
> with the monitor.
> It is not a general case like QOM and block node.
> Maybe no need for this case add a third kind here?
I don't know. Or maybe I don't understand the question.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH V6 06/14] monitor: Update tracking iothread users with holder name
2026-04-28 7:26 ` Markus Armbruster
@ 2026-04-29 5:34 ` Zhang Chen
0 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-29 5:34 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
On Tue, Apr 28, 2026 at 3:26 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > On Thu, Apr 23, 2026 at 8:44 PM Markus Armbruster <armbru@redhat.com> wrote:
> >> Oh.
> >>
> >> PATCH 02 defines QAPI type IoThreadHolder as a union with two branches
> >> for the two kinds of holders: one for block node holder, and one for QOM
> >> object holder. Both branches use a string to identify the holder.
> >>
> >> The C code in PATCH 02 uses just a string to identify the holder. This
> >> silently assumes that the strings for block nodes are distinct from the
> >> strings for QOM objects. True as long as we only use absolute QOM
> >> paths: these start with '/', and block nodes can't.
> >>
> >> This patch shows me there's actually a third kind: monitor. You use the
> >> QOM object branch for it. That's confusing; a monitor is not a QOM
> >> object. Moreover, you make yet another silent assumption: absolute QOM
> >> paths do not start with "/monitor/".
> >>
> >> This is too much for me.
> >>
> >> Please use a separate IoThreadHolderKind value and IoThreadHolder branch
> >> for each kind of holder. The kinds I've seen so far are block-node, QOM
> >> object, monitor.
> >>
> >> If Daniel Berrangé's "[PATCH RFC 00/17] monitor: turn QMP and HMP into
> >> QOM objects" gets merged, kind monitor can go away.
> >>
> >> Use of just a string to identify the holder requires strings for
> >> different kinds to be distinct. The argument why they are must be
> >> written down, simple, and likely to remain true.
> >>
> >> But I'd prefer to use IoThreadHolder instead of string. No assumptions
> >> necessary then.
> >>
> >> If this IoThreadHolder arguments make the calls ugly or overly verbose,
> >> consider thin wrappers for each kind, i.e.
> >>
> >> new_ctx = iothread_get_aio_context_block(iothread, bs);
> >>
> >> instead of
> >>
> >> holder_name = bdrv_get_node_name(bs);
> >> new_ctx = iothread_get_aio_context(iothread, holder_name);
> >>
> >> and so forth.
> >>
> >> Questions?
> >
> > Thank you for the detailed explanation.
> > You are right, in this patch I silent assumption: absolute QOM paths
> > do not start with "/monitor/".
> > Because the Daniel Berrangé's RFC patch and the monitor implementation
> > is very special,
> > it creates the iothread for itself. The iothread lifecycle is same
> > with the monitor.
> > It is not a general case like QOM and block node.
> > Maybe no need for this case add a third kind here?
>
> I don't know. Or maybe I don't understand the question.
>
If no other comments I will keep the assumption in the next version.
At the same time, I will try to keep looking at Daniel Berrangé's RFC patch
about monitor QOM.
Thanks
Chen
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH V6 07/14] virtio-vq-mapping: track iothread-vq-mapping references using device path
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (5 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 06/14] monitor: Update tracking iothread users with holder name Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 08/14] virtio: use iothread_get/put_aio_context for thread pinning Zhang Chen
` (6 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Replace raw object_ref/unref calls with iothread_get/put_aio_context
in iothread-vq-mapping. This allows tracking IOThread users via
the device's canonical QOM path, improving lifecycle traceability
for virtio-blk and virtio-scsi devices.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
hw/block/virtio-blk.c | 8 +++++++-
hw/scsi/virtio-scsi-dataplane.c | 9 +++++++--
hw/virtio/iothread-vq-mapping.c | 11 +++++------
include/hw/virtio/iothread-vq-mapping.h | 6 +++++-
4 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index ddf0e9ee53..762714f4ba 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1453,9 +1453,12 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
s->vq_aio_context = g_new(AioContext *, conf->num_queues);
if (conf->iothread_vq_mapping_list) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(vdev));
+
if (!iothread_vq_mapping_apply(conf->iothread_vq_mapping_list,
s->vq_aio_context,
conf->num_queues,
+ path,
errp)) {
g_free(s->vq_aio_context);
s->vq_aio_context = NULL;
@@ -1487,7 +1490,10 @@ static void virtio_blk_vq_aio_context_cleanup(VirtIOBlock *s)
assert(!s->ioeventfd_started);
if (conf->iothread_vq_mapping_list) {
- iothread_vq_mapping_cleanup(conf->iothread_vq_mapping_list);
+ g_autofree char *path = object_get_canonical_path(
+ OBJECT(VIRTIO_DEVICE(s)));
+
+ iothread_vq_mapping_cleanup(conf->iothread_vq_mapping_list, path);
}
if (conf->iothread) {
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
index 95f13fb7c2..26ecefd547 100644
--- a/hw/scsi/virtio-scsi-dataplane.c
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -65,9 +65,11 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
s->vq_aio_context[1] = qemu_get_aio_context();
if (vs->conf.iothread_vq_mapping_list) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(vdev));
+
if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list,
&s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED],
- vs->conf.num_queues, errp)) {
+ vs->conf.num_queues, path, errp)) {
g_free(s->vq_aio_context);
s->vq_aio_context = NULL;
return;
@@ -94,7 +96,10 @@ void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s)
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
if (vs->conf.iothread_vq_mapping_list) {
- iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list);
+ g_autofree char *path = object_get_canonical_path(
+ OBJECT(VIRTIO_DEVICE(s)));
+
+ iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list, path);
}
if (vs->conf.iothread) {
diff --git a/hw/virtio/iothread-vq-mapping.c b/hw/virtio/iothread-vq-mapping.c
index 55ce62986c..c993281d7f 100644
--- a/hw/virtio/iothread-vq-mapping.c
+++ b/hw/virtio/iothread-vq-mapping.c
@@ -77,6 +77,7 @@ bool iothread_vq_mapping_apply(
IOThreadVirtQueueMappingList *list,
AioContext **vq_aio_context,
uint16_t num_queues,
+ const char *holder,
Error **errp)
{
IOThreadVirtQueueMappingList *node;
@@ -93,10 +94,7 @@ bool iothread_vq_mapping_apply(
for (node = list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
- AioContext *ctx = iothread_get_aio_context(iothread);
-
- /* Released in virtio_blk_vq_aio_context_cleanup() */
- object_ref(OBJECT(iothread));
+ AioContext *ctx = iothread_ref_and_get_aio_context(iothread, holder);
if (node->value->vqs) {
uint16List *vq;
@@ -120,13 +118,14 @@ bool iothread_vq_mapping_apply(
return true;
}
-void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list)
+void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list,
+ const char *holder)
{
IOThreadVirtQueueMappingList *node;
for (node = list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
- object_unref(OBJECT(iothread));
+ iothread_put_aio_context(iothread, holder);
}
}
diff --git a/include/hw/virtio/iothread-vq-mapping.h b/include/hw/virtio/iothread-vq-mapping.h
index 57335c3703..0d39caddf3 100644
--- a/include/hw/virtio/iothread-vq-mapping.h
+++ b/include/hw/virtio/iothread-vq-mapping.h
@@ -17,6 +17,7 @@
* @list: The mapping of virtqueues to IOThreads.
* @vq_aio_context: The array of AioContext pointers to fill in.
* @num_queues: The length of @vq_aio_context.
+ * @holder: The QOM paths for attached device.
* @errp: If an error occurs, a pointer to the area to store the error.
*
* Fill in the AioContext for each virtqueue in the @vq_aio_context array given
@@ -31,15 +32,18 @@ bool iothread_vq_mapping_apply(
IOThreadVirtQueueMappingList *list,
AioContext **vq_aio_context,
uint16_t num_queues,
+ const char *holder,
Error **errp);
/**
* iothread_vq_mapping_cleanup:
* @list: The mapping of virtqueues to IOThreads.
+ * @holder: The QOM paths for attached device.
*
* Release IOThread object references that were acquired by
* iothread_vq_mapping_apply().
*/
-void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list);
+void iothread_vq_mapping_cleanup(IOThreadVirtQueueMappingList *list,
+ const char *holder);
#endif /* HW_VIRTIO_IOTHREAD_VQ_MAPPING_H */
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 08/14] virtio: use iothread_get/put_aio_context for thread pinning
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (6 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 07/14] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 09/14] net/colo: track IOThread references using path-based holder Zhang Chen
` (5 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Refactor virtio-blk and virtio-scsi to use the new iothread_get/put
APIs for AioContext management. This ensures IOThread references
are tracked via the device's canonical QOM path.
Summary of changes:
- Lift 'path' scope to cover both vq_mapping and single iothread cases.
- Replace raw object_ref/unref with iothread_get/put_aio_context.
- Ensure consistent memory cleanup of the QOM path string.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
hw/block/virtio-blk.c | 16 +++++-----------
hw/scsi/virtio-scsi-dataplane.c | 15 +++++----------
2 files changed, 10 insertions(+), 21 deletions(-)
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index 762714f4ba..70fc85bdde 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1429,6 +1429,7 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
VirtIOBlkConf *conf = &s->conf;
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ g_autofree char *path = object_get_canonical_path(OBJECT(vdev));
if (conf->iothread && conf->iothread_vq_mapping_list) {
error_setg(errp,
@@ -1453,8 +1454,6 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
s->vq_aio_context = g_new(AioContext *, conf->num_queues);
if (conf->iothread_vq_mapping_list) {
- g_autofree char *path = object_get_canonical_path(OBJECT(vdev));
-
if (!iothread_vq_mapping_apply(conf->iothread_vq_mapping_list,
s->vq_aio_context,
conf->num_queues,
@@ -1465,13 +1464,11 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
return false;
}
} else if (conf->iothread) {
- AioContext *ctx = iothread_get_aio_context(conf->iothread);
+ AioContext *ctx = iothread_ref_and_get_aio_context(conf->iothread,
+ path);
for (unsigned i = 0; i < conf->num_queues; i++) {
s->vq_aio_context[i] = ctx;
}
-
- /* Released in virtio_blk_vq_aio_context_cleanup() */
- object_ref(OBJECT(conf->iothread));
} else {
AioContext *ctx = qemu_get_aio_context();
for (unsigned i = 0; i < conf->num_queues; i++) {
@@ -1486,21 +1483,18 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
static void virtio_blk_vq_aio_context_cleanup(VirtIOBlock *s)
{
VirtIOBlkConf *conf = &s->conf;
+ g_autofree char *path = object_get_canonical_path(OBJECT(VIRTIO_DEVICE(s)));
assert(!s->ioeventfd_started);
if (conf->iothread_vq_mapping_list) {
- g_autofree char *path = object_get_canonical_path(
- OBJECT(VIRTIO_DEVICE(s)));
-
iothread_vq_mapping_cleanup(conf->iothread_vq_mapping_list, path);
}
if (conf->iothread) {
- object_unref(OBJECT(conf->iothread));
+ iothread_put_aio_context(conf->iothread, path);
}
- g_free(s->vq_aio_context);
s->vq_aio_context = NULL;
}
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
index 26ecefd547..cc318162f5 100644
--- a/hw/scsi/virtio-scsi-dataplane.c
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -28,6 +28,7 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
VirtIODevice *vdev = VIRTIO_DEVICE(s);
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ g_autofree char *path = object_get_canonical_path(OBJECT(vdev));
if (vs->conf.iothread && vs->conf.iothread_vq_mapping_list) {
error_setg(errp,
@@ -65,8 +66,6 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
s->vq_aio_context[1] = qemu_get_aio_context();
if (vs->conf.iothread_vq_mapping_list) {
- g_autofree char *path = object_get_canonical_path(OBJECT(vdev));
-
if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list,
&s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED],
vs->conf.num_queues, path, errp)) {
@@ -75,13 +74,11 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
return;
}
} else if (vs->conf.iothread) {
- AioContext *ctx = iothread_get_aio_context(vs->conf.iothread);
+ AioContext *ctx = iothread_ref_and_get_aio_context(vs->conf.iothread,
+ path);
for (uint16_t i = 0; i < vs->conf.num_queues; i++) {
s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx;
}
-
- /* Released in virtio_scsi_dataplane_cleanup() */
- object_ref(OBJECT(vs->conf.iothread));
} else {
AioContext *ctx = qemu_get_aio_context();
for (unsigned i = 0; i < vs->conf.num_queues; i++) {
@@ -94,16 +91,14 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s)
{
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ g_autofree char *path = object_get_canonical_path(OBJECT(VIRTIO_DEVICE(s)));
if (vs->conf.iothread_vq_mapping_list) {
- g_autofree char *path = object_get_canonical_path(
- OBJECT(VIRTIO_DEVICE(s)));
-
iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list, path);
}
if (vs->conf.iothread) {
- object_unref(OBJECT(vs->conf.iothread));
+ iothread_put_aio_context(vs->conf.iothread, path);
}
g_free(s->vq_aio_context);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 09/14] net/colo: track IOThread references using path-based holder
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (7 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 08/14] virtio: use iothread_get/put_aio_context for thread pinning Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 10/14] virtio-balloon: Update tracking iothread users with holder name Zhang Chen
` (4 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Convert colo-compare to use the iothread_ref_and_get_aio_context()
and iothread_put_aio_context() APIs. This ensures that IOThread
references are tracked using the COLO object's canonical QOM path
as the holder ID.
This refactoring improves IOThread lifecycle traceability and aligns
the code with modern QEMU iothread reference management patterns.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
net/colo-compare.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/net/colo-compare.c b/net/colo-compare.c
index c356419d6a..f3231257d2 100644
--- a/net/colo-compare.c
+++ b/net/colo-compare.c
@@ -130,6 +130,7 @@ struct CompareState {
GHashTable *connection_track_table;
IOThread *iothread;
+ AioContext *iothread_ctx;
GMainContext *worker_context;
QEMUTimer *packet_check_timer;
@@ -926,9 +927,7 @@ void colo_notify_compares_event(void *opaque, int event, Error **errp)
static void colo_compare_timer_init(CompareState *s)
{
- AioContext *ctx = iothread_get_aio_context(s->iothread);
-
- s->packet_check_timer = aio_timer_new(ctx, QEMU_CLOCK_HOST,
+ s->packet_check_timer = aio_timer_new(s->iothread_ctx, QEMU_CLOCK_HOST,
SCALE_MS, check_old_packet_regular,
s);
timer_mod(s->packet_check_timer, qemu_clock_get_ms(QEMU_CLOCK_HOST) +
@@ -968,8 +967,10 @@ static void colo_compare_handle_event(void *opaque)
static void colo_compare_iothread(CompareState *s)
{
- AioContext *ctx = iothread_get_aio_context(s->iothread);
- object_ref(OBJECT(s->iothread));
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ AioContext *ctx = iothread_ref_and_get_aio_context(s->iothread, path);
+
+ s->iothread_ctx = ctx;
s->worker_context = iothread_get_g_main_context(s->iothread);
qemu_chr_fe_set_handlers(&s->chr_pri_in, compare_chr_can_read,
@@ -1408,6 +1409,7 @@ static void colo_compare_finalize(Object *obj)
{
CompareState *s = COLO_COMPARE(obj);
CompareState *tmp = NULL;
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
qemu_mutex_lock(&colo_compare_mutex);
QTAILQ_FOREACH(tmp, &net_compares, next) {
@@ -1434,11 +1436,11 @@ static void colo_compare_finalize(Object *obj)
qemu_bh_delete(s->event_bh);
- AioContext *ctx = iothread_get_aio_context(s->iothread);
- AIO_WAIT_WHILE(ctx, !s->out_sendco.done);
+ AIO_WAIT_WHILE(s->iothread_ctx, !s->out_sendco.done);
if (s->notify_dev) {
- AIO_WAIT_WHILE(ctx, !s->notify_sendco.done);
+ AIO_WAIT_WHILE(s->iothread_ctx, !s->notify_sendco.done);
}
+ iothread_put_aio_context(s->iothread, path);
/* Release all unhandled packets after compare thead exited */
g_queue_foreach(&s->conn_list, colo_flush_packets, s);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 10/14] virtio-balloon: Update tracking iothread users with holder name
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (8 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 09/14] net/colo: track IOThread references using path-based holder Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 11/14] vfio-user/proxy: " Zhang Chen
` (3 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Replace raw object_ref/unref calls with iothread_get/put_aio_context.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
hw/virtio/virtio-balloon.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index 4c5f486ba2..8a4494905a 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -892,14 +892,16 @@ static void virtio_balloon_device_realize(DeviceState *dev, Error **errp)
s->svq = virtio_add_queue(vdev, 128, virtio_balloon_receive_stats);
if (virtio_has_feature(s->host_features, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
s->free_page_vq = virtio_add_queue(vdev, VIRTQUEUE_MAX_SIZE,
virtio_balloon_handle_free_page_vq);
precopy_add_notifier(&s->free_page_hint_notify);
- object_ref(OBJECT(s->iothread));
- s->free_page_bh = aio_bh_new_guarded(iothread_get_aio_context(s->iothread),
- virtio_ballloon_get_free_page_hints, s,
- &dev->mem_reentrancy_guard);
+ s->free_page_bh = aio_bh_new_guarded(
+ iothread_ref_and_get_aio_context(s->iothread, path),
+ virtio_ballloon_get_free_page_hints, s,
+ &dev->mem_reentrancy_guard);
}
if (virtio_has_feature(s->host_features, VIRTIO_BALLOON_F_REPORTING)) {
@@ -919,9 +921,11 @@ static void virtio_balloon_device_unrealize(DeviceState *dev)
qemu_unregister_resettable(OBJECT(dev));
if (s->free_page_bh) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+
qemu_bh_delete(s->free_page_bh);
- object_unref(OBJECT(s->iothread));
virtio_balloon_free_page_stop(s);
+ iothread_put_aio_context(s->iothread, path);
precopy_remove_notifier(&s->free_page_hint_notify);
}
balloon_stats_destroy_timer(s);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 11/14] vfio-user/proxy: Update tracking iothread users with holder name
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (9 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 10/14] virtio-balloon: Update tracking iothread users with holder name Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 12/14] xen-block: " Zhang Chen
` (2 subsequent siblings)
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Add object_ref/unref calls with iothread_get/put_aio_context.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
hw/vfio-user/proxy.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/hw/vfio-user/proxy.c b/hw/vfio-user/proxy.c
index 314dfd23d8..eebe202c4a 100644
--- a/hw/vfio-user/proxy.c
+++ b/hw/vfio-user/proxy.c
@@ -898,6 +898,7 @@ VFIOUserProxy *vfio_user_connect_dev(SocketAddress *addr, Error **errp)
QIOChannelSocket *sioc;
QIOChannel *ioc;
char *sockname;
+ g_autofree char *path;
if (addr->type != SOCKET_ADDRESS_TYPE_UNIX) {
error_setg(errp, "vfio_user_connect - bad address family");
@@ -917,6 +918,7 @@ VFIOUserProxy *vfio_user_connect_dev(SocketAddress *addr, Error **errp)
proxy = g_malloc0(sizeof(VFIOUserProxy));
proxy->sockname = g_strdup_printf("unix:%s", sockname);
proxy->ioc = ioc;
+ path = object_get_canonical_path(OBJECT(proxy->ioc));
/* init defaults */
proxy->max_xfer_size = VFIO_USER_DEF_MAX_XFER;
@@ -936,7 +938,7 @@ VFIOUserProxy *vfio_user_connect_dev(SocketAddress *addr, Error **errp)
vfio_user_iothread = iothread_create("VFIO user", errp);
}
- proxy->ctx = iothread_get_aio_context(vfio_user_iothread);
+ proxy->ctx = iothread_ref_and_get_aio_context(vfio_user_iothread, path);
proxy->req_bh = qemu_bh_new(vfio_user_request, proxy);
QTAILQ_INIT(&proxy->outgoing);
@@ -967,6 +969,7 @@ void vfio_user_set_handler(VFIODevice *vbasedev,
void vfio_user_disconnect(VFIOUserProxy *proxy)
{
VFIOUserMsg *r1, *r2;
+ g_autofree char *path = object_get_canonical_path(OBJECT(proxy->ioc));
qemu_mutex_lock(&proxy->lock);
@@ -1021,6 +1024,8 @@ void vfio_user_disconnect(VFIOUserProxy *proxy)
qemu_cond_destroy(&proxy->close_cv);
qemu_mutex_destroy(&proxy->lock);
+ iothread_put_aio_context(vfio_user_iothread, path);
+
QLIST_REMOVE(proxy, next);
if (QLIST_EMPTY(&vfio_user_sockets)) {
iothread_destroy(vfio_user_iothread);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 12/14] xen-block: Update tracking iothread users with holder name
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (10 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 11/14] vfio-user/proxy: " Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-10 15:04 ` [PATCH V6 13/14] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
2026-04-10 15:04 ` [PATCH V6 14/14] iothread: simplify API by merging iothread_get_aio_context variants Zhang Chen
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Replace raw object_ref/unref calls with iothread_get/put_aio_context.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
hw/block/dataplane/xen-block.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/hw/block/dataplane/xen-block.c b/hw/block/dataplane/xen-block.c
index 48c2e315f3..552bd8b039 100644
--- a/hw/block/dataplane/xen-block.c
+++ b/hw/block/dataplane/xen-block.c
@@ -621,9 +621,11 @@ XenBlockDataPlane *xen_block_dataplane_create(XenDevice *xendev,
QLIST_INIT(&dataplane->freelist);
if (iothread) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(xendev));
+
dataplane->iothread = iothread;
- object_ref(OBJECT(dataplane->iothread));
- dataplane->ctx = iothread_get_aio_context(dataplane->iothread);
+ dataplane->ctx = iothread_ref_and_get_aio_context(dataplane->iothread,
+ path);
} else {
dataplane->ctx = qemu_get_aio_context();
}
@@ -652,7 +654,10 @@ void xen_block_dataplane_destroy(XenBlockDataPlane *dataplane)
qemu_bh_delete(dataplane->bh);
if (dataplane->iothread) {
- object_unref(OBJECT(dataplane->iothread));
+ g_autofree char *path = object_get_canonical_path(
+ OBJECT(dataplane->xendev));
+
+ iothread_put_aio_context(dataplane->iothread, path);
}
g_free(dataplane);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH V6 13/14] qapi: examine IOThread attachment status via query-iothreads
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (11 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 12/14] xen-block: " Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
2026-04-23 13:09 ` Markus Armbruster
2026-04-10 15:04 ` [PATCH V6 14/14] iothread: simplify API by merging iothread_get_aio_context variants Zhang Chen
13 siblings, 1 reply; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Extend the 'IOThreadInfo' structure to include attachment metrics.
This allows users to monitor the associated devices by identify them
by their QOM paths.
New fields added to IOThreadInfo:
- @holders: IoThreadHolder(QOM path or block node name) of the devices
currently associated with this iothread.
These fields are also exposed via the Human Monitor Interface (HMP)
command 'info iothreads' to assist with manual debugging and
performance tuning.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
iothread.c | 22 ++++++++++++++++++++++
monitor/hmp-cmds.c | 22 ++++++++++++++++++++++
qapi/misc.json | 16 ++++++++++++++--
3 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/iothread.c b/iothread.c
index 60a024f770..70bc5fb62a 100644
--- a/iothread.c
+++ b/iothread.c
@@ -24,6 +24,8 @@
#include "qemu/error-report.h"
#include "qemu/rcu.h"
#include "qemu/main-loop.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qapi-visit-misc.h"
#ifdef CONFIG_POSIX
@@ -93,6 +95,25 @@ static void iothread_unref(IOThread *iothread, const char *holder)
object_unref(OBJECT(iothread));
}
+static IoThreadHolderList *iothread_get_holders_list(IOThread *iothread)
+{
+ IoThreadHolderList *head = NULL;
+ IoThreadHolderList **prev = &head;
+ GList *l;
+
+ for (l = iothread->holders; l; l = l->next) {
+ IoThreadHolder *src = l->data;
+ IoThreadHolderList *entry = g_new0(IoThreadHolderList, 1);
+
+ entry->value = QAPI_CLONE(IoThreadHolder, src);
+
+ *prev = entry;
+ prev = &entry->next;
+ }
+
+ return head;
+}
+
static void *iothread_run(void *opaque)
{
IOThread *iothread = opaque;
@@ -434,6 +455,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 = iothread_get_holders_list(iothread);
info->poll_max_ns = iothread->poll_max_ns;
info->poll_grow = iothread->poll_grow;
info->poll_shrink = iothread->poll_shrink;
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index bc26b39d70..344eaf330b 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -198,11 +198,33 @@ void hmp_info_iothreads(Monitor *mon, const QDict *qdict)
IOThreadInfoList *info_list = qmp_query_iothreads(NULL);
IOThreadInfoList *info;
IOThreadInfo *value;
+ IoThreadHolderList *h;
for (info = info_list; info; info = info->next) {
value = info->value;
monitor_printf(mon, "%s:\n", value->id);
monitor_printf(mon, " thread_id=%" PRId64 "\n", value->thread_id);
+ monitor_printf(mon, " holders=");
+ if (value->holders) {
+ for (h = value->holders; h; h = h->next) {
+ IoThreadHolder *holder = h->value;
+
+ switch (holder->type) {
+ case IO_THREAD_HOLDER_KIND_BLOCK_NODE:
+ monitor_printf(mon, "[block-node: %s]",
+ holder->u.block_node.data);
+ break;
+ case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
+ monitor_printf(mon, "[qom: %s]",
+ holder->u.qom_object.data);
+ break;
+ default:
+ monitor_printf(mon, "[unknown]");
+ break;
+ }
+ }
+ monitor_printf(mon, "\n");
+ }
monitor_printf(mon, " poll-max-ns=%" PRId64 "\n", value->poll_max_ns);
monitor_printf(mon, " poll-grow=%" PRId64 "\n", value->poll_grow);
monitor_printf(mon, " poll-shrink=%" PRId64 "\n", value->poll_shrink);
diff --git a/qapi/misc.json b/qapi/misc.json
index d65d8012b2..e173d54a5e 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -94,7 +94,8 @@
# @IoThreadHolderKind:
#
# @block-node: Block node name.
-# @qom-object: Standard QOM path.
+#
+# @qom-object: QOM path.
#
# Since: 11.0
##
@@ -104,7 +105,7 @@
##
# @IoThreadHolder:
#
-# @type: Current IoThread holder type support QOM path and Block node.
+# @type: Current IoThread holder type support QOM path and block node.
#
# Since: 11.0
##
@@ -124,6 +125,13 @@
#
# @thread-id: ID of the underlying host thread
#
+# @holders: IoThreadHolder(QOM path or block node name) of the devices
+# currently associated with this iothread. Users can pre-allocate
+# multiple iothread objects to serve as a persistent thread pool.
+# When a device is hot-unplugged, it is detached from its
+# iothread, but the iothread remains available, allowing future
+# hot-plugged devices to attach to it.
+#
# @poll-max-ns: maximum polling time in ns, 0 means polling is
# disabled (since 2.9)
#
@@ -141,6 +149,7 @@
{ 'struct': 'IOThreadInfo',
'data': {'id': 'str',
'thread-id': 'int',
+ 'holders': ['IoThreadHolder'],
'poll-max-ns': 'int',
'poll-grow': 'int',
'poll-shrink': 'int',
@@ -166,6 +175,8 @@
# {
# "id":"iothread0",
# "thread-id":3134,
+# "holders":["/machine/peripheral/blk1/virtio-backend",
+# "/machine/peripheral/blk0/virtio-backend"],
# "poll-max-ns":32768,
# "poll-grow":0,
# "poll-shrink":0,
@@ -174,6 +185,7 @@
# {
# "id":"iothread1",
# "thread-id":3135,
+# "holders":["/machine/peripheral/blk2/virtio-backend"],
# "poll-max-ns":32768,
# "poll-grow":0,
# "poll-shrink":0,
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH V6 13/14] qapi: examine IOThread attachment status via query-iothreads
2026-04-10 15:04 ` [PATCH V6 13/14] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
@ 2026-04-23 13:09 ` Markus Armbruster
2026-04-27 15:10 ` Zhang Chen
0 siblings, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2026-04-23 13:09 UTC (permalink / raw)
To: Zhang Chen
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
Zhang Chen <zhangckid@gmail.com> writes:
> Extend the 'IOThreadInfo' structure to include attachment metrics.
> This allows users to monitor the associated devices by identify them
> by their QOM paths.
>
> New fields added to IOThreadInfo:
> - @holders: IoThreadHolder(QOM path or block node name) of the devices
> currently associated with this iothread.
See "I'm confused" below.
>
> These fields are also exposed via the Human Monitor Interface (HMP)
> command 'info iothreads' to assist with manual debugging and
> performance tuning.
>
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
[...]
> diff --git a/qapi/misc.json b/qapi/misc.json
> index d65d8012b2..e173d54a5e 100644
> --- a/qapi/misc.json
> +++ b/qapi/misc.json
> @@ -94,7 +94,8 @@
> # @IoThreadHolderKind:
> #
> # @block-node: Block node name.
> -# @qom-object: Standard QOM path.
> +#
> +# @qom-object: QOM path.
> #
> # Since: 11.0
> ##
Squash this into PATCH 02, please.
> @@ -104,7 +105,7 @@
> ##
> # @IoThreadHolder:
> #
> -# @type: Current IoThread holder type support QOM path and Block node.
> +# @type: Current IoThread holder type support QOM path and block node.
This, too.
> #
> # Since: 11.0
> ##
> @@ -124,6 +125,13 @@
> #
> # @thread-id: ID of the underlying host thread
> #
> +# @holders: IoThreadHolder(QOM path or block node name) of the devices
Space before (
> +# currently associated with this iothread. Users can pre-allocate
I'm confused. What is holding what?
A QOM object / block node / monitor holding an I/O thread?
> +# multiple iothread objects to serve as a persistent thread pool.
> +# When a device is hot-unplugged, it is detached from its
> +# iothread, but the iothread remains available, allowing future
> +# hot-plugged devices to attach to it.
> +#
> # @poll-max-ns: maximum polling time in ns, 0 means polling is
> # disabled (since 2.9)
> #
> @@ -141,6 +149,7 @@
> { 'struct': 'IOThreadInfo',
> 'data': {'id': 'str',
> 'thread-id': 'int',
> + 'holders': ['IoThreadHolder'],
> 'poll-max-ns': 'int',
> 'poll-grow': 'int',
> 'poll-shrink': 'int',
> @@ -166,6 +175,8 @@
> # {
> # "id":"iothread0",
> # "thread-id":3134,
> +# "holders":["/machine/peripheral/blk1/virtio-backend",
> +# "/machine/peripheral/blk0/virtio-backend"],
> # "poll-max-ns":32768,
> # "poll-grow":0,
> # "poll-shrink":0,
> @@ -174,6 +185,7 @@
> # {
> # "id":"iothread1",
> # "thread-id":3135,
> +# "holders":["/machine/peripheral/blk2/virtio-backend"],
> # "poll-max-ns":32768,
> # "poll-grow":0,
> # "poll-shrink":0,
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH V6 13/14] qapi: examine IOThread attachment status via query-iothreads
2026-04-23 13:09 ` Markus Armbruster
@ 2026-04-27 15:10 ` Zhang Chen
0 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-27 15:10 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Michael S . Tsirkin, Stefan Hajnoczi
On Thu, Apr 23, 2026 at 9:10 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Extend the 'IOThreadInfo' structure to include attachment metrics.
> > This allows users to monitor the associated devices by identify them
> > by their QOM paths.
> >
> > New fields added to IOThreadInfo:
> > - @holders: IoThreadHolder(QOM path or block node name) of the devices
> > currently associated with this iothread.
>
> See "I'm confused" below.
>
> >
> > These fields are also exposed via the Human Monitor Interface (HMP)
> > command 'info iothreads' to assist with manual debugging and
> > performance tuning.
> >
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
>
> [...]
>
> > diff --git a/qapi/misc.json b/qapi/misc.json
> > index d65d8012b2..e173d54a5e 100644
> > --- a/qapi/misc.json
> > +++ b/qapi/misc.json
> > @@ -94,7 +94,8 @@
> > # @IoThreadHolderKind:
> > #
> > # @block-node: Block node name.
> > -# @qom-object: Standard QOM path.
> > +#
> > +# @qom-object: QOM path.
> > #
> > # Since: 11.0
> > ##
>
> Squash this into PATCH 02, please.
OK.
>
> > @@ -104,7 +105,7 @@
> > ##
> > # @IoThreadHolder:
> > #
> > -# @type: Current IoThread holder type support QOM path and Block node.
> > +# @type: Current IoThread holder type support QOM path and block node.
>
> This, too.
>
> > #
> > # Since: 11.0
> > ##
> > @@ -124,6 +125,13 @@
> > #
> > # @thread-id: ID of the underlying host thread
> > #
> > +# @holders: IoThreadHolder(QOM path or block node name) of the devices
>
> Space before (
OK.
>
> > +# currently associated with this iothread. Users can pre-allocate
>
> I'm confused. What is holding what?
>
> A QOM object / block node / monitor holding an I/O thread?
Yes, this series tried to manage the IOthread lifecycle.
So we need to know who is using any of the IOthread, step by step:
1. Create an iothread.
2. Attach the iothread to a QOM object/ block node/ monitor(iothread_ref()).
3. Detach the iothread from a QOM object/ block node/ monitor(iothread_unref()).
Thanks
Chen
>
> > +# multiple iothread objects to serve as a persistent thread pool.
> > +# When a device is hot-unplugged, it is detached from its
> > +# iothread, but the iothread remains available, allowing future
> > +# hot-plugged devices to attach to it.
> > +#
> > # @poll-max-ns: maximum polling time in ns, 0 means polling is
> > # disabled (since 2.9)
> > #
> > @@ -141,6 +149,7 @@
> > { 'struct': 'IOThreadInfo',
> > 'data': {'id': 'str',
> > 'thread-id': 'int',
> > + 'holders': ['IoThreadHolder'],
> > 'poll-max-ns': 'int',
> > 'poll-grow': 'int',
> > 'poll-shrink': 'int',
> > @@ -166,6 +175,8 @@
> > # {
> > # "id":"iothread0",
> > # "thread-id":3134,
> > +# "holders":["/machine/peripheral/blk1/virtio-backend",
> > +# "/machine/peripheral/blk0/virtio-backend"],
> > # "poll-max-ns":32768,
> > # "poll-grow":0,
> > # "poll-shrink":0,
> > @@ -174,6 +185,7 @@
> > # {
> > # "id":"iothread1",
> > # "thread-id":3135,
> > +# "holders":["/machine/peripheral/blk2/virtio-backend"],
> > # "poll-max-ns":32768,
> > # "poll-grow":0,
> > # "poll-shrink":0,
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH V6 14/14] iothread: simplify API by merging iothread_get_aio_context variants
2026-04-10 15:04 [PATCH V6 00/14] iothread: Support tracking and querying IOThread holders Zhang Chen
` (12 preceding siblings ...)
2026-04-10 15:04 ` [PATCH V6 13/14] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
@ 2026-04-10 15:04 ` Zhang Chen
13 siblings, 0 replies; 23+ messages in thread
From: Zhang Chen @ 2026-04-10 15:04 UTC (permalink / raw)
To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
Cc: Zhang Chen
Simplify the interface by merging iothread_ref_and_get_aio_context()
into iothread_get_aio_context(). The updated function now requires a
'holder' parameter, ensuring that every retrieval of an AioContext for
long-term use is automatically registered in the IOThread's holder list.
Update all callers across block, virtio, scsi, net, and monitor
subsystems to match the new signature. This cleanup reduces code
redundancy and improves the reliability of IOThread introspection.
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
block/export/export.c | 6 +++---
blockdev.c | 2 +-
hw/block/dataplane/xen-block.c | 3 +--
hw/block/virtio-blk.c | 3 +--
hw/scsi/virtio-scsi-dataplane.c | 3 +--
hw/vfio-user/proxy.c | 2 +-
hw/virtio/iothread-vq-mapping.c | 2 +-
hw/virtio/virtio-balloon.c | 2 +-
include/system/iothread.h | 5 ++---
iothread.c | 9 ++-------
monitor/monitor.c | 2 +-
net/colo-compare.c | 2 +-
12 files changed, 16 insertions(+), 25 deletions(-)
diff --git a/block/export/export.c b/block/export/export.c
index 636633c324..37bcb4f4d6 100644
--- a/block/export/export.c
+++ b/block/export/export.c
@@ -141,7 +141,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
}
holder_name = bdrv_get_node_name(bs);
- new_ctx = iothread_ref_and_get_aio_context(iothread, holder_name);
+ new_ctx = iothread_get_aio_context(iothread, holder_name);
multithread_count = 1;
local_iothreads = g_new0(IOThread *, 1);
local_iothreads[0] = iothread;
@@ -180,8 +180,8 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
goto fail;
}
local_iothreads[i] = iothread;
- multithread_ctxs[i++] = iothread_ref_and_get_aio_context(iothread,
- holder_name);
+ multithread_ctxs[i++] = iothread_get_aio_context(iothread,
+ holder_name);
}
assert(i == multithread_count);
}
diff --git a/blockdev.c b/blockdev.c
index 6e20579187..7eb206a02a 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3683,7 +3683,7 @@ void qmp_x_blockdev_set_iothread(const char *node_name, StrOrNull *iothread,
goto out;
}
- new_context = iothread_ref_and_get_aio_context(obj, node_name);
+ new_context = iothread_get_aio_context(obj, node_name);
bs->iothread = obj;
} else {
if (bs->iothread) {
diff --git a/hw/block/dataplane/xen-block.c b/hw/block/dataplane/xen-block.c
index 552bd8b039..f5984c86e9 100644
--- a/hw/block/dataplane/xen-block.c
+++ b/hw/block/dataplane/xen-block.c
@@ -624,8 +624,7 @@ XenBlockDataPlane *xen_block_dataplane_create(XenDevice *xendev,
g_autofree char *path = object_get_canonical_path(OBJECT(xendev));
dataplane->iothread = iothread;
- dataplane->ctx = iothread_ref_and_get_aio_context(dataplane->iothread,
- path);
+ dataplane->ctx = iothread_get_aio_context(dataplane->iothread, path);
} else {
dataplane->ctx = qemu_get_aio_context();
}
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index 70fc85bdde..8c6a3eaa1e 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1464,8 +1464,7 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
return false;
}
} else if (conf->iothread) {
- AioContext *ctx = iothread_ref_and_get_aio_context(conf->iothread,
- path);
+ AioContext *ctx = iothread_get_aio_context(conf->iothread, path);
for (unsigned i = 0; i < conf->num_queues; i++) {
s->vq_aio_context[i] = ctx;
}
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
index cc318162f5..7b37e3d223 100644
--- a/hw/scsi/virtio-scsi-dataplane.c
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -74,8 +74,7 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
return;
}
} else if (vs->conf.iothread) {
- AioContext *ctx = iothread_ref_and_get_aio_context(vs->conf.iothread,
- path);
+ AioContext *ctx = iothread_get_aio_context(vs->conf.iothread, path);
for (uint16_t i = 0; i < vs->conf.num_queues; i++) {
s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i] = ctx;
}
diff --git a/hw/vfio-user/proxy.c b/hw/vfio-user/proxy.c
index eebe202c4a..9a9059290f 100644
--- a/hw/vfio-user/proxy.c
+++ b/hw/vfio-user/proxy.c
@@ -938,7 +938,7 @@ VFIOUserProxy *vfio_user_connect_dev(SocketAddress *addr, Error **errp)
vfio_user_iothread = iothread_create("VFIO user", errp);
}
- proxy->ctx = iothread_ref_and_get_aio_context(vfio_user_iothread, path);
+ proxy->ctx = iothread_get_aio_context(vfio_user_iothread, path);
proxy->req_bh = qemu_bh_new(vfio_user_request, proxy);
QTAILQ_INIT(&proxy->outgoing);
diff --git a/hw/virtio/iothread-vq-mapping.c b/hw/virtio/iothread-vq-mapping.c
index c993281d7f..7dadc43c44 100644
--- a/hw/virtio/iothread-vq-mapping.c
+++ b/hw/virtio/iothread-vq-mapping.c
@@ -94,7 +94,7 @@ bool iothread_vq_mapping_apply(
for (node = list; node; node = node->next) {
IOThread *iothread = iothread_by_id(node->value->iothread);
- AioContext *ctx = iothread_ref_and_get_aio_context(iothread, holder);
+ AioContext *ctx = iothread_get_aio_context(iothread, holder);
if (node->value->vqs) {
uint16List *vq;
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index 8a4494905a..bd47ba8256 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -899,7 +899,7 @@ static void virtio_balloon_device_realize(DeviceState *dev, Error **errp)
precopy_add_notifier(&s->free_page_hint_notify);
s->free_page_bh = aio_bh_new_guarded(
- iothread_ref_and_get_aio_context(s->iothread, path),
+ iothread_get_aio_context(s->iothread, path),
virtio_ballloon_get_free_page_hints, s,
&dev->mem_reentrancy_guard);
}
diff --git a/include/system/iothread.h b/include/system/iothread.h
index dbada8249c..fde80f4149 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -47,9 +47,8 @@ DECLARE_INSTANCE_CHECKER(IOThread, IOTHREAD,
char *iothread_get_id(IOThread *iothread);
IOThread *iothread_by_id(const char *id);
-AioContext *iothread_get_aio_context(IOThread *iothread);
-AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
- const char *holder);
+AioContext *iothread_get_aio_context(IOThread *iothread,
+ const char *holder);
void iothread_put_aio_context(IOThread *iothread, const char *holder);
GMainContext *iothread_get_g_main_context(IOThread *iothread);
diff --git a/iothread.c b/iothread.c
index 70bc5fb62a..470f267901 100644
--- a/iothread.c
+++ b/iothread.c
@@ -415,13 +415,8 @@ char *iothread_get_id(IOThread *iothread)
return g_strdup(object_get_canonical_path_component(OBJECT(iothread)));
}
-AioContext *iothread_get_aio_context(IOThread *iothread)
-{
- return iothread->ctx;
-}
-
-AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
- const char *holder)
+AioContext *iothread_get_aio_context(IOThread *iothread,
+ const char *holder)
{
/*
* In some cases, iothread user need the ctx to clearup other resource.
diff --git a/monitor/monitor.c b/monitor/monitor.c
index b6efe776d6..0f9642f11a 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -623,7 +623,7 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
*/
g_autofree char *path = g_strdup(is_qmp ? "/monitor/qmp_mon0" :
"/monitor/hmp_mon0");
- mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, path);
+ mon->ctx = iothread_get_aio_context(mon_iothread, path);
}
qemu_mutex_init(&mon->mon_lock);
mon->is_qmp = is_qmp;
diff --git a/net/colo-compare.c b/net/colo-compare.c
index f3231257d2..ff58ea9e0a 100644
--- a/net/colo-compare.c
+++ b/net/colo-compare.c
@@ -968,7 +968,7 @@ static void colo_compare_handle_event(void *opaque)
static void colo_compare_iothread(CompareState *s)
{
g_autofree char *path = object_get_canonical_path(OBJECT(s));
- AioContext *ctx = iothread_ref_and_get_aio_context(s->iothread, path);
+ AioContext *ctx = iothread_get_aio_context(s->iothread, path);
s->iothread_ctx = ctx;
s->worker_context = iothread_get_g_main_context(s->iothread);
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread