All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders
@ 2026-05-29 21:33 Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 01/15] qapi/misc: Fix missed query-iothreads items Zhang Chen
                   ` (14 more replies)
  0 siblings, 15 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 UTC (permalink / raw)
  To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
  Cc: Zhang Chen

### Motivation
Currently, the relationship between IOThreads and the resources using them is
opaque. From a management perspective, it is difficult to determine which
devices or block exports are currently associated with a specific IOThread
via QMP or HMP.

This series introduces a "holder" tracking mechanism based on QOM paths to
significantly improve IOThread observability. In high-performance
virtualization environments, users often pre-allocate a set of IOThreads to
serve as a persistent thread pool. During device hotplug/unplug cycles, while
IOThread objects remain persistent, their attachment state changes dynamically.

By tracking these "holders," administrators can use `query-iothreads` to  
monitor the real-time load distribution of the thread pool and identify which 
specific devices (e.g., virtio-blk, BlockExport) are attached to which thread.

### Technical Overview
* Atomic API Design: Merged iothread_get_aio_context and 
  iothread_ref_and_get_aio_context into a single unified function. The new 
  API enforces a 'holder' parameter, making "retrieval" and "tracking" an  
  atomic operation to prevent reference tracking leaks.
* Subsystem Integration: Fully migrated and adapted core modules including 
  Virtio (blk, vq-mapping, balloon, scsi), Block Layer (export, xen), 
  Network (COLO), VFIO, and the Monitor subsystem.
* Introspection: Expanded the IOThreadInfo QAPI structure to include a 
  'holders' array (list of QOM paths), accessible via both 
  query-iothreads (QMP) and info iothreads (HMP).

### V7 -> V8 Changelog
  - Drop the assumption of the Monitor QOM, add the new type
    IO_THREAD_HOLDER_KIND_MONITOR_NAME.
  - Drop the assumption of the string '/' as QOM path.
  - Introduce new patch for Monitor:
    [PATCH 06/15] assign unique default ID to anonymous monitors
  - Fixed Monitor ID for the IOThread.
  - Refactorcode according comments.
  - Update QMP example.
  - Rebased patches on upstream code.
  - Fixed comments issues.
  - Removed redundant code.

### V6 -> V7 Changelog
  - Fixed comments issues in patch 02/14 (Thanks Markus).
  - Removed redundant code.
  - Rebased patches on upstream code.
  - Fixed code conflict with latest patches.
  - Added detailed description in git log and code.
  - Added comments for patch 06/14 assuming the monitor is a QOM object,
    Depends on Daniel Berrange's RFC patch:
    [PATCH RFC 00/17] monitor: turn QMP and HMP into QOM objects

### V5 -> V6 Changelog
* API Refactoring (Breaking Change):
  - Implement the 'union': 'IoThreadHolder' for combine the QOM objects
    and the block-node.
  - Completely merged iothread_get_aio_context variants.
  - The iothread_get_aio_context function now strictly requires a 'holder' 
    argument to ensure every context retrieval is accounted for.
* Expanded Module Coverage:
  - Added holder tracking for the monitor subsystem (tracking threads used 
    by QMP/HMP).
  - Added support for the virtio-scsi dataplane.
* Code Cleanup:
  - Removed redundant and obsolete API declarations.
  - Standardized the use of object_get_canonical_path() across all callers 
    to provide consistent and unique holder names.
* Bug Fixes:
  - Updated qapi/misc.json documentation to fix missing descriptions in
    query-iothreads items.
* Build System:
  - Improved header inclusion logic to avoid redundant declaration errors
    in unit tests.
* QAPI part:
  - Sorry to drop the "Acked-by: Markus Armbruster <armbru@redhat.com>",
    because this version have lots of changes in the:
    [PATCH V6 13/14]qapi: examine IOThread attachment status via query-iothreads
 


Zhang Chen (15):
  qapi/misc: Fix missed query-iothreads items
  iothread: introduce iothread_ref/unref to track attached devices
  iothread: tracking iothread users with holder name
  iothread: introduce iothread_unsafe_get_aio_context()
  block/export: track IOThread reference in BlockExport
  monitor: assign unique default ID to anonymous monitors
  monitor: Update tracking iothread users with holder
  virtio-vq-mapping: track iothread-vq-mapping references using device
    path
  virtio: use iothread_get/put_aio_context for thread pinning
  net/colo: track IOThread references using path-based holder
  virtio-balloon: Update tracking iothread users with holder
  vfio-user/proxy: Update tracking iothread users with holder name
  xen-block: Update tracking iothread users with holder name
  qapi: examine IOThread attachment status via query-iothreads
  iothread: simplify API by merging iothread_get_aio_context variants

 block/export/export.c                   |  62 +++++++++--
 blockdev.c                              |   2 +-
 chardev/char.c                          |   2 +-
 gdbstub/system.c                        |   3 +-
 hw/block/dataplane/xen-block.c          |  19 +++-
 hw/block/virtio-blk.c                   |  22 ++--
 hw/scsi/virtio-scsi-dataplane.c         |  22 ++--
 hw/vfio-user/proxy.c                    |  15 ++-
 hw/virtio/iothread-vq-mapping.c         |  19 +++-
 hw/virtio/virtio-balloon.c              |  22 +++-
 include/block/export.h                  |   6 ++
 include/hw/virtio/iothread-vq-mapping.h |   6 +-
 include/monitor/monitor.h               |   6 +-
 include/system/iothread.h               |  17 ++-
 iothread.c                              | 136 +++++++++++++++++++++++-
 monitor/hmp-cmds.c                      |  26 +++++
 monitor/hmp.c                           |  20 +++-
 monitor/monitor-internal.h              |   5 +
 monitor/monitor.c                       |  24 ++++-
 monitor/qmp.c                           |  21 +++-
 net/colo-compare.c                      |  29 +++--
 qapi/misc.json                          |  84 ++++++++++++++-
 stubs/monitor-internal.c                |   3 +-
 23 files changed, 505 insertions(+), 66 deletions(-)

-- 
2.49.0



^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH V8 01/15] qapi/misc: Fix missed query-iothreads items
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 02/15] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 22b7afed9f..c71a5fe657 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -123,11 +123,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] 25+ messages in thread

* [PATCH V8 02/15] iothread: introduce iothread_ref/unref to track attached devices
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 01/15] qapi/misc: Fix missed query-iothreads items Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-06-02 11:29   ` Markus Armbruster
  2026-05-29 21:33 ` [PATCH V8 03/15] iothread: tracking iothread users with holder name Zhang Chen
                   ` (12 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 IOThreadHolder to a list.
- iothread_unref(): Searches for the IOThreadHolder using a custom
  string comparison (g_strcmp0), releases the associated memory
  upon a successful match.
- holders: A GList storing the IOThreadHolder of attached devices
  for runtime introspection.

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 |  5 +++
 iothread.c                | 94 +++++++++++++++++++++++++++++++++++++++
 qapi/misc.json            | 61 +++++++++++++++++++++++++
 3 files changed, 160 insertions(+)

diff --git a/include/system/iothread.h b/include/system/iothread.h
index a1ef7696cb..b9207ad829 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -50,6 +50,11 @@ struct IOThread {
     bool stopping;              /* has iothread_stop() been called? */
     bool running;               /* should iothread_run() continue? */
     int thread_id;
+    /*
+     * The list elements are of type IOThreadHolder, which can
+     * represent either a QOM path or a block node name.
+     */
+    GList *holders;
 
     /* AioContext poll parameters */
     int64_t poll_max_ns;
diff --git a/iothread.c b/iothread.c
index 3558535b40..3301b8d495 100644
--- a/iothread.c
+++ b/iothread.c
@@ -25,6 +25,92 @@
 #include "qemu/rcu.h"
 #include "qemu/main-loop.h"
 
+/*
+ * iothread_ref:
+ * @iothread: the iothread to track
+ * @holder: the IOThreadHolder object initialized by the caller
+ *
+ * Add the @holder to the iothread's tracking list.
+ */
+static void iothread_ref(IOThread *iothread, const IOThreadHolder *holder)
+{
+    assert(holder);
+    IOThreadHolder *h = g_new0(IOThreadHolder, 1);
+
+    h->type = holder->type;
+    switch (holder->type) {
+    case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
+        h->u.qom_object.qom_path = g_strdup(holder->u.qom_object.qom_path);
+        break;
+    case IO_THREAD_HOLDER_KIND_BLOCK_NODE:
+        h->u.block_node.node_name =
+            g_strdup(holder->u.block_node.node_name);
+        break;
+    case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
+        h->u.monitor_name.monitor_name =
+            g_strdup(holder->u.monitor_name.monitor_name);
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    iothread->holders = g_list_prepend(iothread->holders, h);
+}
+
+static int iothread_holder_compare(gconstpointer a, gconstpointer b)
+{
+    const IOThreadHolder *holder_a = a;
+    const IOThreadHolder *holder_b = b;
+    const char *name_a, *name_b;
+
+    if (holder_a->type != holder_b->type) {
+        return -1;
+    }
+
+    switch (holder_a->type) {
+    case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
+        name_a = holder_a->u.qom_object.qom_path;
+        name_b = holder_b->u.qom_object.qom_path;
+        break;
+    case IO_THREAD_HOLDER_KIND_BLOCK_NODE:
+        name_a = holder_a->u.block_node.node_name;
+        name_b = holder_b->u.block_node.node_name;
+        break;
+    case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
+        name_a = holder_a->u.monitor_name.monitor_name;
+        name_b = holder_b->u.monitor_name.monitor_name;
+        break;
+    default:
+       /*
+        * This should not happen. If it does, name_a/b remains
+        * NULL and g_strcmp0 will handle it safely.
+        */
+        name_a = NULL;
+        name_b = NULL;
+    }
+
+    return g_strcmp0(name_a, name_b);
+}
+
+/*
+ * This function removes the @holder from the @iothread's tracking list.
+ * The @holder must match the one used previously in iothread_ref().
+ * It is a programming error to call this with a @holder that is not
+ * currently associated with the @iothread.
+ */
+static void iothread_unref(IOThread *iothread, const IOThreadHolder *holder)
+{
+    assert(holder);
+    GList *link = g_list_find_custom(iothread->holders, holder,
+                                     (GCompareFunc)iothread_holder_compare);
+
+    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;
@@ -356,6 +442,14 @@ char *iothread_get_id(IOThread *iothread)
 
 AioContext *iothread_get_aio_context(IOThread *iothread)
 {
+    /* Remove in next patch for build */
+    IOThreadHolder holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = (char *)"tmp_path",
+    };
+    iothread_ref(iothread, &holder);
+    iothread_unref(iothread, &holder);
+
     return iothread->ctx;
 }
 
diff --git a/qapi/misc.json b/qapi/misc.json
index c71a5fe657..d9f82f0922 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -67,6 +67,67 @@
 ##
 { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
 
+
+##
+# @IOThreadHolderBlockNode:
+#
+# @node-name: A block node.
+#
+# Since: 11.1
+#
+##
+{ 'struct': 'IOThreadHolderBlockNode',
+  'data': { 'node-name': 'str' } }
+
+##
+# @IOThreadHolderQomObject:
+#
+# @qom-path: A QOM Object.
+#
+# Since: 11.1
+#
+##
+{ 'struct': 'IOThreadHolderQomObject',
+  'data': { 'qom-path': 'str' } }
+
+##
+# @IOThreadHolderMonitor:
+#
+# @monitor-name: A HMP/QMP monitor.
+#
+# Since: 11.1
+#
+##
+{ 'struct': 'IOThreadHolderMonitor',
+  'data': { 'monitor-name': 'str' } }
+
+##
+# @IOThreadHolderKind:
+#
+# @block-node: A block node.
+# @qom-object: A QOM Object.
+# @monitor-name: A HMP/QMP monitor.
+#
+# Since: 11.1
+##
+{ 'enum': 'IOThreadHolderKind',
+  'data': [ 'block-node', 'qom-object', 'monitor-name' ] }
+
+##
+# @IOThreadHolder:
+#
+# @type: the kind of I/O thread holder.
+#
+# Since: 11.1
+##
+{ 'union': 'IOThreadHolder',
+  'base': { 'type': 'IOThreadHolderKind' },
+  'discriminator': 'type',
+  'data': {
+    'block-node': 'IOThreadHolderBlockNode',
+    'qom-object': 'IOThreadHolderQomObject',
+    'monitor-name': 'IOThreadHolderMonitor' } }
+
 ##
 # @IOThreadInfo:
 #
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 03/15] iothread: tracking iothread users with holder name
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 01/15] qapi/misc: Fix missed query-iothreads items Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 02/15] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 04/15] iothread: introduce iothread_unsafe_get_aio_context() Zhang Chen
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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() (In this patch named
"iothread_ref_and_get_aio_context" for build, will change the name
in the last patch) 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 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 |  4 ++++
 iothread.c                | 31 +++++++++++++++++++++++--------
 2 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/include/system/iothread.h b/include/system/iothread.h
index b9207ad829..b483bbfab3 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -18,6 +18,7 @@
 #include "qemu/thread.h"
 #include "qom/object.h"
 #include "system/event-loop-base.h"
+#include "qapi/qapi-types-misc.h"
 
 #define TYPE_IOTHREAD "iothread"
 
@@ -70,6 +71,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 IOThreadHolder *holder);
+void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder);
 GMainContext *iothread_get_g_main_context(IOThread *iothread);
 
 /*
diff --git a/iothread.c b/iothread.c
index 3301b8d495..528002c34a 100644
--- a/iothread.c
+++ b/iothread.c
@@ -55,6 +55,12 @@ static void iothread_ref(IOThread *iothread, const IOThreadHolder *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)
@@ -109,6 +115,8 @@ static void iothread_unref(IOThread *iothread, const IOThreadHolder *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)
@@ -223,7 +231,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);
@@ -442,17 +450,24 @@ char *iothread_get_id(IOThread *iothread)
 
 AioContext *iothread_get_aio_context(IOThread *iothread)
 {
-    /* Remove in next patch for build */
-    IOThreadHolder holder = {
-        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
-        .u.qom_object.qom_path = (char *)"tmp_path",
-    };
-    iothread_ref(iothread, &holder);
-    iothread_unref(iothread, &holder);
+    return iothread->ctx;
+}
+
+AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
+                                             const IOThreadHolder *holder)
+{
+    /* Add IOThreadHolder to the list */
+    iothread_ref(iothread, holder);
 
     return iothread->ctx;
 }
 
+void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder)
+{
+    /* Delete IOThreadHolder 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] 25+ messages in thread

* [PATCH V8 04/15] iothread: introduce iothread_unsafe_get_aio_context()
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (2 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 03/15] iothread: tracking iothread users with holder name Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 05/15] block/export: track IOThread reference in BlockExport Zhang Chen
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 UTC (permalink / raw)
  To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
  Cc: Zhang Chen

The iothread_unsafe_get_aio_context() is a low-level unsafe way of getting
the AioContext, recommend migrating to the new API with IOThreadHolder
as much as possible.

The blockdev is the first user:
Every BlockDriverState would need to get/put the IOThread AioContext in
bdrv_change_aio_context() or related functions.
However, the QEMU block layer has been moving away from having a
per-BlockDriverState AioContext. It is possible to use a
BlockDriverState from any AioContext (including multiple AioContexts at
the same time). I'm in favor of not associating BlockDriverStates with
IOThreads and instead relying on their device owners (e.g. emulated
storage controllers) to be the IOThread holders.

Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 blockdev.c                | 2 +-
 include/system/iothread.h | 6 ++++++
 iothread.c                | 6 ++++++
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/blockdev.c b/blockdev.c
index 6e86c6262f..baeab3a3e1 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_get_aio_context(obj);
+        new_context = iothread_unsafe_get_aio_context(obj);
     } else {
         new_context = qemu_get_aio_context();
     }
diff --git a/include/system/iothread.h b/include/system/iothread.h
index b483bbfab3..064c05e78d 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -71,6 +71,12 @@ 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);
+/*
+ * The iothread_unsafe_get_aio_context() is a low-level unsafe way of getting
+ * the AioContext, recommend migrating to the new API with IOThreadHolder
+ * as much as possible.
+ */
+AioContext *iothread_unsafe_get_aio_context(IOThread *iothread);
 AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
                                              const IOThreadHolder *holder);
 void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder);
diff --git a/iothread.c b/iothread.c
index 528002c34a..a85e960e45 100644
--- a/iothread.c
+++ b/iothread.c
@@ -468,6 +468,12 @@ void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder)
     iothread_unref(iothread, holder);
 }
 
+/* Recommend migrating to the new API with IOThreadHolder as much as possible */
+AioContext *iothread_unsafe_get_aio_context(IOThread *iothread)
+{
+    return iothread->ctx;
+}
+
 static int query_one_iothread(Object *object, void *opaque)
 {
     IOThreadInfoList ***tail = opaque;
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 05/15] block/export: track IOThread reference in BlockExport
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (3 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 04/15] iothread: introduce iothread_unsafe_get_aio_context() Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors Zhang Chen
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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  | 63 ++++++++++++++++++++++++++++++++++++------
 include/block/export.h |  6 ++++
 2 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/block/export/export.c b/block/export/export.c
index b733f269f3..b6c07f69b5 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,16 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
             goto fail;
         }
 
-        new_ctx = iothread_get_aio_context(iothread);
+        holder_name = bdrv_get_node_name(bs);
+        IOThreadHolder holder = {
+            .type = IO_THREAD_HOLDER_KIND_BLOCK_NODE,
+            .u.block_node.node_name = (char *)holder_name,
+        };
+
+        new_ctx = iothread_ref_and_get_aio_context(iothread, &holder);
+        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 +173,15 @@ 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);
+        IOThreadHolder holder = {
+            .type = IO_THREAD_HOLDER_KIND_BLOCK_NODE,
+            .u.block_node.node_name = (char *)holder_name,
+        };
+
         for (strList *e = iothread_list; e; e = e->next) {
             IOThread *iothread = iothread_by_id(e->value);
 
@@ -172,7 +189,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);
         }
         assert(i == multithread_count);
     }
@@ -225,12 +244,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 +275,18 @@ fail:
         g_free(exp->id);
         g_free(exp);
     }
+    if (local_iothreads) {
+        IOThreadHolder holder = {
+            .type = IO_THREAD_HOLDER_KIND_BLOCK_NODE,
+            .u.block_node.node_name = (char *)holder_name,
+        };
+
+        for (size_t j = 0; j < multithread_count; j++) {
+            if (local_iothreads[j]) {
+                iothread_put_aio_context(local_iothreads[j], &holder);
+            }
+        }
+    }
     g_free(multithread_ctxs);
     return NULL;
 }
@@ -269,6 +303,17 @@ static void blk_exp_delete_bh(void *opaque)
     BlockExport *exp = opaque;
 
     assert(exp->refcount == 0);
+    if (exp->iothreads) {
+        IOThreadHolder holder = {
+            .type = IO_THREAD_HOLDER_KIND_BLOCK_NODE,
+            .u.block_node.node_name = (char *)exp->iothread_holder_name,
+        };
+
+        for (size_t i = 0; i < exp->iothread_count; i++) {
+            iothread_put_aio_context(exp->iothreads[i], &holder);
+        }
+        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] 25+ messages in thread

* [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (4 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 05/15] block/export: track IOThread reference in BlockExport Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-06-02 17:02   ` Markus Armbruster
  2026-05-29 21:33 ` [PATCH V8 07/15] monitor: Update tracking iothread users with holder Zhang Chen
                   ` (8 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 UTC (permalink / raw)
  To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
  Cc: Zhang Chen

Currently, dynamically or implicitly created monitors (such as those
generated via '-monitor stdio', GDB stub terminals, or mux chardevs)
leave the 'id' field in the Monitor struct as NULL. But the original
@MonitorOptions have the @id field. This makes it difficult to track,
debug, or associate monitors with specific iothreads or
infrastructure components.

Fix this by introducing monitor_get_id(), which uses an atomic counter
to generate a unique fallback name ("mon_default_X") when 'id' is
not explicitly supplied in MonitorOptions. Update all internal HMP
and QMP initialization paths to propagate and store this string.

Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 chardev/char.c             |  2 +-
 gdbstub/system.c           |  3 ++-
 include/monitor/monitor.h  |  6 ++++--
 monitor/hmp.c              | 20 +++++++++++++++++++-
 monitor/monitor-internal.h |  2 ++
 monitor/monitor.c          |  4 ++--
 monitor/qmp.c              | 18 +++++++++++++++++-
 stubs/monitor-internal.c   |  3 ++-
 8 files changed, 49 insertions(+), 9 deletions(-)

diff --git a/chardev/char.c b/chardev/char.c
index ca8b37ed8d..f057247001 100644
--- a/chardev/char.c
+++ b/chardev/char.c
@@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
 
     if (qemu_opt_get_bool(opts, "mux", 0)) {
         assert(permit_mux_mon);
-        monitor_init_hmp(chr, true, &err);
+        monitor_init_hmp(chr, true, NULL, &err);
         if (err) {
             error_report_err(err);
             object_unparent(OBJECT(chr));
diff --git a/gdbstub/system.c b/gdbstub/system.c
index e86c5870ab..50f934fde3 100644
--- a/gdbstub/system.c
+++ b/gdbstub/system.c
@@ -388,7 +388,8 @@ bool gdbserver_start(const char *device, Error **errp)
         /* Initialize a monitor terminal for gdb */
         mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
                                    NULL, NULL, &error_abort);
-        monitor_init_hmp(mon_chr, false, &error_abort);
+
+        monitor_init_hmp(mon_chr, false, NULL, &error_abort);
     } else {
         qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
         mon_chr = gdbserver_system_state.mon_chr;
diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
index 55649a8664..c6ccd34cda 100644
--- a/include/monitor/monitor.h
+++ b/include/monitor/monitor.h
@@ -19,11 +19,13 @@ bool monitor_cur_is_qmp(void);
 
 void monitor_init_globals(void);
 void monitor_init_globals_core(void);
-void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp);
-void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp);
+void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, Error **errp);
+void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
+                      Error **errp);
 int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp);
 int monitor_init_opts(QemuOpts *opts, Error **errp);
 void monitor_cleanup(void);
+char *monitor_get_id(void);
 
 int monitor_suspend(Monitor *mon);
 void monitor_resume(Monitor *mon);
diff --git a/monitor/hmp.c b/monitor/hmp.c
index cc4390486e..47f57f69e5 100644
--- a/monitor/hmp.c
+++ b/monitor/hmp.c
@@ -43,6 +43,8 @@
 #include "system/block-backend.h"
 #include "trace.h"
 
+static int mon_hmp_id_counter;
+
 static void monitor_command_cb(void *opaque, const char *cmdline,
                                void *readline_opaque)
 {
@@ -65,6 +67,14 @@ void monitor_read_command(MonitorHMP *mon, int show_prompt)
     }
 }
 
+static char *monitor_hmp_get_id(void)
+{
+    int id = qatomic_fetch_inc(&mon_hmp_id_counter);
+    char *name = g_strdup_printf("mon_default_hmp_%d", id);
+
+    return name;
+}
+
 int monitor_read_password(MonitorHMP *mon, ReadLineFunc *readline_func,
                           void *opaque)
 {
@@ -1522,10 +1532,18 @@ static void monitor_readline_flush(void *opaque)
     monitor_flush(&mon->common);
 }
 
-void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
+void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
+                      Error **errp)
 {
     MonitorHMP *mon = g_new0(MonitorHMP, 1);
 
+    if (!id) {
+        g_autofree char *mon_id =  monitor_hmp_get_id();
+        mon->common.id = g_strdup(mon_id);
+    } else {
+        mon->common.id = g_strdup(id);
+    }
+
     if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
         g_free(mon);
         return;
diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index a5c4aba306..2060311b03 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -108,6 +108,8 @@ struct Monitor {
     bool skip_flush;
     bool use_io_thread;
 
+    char *id;
+
     char *mon_cpu_path;
     QTAILQ_ENTRY(Monitor) entry;
 
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 00b93ed612..dafb4ad8b0 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -732,7 +732,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
 
     switch (opts->mode) {
     case MONITOR_MODE_CONTROL:
-        monitor_init_qmp(chr, opts->pretty, errp);
+        monitor_init_qmp(chr, opts->pretty, opts->id, errp);
         break;
     case MONITOR_MODE_READLINE:
         if (!allow_hmp) {
@@ -743,7 +743,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
             error_setg(errp, "'pretty' is not compatible with HMP monitors");
             return -1;
         }
-        monitor_init_hmp(chr, true, errp);
+        monitor_init_hmp(chr, true, opts->id, errp);
         break;
     default:
         g_assert_not_reached();
diff --git a/monitor/qmp.c b/monitor/qmp.c
index 687019811f..b210850a15 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -56,6 +56,7 @@
  * Access must be atomic for thread safety.
  */
 static bool qmp_dispatcher_co_busy = true;
+static int mon_qmp_id_counter;
 
 struct QMPRequest {
     /* Owner of the request */
@@ -76,6 +77,14 @@ static bool qmp_oob_enabled(MonitorQMP *mon)
     return mon->capab[QMP_CAPABILITY_OOB];
 }
 
+static char *monitor_qmp_get_id(void)
+{
+    int id = qatomic_fetch_inc(&mon_qmp_id_counter);
+    char *name = g_strdup_printf("mon_default_qmp_%d", id);
+
+    return name;
+}
+
 static void monitor_qmp_caps_reset(MonitorQMP *mon)
 {
     memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
@@ -513,10 +522,17 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     monitor_list_append(&mon->common);
 }
 
-void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
+void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, Error **errp)
 {
     MonitorQMP *mon = g_new0(MonitorQMP, 1);
 
+    if (!id) {
+        g_autofree char *mon_id =  monitor_qmp_get_id();
+        mon->common.id = g_strdup(mon_id);
+    } else {
+        mon->common.id = g_strdup(id);
+    }
+
     if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
         g_free(mon);
         return;
diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
index 4fece49d53..325a559e62 100644
--- a/stubs/monitor-internal.c
+++ b/stubs/monitor-internal.c
@@ -8,6 +8,7 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
     return -1;
 }
 
-void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
+void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
+                      Error **errp)
 {
 }
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 07/15] monitor: Update tracking iothread users with holder
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (5 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-06-02 17:20   ` Markus Armbruster
  2026-05-29 21:33 ` [PATCH V8 08/15] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 UTC (permalink / raw)
  To: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Markus Armbruster, Michael S . Tsirkin, Stefan Hajnoczi
  Cc: Zhang Chen

Based on monitor ID tracking iothread users with holder.
Introduce 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          | 20 ++++++++++++++++++--
 monitor/qmp.c              |  3 ++-
 3 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 2060311b03..92d7630921 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -127,6 +127,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 dafb4ad8b0..7eec6f967d 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,12 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
 {
     if (use_io_thread && !mon_iothread) {
         monitor_iothread_init();
+
+        IOThreadHolder holder = {
+            .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
+            .u.monitor_name.monitor_name = (char *)mon->id,
+        };
+        mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, &holder);
     }
     qemu_mutex_init(&mon->mon_lock);
     mon->is_qmp = is_qmp;
@@ -631,6 +637,16 @@ void monitor_data_destroy(Monitor *mon)
     }
     g_string_free(mon->outbuf, true);
     qemu_mutex_destroy(&mon->mon_lock);
+
+    if (mon->ctx && mon_iothread) {
+        IOThreadHolder holder = {
+            .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
+            .u.monitor_name.monitor_name = (char *)mon->id,
+        };
+
+        iothread_put_aio_context(mon_iothread, &holder);
+        mon->ctx = NULL;
+    }
 }
 
 void monitor_cleanup(void)
diff --git a/monitor/qmp.c b/monitor/qmp.c
index b210850a15..b67674aef3 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -565,7 +565,8 @@ void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, 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] 25+ messages in thread

* [PATCH V8 08/15] virtio-vq-mapping: track iothread-vq-mapping references using device path
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (6 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 07/15] monitor: Update tracking iothread users with holder Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 09/15] virtio: use iothread_get/put_aio_context for thread pinning Zhang Chen
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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         | 20 +++++++++++++++-----
 include/hw/virtio/iothread-vq-mapping.h |  6 +++++-
 4 files changed, 34 insertions(+), 9 deletions(-)

diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index 9cb9f1fb2b..30e9fcf870 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1487,9 +1487,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;
@@ -1521,7 +1524,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..2cb48dd387 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,13 @@ 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);
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)holder,
+        };
 
-        /* Released in virtio_blk_vq_aio_context_cleanup() */
-        object_ref(OBJECT(iothread));
+        AioContext *ctx = iothread_ref_and_get_aio_context(iothread,
+                                                           &io_holder);
 
         if (node->value->vqs) {
             uint16List *vq;
@@ -120,13 +124,19 @@ 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));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)holder,
+        };
+
+        iothread_put_aio_context(iothread, &io_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] 25+ messages in thread

* [PATCH V8 09/15] virtio: use iothread_get/put_aio_context for thread pinning
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (7 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 08/15] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 10/15] net/colo: track IOThread references using path-based holder Zhang Chen
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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           | 24 +++++++++++++-----------
 hw/scsi/virtio-scsi-dataplane.c | 23 +++++++++++++----------
 2 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index 30e9fcf870..d8dc1dd136 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1463,6 +1463,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,
@@ -1487,8 +1488,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,
@@ -1499,13 +1498,15 @@ 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);
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+        AioContext *ctx = iothread_ref_and_get_aio_context(conf->iothread,
+                                                           &io_holder);
         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++) {
@@ -1520,21 +1521,22 @@ 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));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+        iothread_put_aio_context(conf->iothread, &io_holder);
     }
 
-    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..c71f33b41e 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,15 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
             return;
         }
     } else if (vs->conf.iothread) {
-        AioContext *ctx = iothread_get_aio_context(vs->conf.iothread);
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+        AioContext *ctx = iothread_ref_and_get_aio_context(vs->conf.iothread,
+                                                           &io_holder);
         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 +95,18 @@ 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));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+        iothread_put_aio_context(vs->conf.iothread, &io_holder);
     }
 
     g_free(s->vq_aio_context);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 10/15] net/colo: track IOThread references using path-based holder
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (8 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 09/15] virtio: use iothread_get/put_aio_context for thread pinning Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 11/15] virtio-balloon: Update tracking iothread users with holder Zhang Chen
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/net/colo-compare.c b/net/colo-compare.c
index 823b8aa323..4f180936e3 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,15 @@ 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));
+    IOThreadHolder io_holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = (char *)path,
+    };
+
+    AioContext *ctx = iothread_ref_and_get_aio_context(s->iothread, &io_holder);
+
+    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 +1414,7 @@ static void colo_compare_finalize(Object *obj)
 {
     CompareState *s = COLO_COMPARE(obj);
     CompareState *tmp;
+    g_autofree char *path = object_get_canonical_path(OBJECT(s));
 
     qemu_mutex_lock(&colo_compare_mutex);
     QTAILQ_FOREACH(tmp, &net_compares, next) {
@@ -1434,18 +1441,20 @@ static void colo_compare_finalize(Object *obj)
     g_clear_pointer(&s->event_bh, qemu_bh_delete);
 
     if (s->iothread) {
-        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);
         }
 
         /* Release all unhandled packets after compare thread exited */
         g_queue_foreach(&s->conn_list, colo_flush_packets, s);
         AIO_WAIT_WHILE(NULL, !s->out_sendco.done);
 
-        object_unref(OBJECT(s->iothread));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+        iothread_put_aio_context(s->iothread, &io_holder);
     }
 
     g_queue_clear(&s->conn_list);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 11/15] virtio-balloon: Update tracking iothread users with holder
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (9 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 10/15] net/colo: track IOThread references using path-based holder Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 12/15] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index 4c5f486ba2..e9d023b4ec 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -892,14 +892,20 @@ 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));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+
         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, &io_holder),
+                      virtio_ballloon_get_free_page_hints, s,
+                      &dev->mem_reentrancy_guard);
     }
 
     if (virtio_has_feature(s->host_features, VIRTIO_BALLOON_F_REPORTING)) {
@@ -919,9 +925,15 @@ 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));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+
         qemu_bh_delete(s->free_page_bh);
-        object_unref(OBJECT(s->iothread));
         virtio_balloon_free_page_stop(s);
+        iothread_put_aio_context(s->iothread, &io_holder);
         precopy_remove_notifier(&s->free_page_hint_notify);
     }
     balloon_stats_destroy_timer(s);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 12/15] vfio-user/proxy: Update tracking iothread users with holder name
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (10 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 11/15] virtio-balloon: Update tracking iothread users with holder Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 13/15] xen-block: " Zhang Chen
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/hw/vfio-user/proxy.c b/hw/vfio-user/proxy.c
index 314dfd23d8..756f368849 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,11 @@ 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));
+    IOThreadHolder io_holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = (char *)path,
+    };
 
     /* init defaults */
     proxy->max_xfer_size = VFIO_USER_DEF_MAX_XFER;
@@ -936,7 +942,8 @@ 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,
+                                                  &io_holder);
     proxy->req_bh = qemu_bh_new(vfio_user_request, proxy);
 
     QTAILQ_INIT(&proxy->outgoing);
@@ -967,6 +974,11 @@ 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));
+    IOThreadHolder io_holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = (char *)path,
+    };
 
     qemu_mutex_lock(&proxy->lock);
 
@@ -1021,6 +1033,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, &io_holder);
+
     QLIST_REMOVE(proxy, next);
     if (QLIST_EMPTY(&vfio_user_sockets)) {
         iothread_destroy(vfio_user_iothread);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 13/15] xen-block: Update tracking iothread users with holder name
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (11 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 12/15] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
  2026-05-29 21:33 ` [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants Zhang Chen
  14 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/hw/block/dataplane/xen-block.c b/hw/block/dataplane/xen-block.c
index 48c2e315f3..b5bf8d359f 100644
--- a/hw/block/dataplane/xen-block.c
+++ b/hw/block/dataplane/xen-block.c
@@ -621,9 +621,15 @@ XenBlockDataPlane *xen_block_dataplane_create(XenDevice *xendev,
     QLIST_INIT(&dataplane->freelist);
 
     if (iothread) {
+        g_autofree char *path = object_get_canonical_path(OBJECT(xendev));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+
         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,
+                                                          &io_holder);
     } else {
         dataplane->ctx = qemu_get_aio_context();
     }
@@ -652,7 +658,14 @@ 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));
+        IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)path,
+        };
+
+        iothread_put_aio_context(dataplane->iothread, &io_holder);
     }
 
     g_free(dataplane);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (12 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 13/15] xen-block: " Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-06-02 17:34   ` Markus Armbruster
  2026-05-29 21:33 ` [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants Zhang Chen
  14 siblings, 1 reply; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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 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.  (Since 11.1)

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 | 26 ++++++++++++++++++++++++++
 qapi/misc.json     | 11 +++++++++++
 3 files changed, 59 insertions(+)

diff --git a/iothread.c b/iothread.c
index a85e960e45..5949785b32 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"
 
 /*
  * iothread_ref:
@@ -119,6 +121,25 @@ static void iothread_unref(IOThread *iothread, const IOThreadHolder *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;
@@ -488,6 +509,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 443b8c785d..1b5883147b 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -237,11 +237,37 @@ 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.node_name);
+                    break;
+                case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
+                    monitor_printf(mon, "[qom-path: %s]",
+                                   holder->u.qom_object.qom_path);
+                    break;
+                case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
+                    monitor_printf(mon, "[monitor-name: %s]",
+                                   holder->u.monitor_name.monitor_name);
+                    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 d9f82f0922..4c16be3fd4 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -137,6 +137,13 @@
 #
 # @thread-id: ID of the underlying host thread
 #
+# @holders: IOThreadHolder 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.  (Since 11.1)
+#
 # @poll-max-ns: maximum polling time in ns, 0 means polling is
 #     disabled (since 2.9)
 #
@@ -159,6 +166,7 @@
 { 'struct': 'IOThreadInfo',
   'data': {'id': 'str',
            'thread-id': 'int',
+           'holders': ['IOThreadHolder'],
            'poll-max-ns': 'int',
            'poll-grow': 'int',
            'poll-shrink': 'int',
@@ -185,6 +193,8 @@
 #              {
 #                 "id":"iothread0",
 #                 "thread-id":3134,
+#                 "holders":[{"qom-path": "/machine/peripheral/blk1/virtio-backend", "type": "qom-object"},
+#                            {"qom-path": "/machine/peripheral/blk0/virtio-backend", "type": "qom-object"}],
 #                 "poll-max-ns":32768,
 #                 "poll-grow":0,
 #                 "poll-shrink":0,
@@ -193,6 +203,7 @@
 #              {
 #                 "id":"iothread1",
 #                 "thread-id":3135,
+#                 "holders":[{"qom-path": "/machine/peripheral/blk2/virtio-backend", "type": "qom-object"}],
 #                 "poll-max-ns":32768,
 #                 "poll-grow":0,
 #                 "poll-shrink":0,
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants
  2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
                   ` (13 preceding siblings ...)
  2026-05-29 21:33 ` [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
@ 2026-05-29 21:33 ` Zhang Chen
  2026-06-02 17:41   ` Markus Armbruster
  14 siblings, 1 reply; 25+ messages in thread
From: Zhang Chen @ 2026-05-29 21:33 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           | 5 ++---
 hw/block/dataplane/xen-block.c  | 4 ++--
 hw/block/virtio-blk.c           | 4 ++--
 hw/scsi/virtio-scsi-dataplane.c | 4 ++--
 hw/vfio-user/proxy.c            | 3 +--
 hw/virtio/iothread-vq-mapping.c | 3 +--
 hw/virtio/virtio-balloon.c      | 2 +-
 include/system/iothread.h       | 6 +++---
 iothread.c                      | 9 ++-------
 monitor/monitor.c               | 2 +-
 net/colo-compare.c              | 2 +-
 11 files changed, 18 insertions(+), 26 deletions(-)

diff --git a/block/export/export.c b/block/export/export.c
index b6c07f69b5..acd061e86e 100644
--- a/block/export/export.c
+++ b/block/export/export.c
@@ -146,7 +146,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
             .u.block_node.node_name = (char *)holder_name,
         };
 
-        new_ctx = iothread_ref_and_get_aio_context(iothread, &holder);
+        new_ctx = iothread_get_aio_context(iothread, &holder);
         multithread_count = 1;
         local_iothreads = g_new0(IOThread *, 1);
         local_iothreads[0] = iothread;
@@ -190,8 +190,7 @@ 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);
+            multithread_ctxs[i++] = iothread_get_aio_context(iothread, &holder);
         }
         assert(i == multithread_count);
     }
diff --git a/hw/block/dataplane/xen-block.c b/hw/block/dataplane/xen-block.c
index b5bf8d359f..46bfd62f5a 100644
--- a/hw/block/dataplane/xen-block.c
+++ b/hw/block/dataplane/xen-block.c
@@ -628,8 +628,8 @@ XenBlockDataPlane *xen_block_dataplane_create(XenDevice *xendev,
         };
 
         dataplane->iothread = iothread;
-        dataplane->ctx = iothread_ref_and_get_aio_context(dataplane->iothread,
-                                                          &io_holder);
+        dataplane->ctx = iothread_get_aio_context(dataplane->iothread,
+                                                  &io_holder);
     } else {
         dataplane->ctx = qemu_get_aio_context();
     }
diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index d8dc1dd136..d1d2bd7025 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1502,8 +1502,8 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
             .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
             .u.qom_object.qom_path = (char *)path,
         };
-        AioContext *ctx = iothread_ref_and_get_aio_context(conf->iothread,
-                                                           &io_holder);
+        AioContext *ctx = iothread_get_aio_context(conf->iothread,
+                                                   &io_holder);
         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 c71f33b41e..76ab22610f 100644
--- a/hw/scsi/virtio-scsi-dataplane.c
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -78,8 +78,8 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
             .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
             .u.qom_object.qom_path = (char *)path,
         };
-        AioContext *ctx = iothread_ref_and_get_aio_context(vs->conf.iothread,
-                                                           &io_holder);
+        AioContext *ctx = iothread_get_aio_context(vs->conf.iothread,
+                                                   &io_holder);
         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 756f368849..24af3c791b 100644
--- a/hw/vfio-user/proxy.c
+++ b/hw/vfio-user/proxy.c
@@ -942,8 +942,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,
-                                                  &io_holder);
+    proxy->ctx = iothread_get_aio_context(vfio_user_iothread, &io_holder);
     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 2cb48dd387..727358a483 100644
--- a/hw/virtio/iothread-vq-mapping.c
+++ b/hw/virtio/iothread-vq-mapping.c
@@ -99,8 +99,7 @@ bool iothread_vq_mapping_apply(
             .u.qom_object.qom_path = (char *)holder,
         };
 
-        AioContext *ctx = iothread_ref_and_get_aio_context(iothread,
-                                                           &io_holder);
+        AioContext *ctx = iothread_get_aio_context(iothread, &io_holder);
 
         if (node->value->vqs) {
             uint16List *vq;
diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index e9d023b4ec..4405a87ed0 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -903,7 +903,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, &io_holder),
+                      iothread_get_aio_context(s->iothread, &io_holder),
                       virtio_ballloon_get_free_page_hints, s,
                       &dev->mem_reentrancy_guard);
     }
diff --git a/include/system/iothread.h b/include/system/iothread.h
index 064c05e78d..e5db215954 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -70,15 +70,15 @@ 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);
+
 /*
  * The iothread_unsafe_get_aio_context() is a low-level unsafe way of getting
  * the AioContext, recommend migrating to the new API with IOThreadHolder
  * as much as possible.
  */
 AioContext *iothread_unsafe_get_aio_context(IOThread *iothread);
-AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
-                                             const IOThreadHolder *holder);
+AioContext *iothread_get_aio_context(IOThread *iothread,
+                                     const IOThreadHolder *holder);
 void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder);
 GMainContext *iothread_get_g_main_context(IOThread *iothread);
 
diff --git a/iothread.c b/iothread.c
index 5949785b32..9bc8e359c1 100644
--- a/iothread.c
+++ b/iothread.c
@@ -469,13 +469,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 IOThreadHolder *holder)
+AioContext *iothread_get_aio_context(IOThread *iothread,
+                                     const IOThreadHolder *holder)
 {
     /* Add IOThreadHolder to the list */
     iothread_ref(iothread, holder);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 7eec6f967d..4d00cf499c 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -617,7 +617,7 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
             .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
             .u.monitor_name.monitor_name = (char *)mon->id,
         };
-        mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, &holder);
+        mon->ctx = iothread_get_aio_context(mon_iothread, &holder);
     }
     qemu_mutex_init(&mon->mon_lock);
     mon->is_qmp = is_qmp;
diff --git a/net/colo-compare.c b/net/colo-compare.c
index 4f180936e3..820ea4d824 100644
--- a/net/colo-compare.c
+++ b/net/colo-compare.c
@@ -973,7 +973,7 @@ static void colo_compare_iothread(CompareState *s)
         .u.qom_object.qom_path = (char *)path,
     };
 
-    AioContext *ctx = iothread_ref_and_get_aio_context(s->iothread, &io_holder);
+    AioContext *ctx = iothread_get_aio_context(s->iothread, &io_holder);
 
     s->iothread_ctx = ctx;
     s->worker_context = iothread_get_g_main_context(s->iothread);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 02/15] iothread: introduce iothread_ref/unref to track attached devices
  2026-05-29 21:33 ` [PATCH V8 02/15] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
@ 2026-06-02 11:29   ` Markus Armbruster
  0 siblings, 0 replies; 25+ messages in thread
From: Markus Armbruster @ 2026-06-02 11:29 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Markus Armbruster, 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 IOThreadHolder to a list.
> - iothread_unref(): Searches for the IOThreadHolder using a custom
>   string comparison (g_strcmp0), releases the associated memory
>   upon a successful match.
> - holders: A GList storing the IOThreadHolder of attached devices
>   for runtime introspection.
>
> 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 |  5 +++
>  iothread.c                | 94 +++++++++++++++++++++++++++++++++++++++
>  qapi/misc.json            | 61 +++++++++++++++++++++++++
>  3 files changed, 160 insertions(+)
>
> diff --git a/include/system/iothread.h b/include/system/iothread.h
> index a1ef7696cb..b9207ad829 100644
> --- a/include/system/iothread.h
> +++ b/include/system/iothread.h
> @@ -50,6 +50,11 @@ struct IOThread {
>      bool stopping;              /* has iothread_stop() been called? */
>      bool running;               /* should iothread_run() continue? */
>      int thread_id;
> +    /*
> +     * The list elements are of type IOThreadHolder, which can
> +     * represent either a QOM path or a block node name.

You forgot monitors.

Suggest "which can either be a QOM object, a block node, or a monitor."

> +     */
> +    GList *holders;
>  
>      /* AioContext poll parameters */
>      int64_t poll_max_ns;
> diff --git a/iothread.c b/iothread.c
> index 3558535b40..3301b8d495 100644
> --- a/iothread.c
> +++ b/iothread.c
> @@ -25,6 +25,92 @@
>  #include "qemu/rcu.h"
>  #include "qemu/main-loop.h"
>  
> +/*
> + * iothread_ref:
> + * @iothread: the iothread to track
> + * @holder: the IOThreadHolder object initialized by the caller
> + *
> + * Add the @holder to the iothread's tracking list.
> + */
> +static void iothread_ref(IOThread *iothread, const IOThreadHolder *holder)

Yes, this is the interface I want :)

> +{
> +    assert(holder);
> +    IOThreadHolder *h = g_new0(IOThreadHolder, 1);
> +
> +    h->type = holder->type;
> +    switch (holder->type) {
> +    case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
> +        h->u.qom_object.qom_path = g_strdup(holder->u.qom_object.qom_path);
> +        break;
> +    case IO_THREAD_HOLDER_KIND_BLOCK_NODE:
> +        h->u.block_node.node_name =
> +            g_strdup(holder->u.block_node.node_name);
> +        break;
> +    case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
> +        h->u.monitor_name.monitor_name =
> +            g_strdup(holder->u.monitor_name.monitor_name);
> +        break;
> +    default:
> +        g_assert_not_reached();
> +    }

This makes @h a deep copy of @holder.  Try

    IOThreadHolder *h = QAPI_CLONE(IOThreadHolder, holder);

instead.

Instead of making a copy, the function could take ownership of @holder,
and require @holder's members to be dynamically allocated.  More
efficient, but complicates the callers somewhat.  Not worthwhile, I
guess.

> +
> +    iothread->holders = g_list_prepend(iothread->holders, h);
> +}
> +
> +static int iothread_holder_compare(gconstpointer a, gconstpointer b)
> +{
> +    const IOThreadHolder *holder_a = a;
> +    const IOThreadHolder *holder_b = b;
> +    const char *name_a, *name_b;
> +
> +    if (holder_a->type != holder_b->type) {
> +        return -1;

Please use

           return holder_b->type - holder_a->type;

Why?  By convention, an int-valued function fn() comparing A to B
returns less than 0 when A<B, 0 when A == B, and greater than 0 when
A>B.  Anything else needs a prominent comment.  Best to stick to the
convention whenever practical.

Since A < B iff B > A, we want fn(A, B) == -fn(B, A).

Your function does not satisfy that!  Harmless for now, because you only
ever pass it to g_list_find_custom(), which treats all non-zero values
the same.  But if anyone passes it to something like qsort() in the
future, they can look forward to some debugging "fun".

> +    }
> +
> +    switch (holder_a->type) {
> +    case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
> +        name_a = holder_a->u.qom_object.qom_path;
> +        name_b = holder_b->u.qom_object.qom_path;
> +        break;
> +    case IO_THREAD_HOLDER_KIND_BLOCK_NODE:
> +        name_a = holder_a->u.block_node.node_name;
> +        name_b = holder_b->u.block_node.node_name;
> +        break;
> +    case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
> +        name_a = holder_a->u.monitor_name.monitor_name;
> +        name_b = holder_b->u.monitor_name.monitor_name;
> +        break;
> +    default:
> +       /*
> +        * This should not happen. If it does, name_a/b remains
> +        * NULL and g_strcmp0 will handle it safely.
> +        */
> +        name_a = NULL;
> +        name_b = NULL;

Replace this by

           g_assert_not_reached();

Then neither @name_a nor @name_b can be null, because a non-optional
QAPI str is never null.  Simply use strcmp().

> +    }
> +
> +    return g_strcmp0(name_a, name_b);
> +}
> +
> +/*
> + * This function removes the @holder from the @iothread's tracking list.
> + * The @holder must match the one used previously in iothread_ref().
> + * It is a programming error to call this with a @holder that is not
> + * currently associated with the @iothread.
> + */
> +static void iothread_unref(IOThread *iothread, const IOThreadHolder *holder)
> +{
> +    assert(holder);
> +    GList *link = g_list_find_custom(iothread->holders, holder,
> +                                     (GCompareFunc)iothread_holder_compare);
> +
> +    assert(link);
> +
> +    IOThreadHolder *h = (IOThreadHolder *)link->data;
> +    qapi_free_IOThreadHolder(h);
> +    iothread->holders = g_list_delete_link(iothread->holders, link);

Removing @link from the list before freeing link->data would be more
obviously correct, wouldn't it?

> +}
> +
>  static void *iothread_run(void *opaque)
>  {
>      IOThread *iothread = opaque;
> @@ -356,6 +442,14 @@ char *iothread_get_id(IOThread *iothread)
>  
>  AioContext *iothread_get_aio_context(IOThread *iothread)
>  {
> +    /* Remove in next patch for build */
> +    IOThreadHolder holder = {
> +        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> +        .u.qom_object.qom_path = (char *)"tmp_path",
> +    };
> +    iothread_ref(iothread, &holder);
> +    iothread_unref(iothread, &holder);
> +

Is this really needed to make this patch work?

>      return iothread->ctx;
>  }
>  
> diff --git a/qapi/misc.json b/qapi/misc.json
> index c71a5fe657..d9f82f0922 100644
> --- a/qapi/misc.json
> +++ b/qapi/misc.json
> @@ -67,6 +67,67 @@
>  ##
>  { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
>  
> +
> +##
> +# @IOThreadHolderBlockNode:
> +#
> +# @node-name: A block node.

Let's use

   # @node-name: Name of the block node

to make it more similar to node-name descriptions elsewhere.

> +#
> +# Since: 11.1
> +#
> +##
> +{ 'struct': 'IOThreadHolderBlockNode',
> +  'data': { 'node-name': 'str' } }
> +
> +##
> +# @IOThreadHolderQomObject:
> +#
> +# @qom-path: A QOM Object.

Likewise, let's use

  # @qom-path: Path to the object in the QOM tree

> +#
> +# Since: 11.1
> +#
> +##
> +{ 'struct': 'IOThreadHolderQomObject',
> +  'data': { 'qom-path': 'str' } }
> +
> +##
> +# @IOThreadHolderMonitor:
> +#

Let's use

   # @monitor-name: Name of the HMP/QMP monitor

> +# @monitor-name: A HMP/QMP monitor.
> +#
> +# Since: 11.1
> +#
> +##
> +{ 'struct': 'IOThreadHolderMonitor',
> +  'data': { 'monitor-name': 'str' } }
> +
> +##
> +# @IOThreadHolderKind:
> +#
> +# @block-node: A block node.
> +# @qom-object: A QOM Object.
> +# @monitor-name: A HMP/QMP monitor.

Rename to @monitor for consistency with the other two.

Remark, not a request to change anything in your patch: kind monitor
will become obsolete when monitors become QOM objects.  Doing that
before we release this interface is desirable.

> +#
> +# Since: 11.1
> +##
> +{ 'enum': 'IOThreadHolderKind',
> +  'data': [ 'block-node', 'qom-object', 'monitor-name' ] }
> +
> +##
> +# @IOThreadHolder:
> +#

Let's add a short description:

   # The block node, QOM object, or monitor holding the I/O thread.

> +# @type: the kind of I/O thread holder.
> +#
> +# Since: 11.1
> +##
> +{ 'union': 'IOThreadHolder',
> +  'base': { 'type': 'IOThreadHolderKind' },
> +  'discriminator': 'type',
> +  'data': {
> +    'block-node': 'IOThreadHolderBlockNode',
> +    'qom-object': 'IOThreadHolderQomObject',
> +    'monitor-name': 'IOThreadHolderMonitor' } }
> +
>  ##
>  # @IOThreadInfo:
>  #



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors
  2026-05-29 21:33 ` [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors Zhang Chen
@ 2026-06-02 17:02   ` Markus Armbruster
  2026-06-04  9:57     ` Zhang Chen
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2026-06-02 17:02 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, dynamically or implicitly created monitors (such as those
> generated via '-monitor stdio', GDB stub terminals, or mux chardevs)
> leave the 'id' field in the Monitor struct as NULL.

There is no @id member in struct Monitor.

>                                                     But the original
> @MonitorOptions have the @id field.

QAPI type MonitorOptions has an optional member @id.

Instances are only created by monitor_init_opts() from a QemuOpts, and
member @id is set to the QemuOpts's @id, which is also optional.

monitor_init_opts() is only called during startup on behalf of CLI
option -mon, its shorthands -monitor, -qmp, -qmp-pretty, and for default
monitors.

-mon accepts an "id" parameter, but it's optional.

The others make up an "id".  For instance, the default monitor usually
gets "compat_monitor0".

Aside: there's code that checks whether a non-null QemuOpts "id" starts
with "compat_monitor".  That's horrifying.

As far as I can tell, the only way to create a monitor with null @id in
MonitorOptions is -mon without "id".

This is a swamp.  Instead of messing with MonitorOptions member @id, ...

>                                     This makes it difficult to track,
> debug, or associate monitors with specific iothreads or
> infrastructure components.
>
> Fix this by introducing monitor_get_id(), which uses an atomic counter
> to generate a unique fallback name ("mon_default_X") when 'id' is
> not explicitly supplied in MonitorOptions. Update all internal HMP
> and QMP initialization paths to propagate and store this string.

... you create yet another ID that is used for nothing but referring to
monitors from IOThreadHolder.  Hmm.

Is it really necessary that every monitor has an ID?  If a user want
one, they can simply avoid -mon without id.

If it's not necessary, ignore the remainder of my review.

> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  chardev/char.c             |  2 +-
>  gdbstub/system.c           |  3 ++-
>  include/monitor/monitor.h  |  6 ++++--
>  monitor/hmp.c              | 20 +++++++++++++++++++-
>  monitor/monitor-internal.h |  2 ++
>  monitor/monitor.c          |  4 ++--
>  monitor/qmp.c              | 18 +++++++++++++++++-
>  stubs/monitor-internal.c   |  3 ++-
>  8 files changed, 49 insertions(+), 9 deletions(-)
>
> diff --git a/chardev/char.c b/chardev/char.c
> index ca8b37ed8d..f057247001 100644
> --- a/chardev/char.c
> +++ b/chardev/char.c
> @@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
>  
>      if (qemu_opt_get_bool(opts, "mux", 0)) {
>          assert(permit_mux_mon);
> -        monitor_init_hmp(chr, true, &err);
> +        monitor_init_hmp(chr, true, NULL, &err);
>          if (err) {
>              error_report_err(err);
>              object_unparent(OBJECT(chr));
> diff --git a/gdbstub/system.c b/gdbstub/system.c
> index e86c5870ab..50f934fde3 100644
> --- a/gdbstub/system.c
> +++ b/gdbstub/system.c
> @@ -388,7 +388,8 @@ bool gdbserver_start(const char *device, Error **errp)
>          /* Initialize a monitor terminal for gdb */
>          mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
>                                     NULL, NULL, &error_abort);
> -        monitor_init_hmp(mon_chr, false, &error_abort);
> +
> +        monitor_init_hmp(mon_chr, false, NULL, &error_abort);
>      } else {
>          qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
>          mon_chr = gdbserver_system_state.mon_chr;
> diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
> index 55649a8664..c6ccd34cda 100644
> --- a/include/monitor/monitor.h
> +++ b/include/monitor/monitor.h
> @@ -19,11 +19,13 @@ bool monitor_cur_is_qmp(void);
>  
>  void monitor_init_globals(void);
>  void monitor_init_globals_core(void);
> -void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp);
> -void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp);
> +void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, Error **errp);
> +void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
> +                      Error **errp);
>  int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp);
>  int monitor_init_opts(QemuOpts *opts, Error **errp);
>  void monitor_cleanup(void);
> +char *monitor_get_id(void);
>  
>  int monitor_suspend(Monitor *mon);
>  void monitor_resume(Monitor *mon);
> diff --git a/monitor/hmp.c b/monitor/hmp.c
> index cc4390486e..47f57f69e5 100644
> --- a/monitor/hmp.c
> +++ b/monitor/hmp.c
> @@ -43,6 +43,8 @@
>  #include "system/block-backend.h"
>  #include "trace.h"
>  
> +static int mon_hmp_id_counter;
> +
>  static void monitor_command_cb(void *opaque, const char *cmdline,
>                                 void *readline_opaque)
>  {
> @@ -65,6 +67,14 @@ void monitor_read_command(MonitorHMP *mon, int show_prompt)
>      }
>  }
>  
> +static char *monitor_hmp_get_id(void)
> +{
> +    int id = qatomic_fetch_inc(&mon_hmp_id_counter);

Why atomic?  As far as I can see, this is only called during startup in
the main thread.  Even if we merge dynamic monitor creation, the monitor
command to create a monitor will also run in the main thread.

> +    char *name = g_strdup_printf("mon_default_hmp_%d", id);

Can clash with the user's monitor IDs.

> +
> +    return name;
> +}
> +
>  int monitor_read_password(MonitorHMP *mon, ReadLineFunc *readline_func,
>                            void *opaque)
>  {
> @@ -1522,10 +1532,18 @@ static void monitor_readline_flush(void *opaque)
>      monitor_flush(&mon->common);
>  }
>  
> -void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
> +void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
> +                      Error **errp)
>  {
>      MonitorHMP *mon = g_new0(MonitorHMP, 1);
>  
> +    if (!id) {
> +        g_autofree char *mon_id =  monitor_hmp_get_id();
> +        mon->common.id = g_strdup(mon_id);
> +    } else {
> +        mon->common.id = g_strdup(id);
> +    }
> +
>      if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
>          g_free(mon);
>          return;
> diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> index a5c4aba306..2060311b03 100644
> --- a/monitor/monitor-internal.h
> +++ b/monitor/monitor-internal.h
> @@ -108,6 +108,8 @@ struct Monitor {
>      bool skip_flush;
>      bool use_io_thread;
>  
> +    char *id;

Needs a comment.  In case you doubt it: explaining monitor IDs in my
review of the commit message took me ~120 words.

> +
>      char *mon_cpu_path;
>      QTAILQ_ENTRY(Monitor) entry;
>  
> diff --git a/monitor/monitor.c b/monitor/monitor.c
> index 00b93ed612..dafb4ad8b0 100644
> --- a/monitor/monitor.c
> +++ b/monitor/monitor.c
> @@ -732,7 +732,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
>  
>      switch (opts->mode) {
>      case MONITOR_MODE_CONTROL:
> -        monitor_init_qmp(chr, opts->pretty, errp);
> +        monitor_init_qmp(chr, opts->pretty, opts->id, errp);
>          break;
>      case MONITOR_MODE_READLINE:
>          if (!allow_hmp) {
> @@ -743,7 +743,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
>              error_setg(errp, "'pretty' is not compatible with HMP monitors");
>              return -1;
>          }
> -        monitor_init_hmp(chr, true, errp);
> +        monitor_init_hmp(chr, true, opts->id, errp);
>          break;
>      default:
>          g_assert_not_reached();
> diff --git a/monitor/qmp.c b/monitor/qmp.c
> index 687019811f..b210850a15 100644
> --- a/monitor/qmp.c
> +++ b/monitor/qmp.c
> @@ -56,6 +56,7 @@
>   * Access must be atomic for thread safety.
>   */
>  static bool qmp_dispatcher_co_busy = true;
> +static int mon_qmp_id_counter;
>  
>  struct QMPRequest {
>      /* Owner of the request */
> @@ -76,6 +77,14 @@ static bool qmp_oob_enabled(MonitorQMP *mon)
>      return mon->capab[QMP_CAPABILITY_OOB];
>  }
>  
> +static char *monitor_qmp_get_id(void)
> +{
> +    int id = qatomic_fetch_inc(&mon_qmp_id_counter);

Again, why atomic?

> +    char *name = g_strdup_printf("mon_default_qmp_%d", id);

Again, can clash with the user's IDs.

> +
> +    return name;
> +}
> +
>  static void monitor_qmp_caps_reset(MonitorQMP *mon)
>  {
>      memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
> @@ -513,10 +522,17 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
>      monitor_list_append(&mon->common);
>  }
>  
> -void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
> +void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, Error **errp)
>  {
>      MonitorQMP *mon = g_new0(MonitorQMP, 1);
>  
> +    if (!id) {
> +        g_autofree char *mon_id =  monitor_qmp_get_id();
> +        mon->common.id = g_strdup(mon_id);
> +    } else {
> +        mon->common.id = g_strdup(id);
> +    }
> +
>      if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
>          g_free(mon);
>          return;
> diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
> index 4fece49d53..325a559e62 100644
> --- a/stubs/monitor-internal.c
> +++ b/stubs/monitor-internal.c
> @@ -8,6 +8,7 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
>      return -1;
>  }
>  
> -void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
> +void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
> +                      Error **errp)
>  {
>  }



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 07/15] monitor: Update tracking iothread users with holder
  2026-05-29 21:33 ` [PATCH V8 07/15] monitor: Update tracking iothread users with holder Zhang Chen
@ 2026-06-02 17:20   ` Markus Armbruster
  2026-06-04 10:16     ` Zhang Chen
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2026-06-02 17:20 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:

> Based on monitor ID tracking iothread users with holder.
> Introduce the AioContext in the Monitor struct to avoid repeated calls to
> iothread_get_aio_context()

iothread_get_aio_context(iothread) returns iothread->ctx.  I doubt
avoiding the call is worth the complexity yet another variable adds.

>                            and ensure symmetrical ref/unref during
> monitor lifecycle.

What do you mean by that?

> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  monitor/monitor-internal.h |  3 +++
>  monitor/monitor.c          | 20 ++++++++++++++++++--
>  monitor/qmp.c              |  3 ++-
>  3 files changed, 23 insertions(+), 3 deletions(-)
>
> diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> index 2060311b03..92d7630921 100644
> --- a/monitor/monitor-internal.h
> +++ b/monitor/monitor-internal.h
> @@ -127,6 +127,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 dafb4ad8b0..7eec6f967d 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,12 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
>  {
>      if (use_io_thread && !mon_iothread) {
>          monitor_iothread_init();
> +
> +        IOThreadHolder holder = {
> +            .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
> +            .u.monitor_name.monitor_name = (char *)mon->id,
> +        };
> +        mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, &holder);
>      }
>      qemu_mutex_init(&mon->mon_lock);
>      mon->is_qmp = is_qmp;
> @@ -631,6 +637,16 @@ void monitor_data_destroy(Monitor *mon)
>      }
>      g_string_free(mon->outbuf, true);
>      qemu_mutex_destroy(&mon->mon_lock);
> +
> +    if (mon->ctx && mon_iothread) {
> +        IOThreadHolder holder = {
> +            .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
> +            .u.monitor_name.monitor_name = (char *)mon->id,
> +        };
> +
> +        iothread_put_aio_context(mon_iothread, &holder);
> +        mon->ctx = NULL;
> +    }
>  }
>  
>  void monitor_cleanup(void)
> diff --git a/monitor/qmp.c b/monitor/qmp.c
> index b210850a15..b67674aef3 100644
> --- a/monitor/qmp.c
> +++ b/monitor/qmp.c
> @@ -565,7 +565,8 @@ void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, 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 {

The patch does two things:

(1) Replace iothread_get_aio_context(mon_iothread) by mon->ctx.

(2) Call iothread_ref_and_get_aio_context() / iothread_put_aio_context().

I challenged (1).  If we decide we want it, I'll ask you to split the
patch.



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads
  2026-05-29 21:33 ` [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
@ 2026-06-02 17:34   ` Markus Armbruster
  2026-06-04 11:49     ` Zhang Chen
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2026-06-02 17:34 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.

Should we mention block nodes and monitors, too?

>
> New fields added to IOThreadInfo:
>  @holders: IOThreadHolder 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.  (Since 11.1)
>
> 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 | 26 ++++++++++++++++++++++++++
>  qapi/misc.json     | 11 +++++++++++
>  3 files changed, 59 insertions(+)
>
> diff --git a/iothread.c b/iothread.c
> index a85e960e45..5949785b32 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"
>  
>  /*
>   * iothread_ref:
> @@ -119,6 +121,25 @@ static void iothread_unref(IOThread *iothread, const IOThreadHolder *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;
> +    }

If iothread->holders was a IOThreadHolderList instead of a GList, this
loop could be QAPI_CLONE(IOThreadHolderList, iothread->holders) instead.

> +
> +    return head;
> +}
> +
>  static void *iothread_run(void *opaque)
>  {
>      IOThread *iothread = opaque;
> @@ -488,6 +509,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 443b8c785d..1b5883147b 100644
> --- a/monitor/hmp-cmds.c
> +++ b/monitor/hmp-cmds.c
> @@ -237,11 +237,37 @@ 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.node_name);
> +                    break;
> +                case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
> +                    monitor_printf(mon, "[qom-path: %s]",
> +                                   holder->u.qom_object.qom_path);
> +                    break;
> +                case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
> +                    monitor_printf(mon, "[monitor-name: %s]",
> +                                   holder->u.monitor_name.monitor_name);
> +                    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 d9f82f0922..4c16be3fd4 100644
> --- a/qapi/misc.json
> +++ b/qapi/misc.json
> @@ -137,6 +137,13 @@
>  #
>  # @thread-id: ID of the underlying host thread
>  #
> +# @holders: IOThreadHolder of the devices currently associated with

This is rendered like

    • holders ([IOThreadHolder]) – IOThreadHolder of the devices currently associated with

Better use something like

   # @holders: the devices currently associated with ...

I'm not sure about "devices", though.  It's QOM objects, block nodes,
and monitors.  Perhaps the QOM objects are all be devices (subtypes of
TYPE_DEVICE), but I'd have to dig through your patches to be sure.  Does
it matter?

> +#     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.  (Since 11.1)

This may need to be rewritten with block nodes and monitors in mind.

> +#
>  # @poll-max-ns: maximum polling time in ns, 0 means polling is
>  #     disabled (since 2.9)
>  #
> @@ -159,6 +166,7 @@
>  { 'struct': 'IOThreadInfo',
>    'data': {'id': 'str',
>             'thread-id': 'int',
> +           'holders': ['IOThreadHolder'],
>             'poll-max-ns': 'int',
>             'poll-grow': 'int',
>             'poll-shrink': 'int',
> @@ -185,6 +193,8 @@
>  #              {
>  #                 "id":"iothread0",
>  #                 "thread-id":3134,
> +#                 "holders":[{"qom-path": "/machine/peripheral/blk1/virtio-backend", "type": "qom-object"},
> +#                            {"qom-path": "/machine/peripheral/blk0/virtio-backend", "type": "qom-object"}],
>  #                 "poll-max-ns":32768,
>  #                 "poll-grow":0,
>  #                 "poll-shrink":0,
> @@ -193,6 +203,7 @@
>  #              {
>  #                 "id":"iothread1",
>  #                 "thread-id":3135,
> +#                 "holders":[{"qom-path": "/machine/peripheral/blk2/virtio-backend", "type": "qom-object"}],
>  #                 "poll-max-ns":32768,
>  #                 "poll-grow":0,
>  #                 "poll-shrink":0,

Would it be useful if one of these examples was a block node?



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants
  2026-05-29 21:33 ` [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants Zhang Chen
@ 2026-06-02 17:41   ` Markus Armbruster
  2026-06-04 11:53     ` Zhang Chen
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2026-06-02 17:41 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:

> 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           | 5 ++---
>  hw/block/dataplane/xen-block.c  | 4 ++--
>  hw/block/virtio-blk.c           | 4 ++--
>  hw/scsi/virtio-scsi-dataplane.c | 4 ++--
>  hw/vfio-user/proxy.c            | 3 +--
>  hw/virtio/iothread-vq-mapping.c | 3 +--
>  hw/virtio/virtio-balloon.c      | 2 +-
>  include/system/iothread.h       | 6 +++---
>  iothread.c                      | 9 ++-------
>  monitor/monitor.c               | 2 +-
>  net/colo-compare.c              | 2 +-
>  11 files changed, 18 insertions(+), 26 deletions(-)
>
> diff --git a/block/export/export.c b/block/export/export.c
> index b6c07f69b5..acd061e86e 100644
> --- a/block/export/export.c
> +++ b/block/export/export.c
> @@ -146,7 +146,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
>              .u.block_node.node_name = (char *)holder_name,
>          };
>  
> -        new_ctx = iothread_ref_and_get_aio_context(iothread, &holder);
> +        new_ctx = iothread_get_aio_context(iothread, &holder);
>          multithread_count = 1;
>          local_iothreads = g_new0(IOThread *, 1);
>          local_iothreads[0] = iothread;
> @@ -190,8 +190,7 @@ 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);
> +            multithread_ctxs[i++] = iothread_get_aio_context(iothread, &holder);
>          }
>          assert(i == multithread_count);
>      }
> diff --git a/hw/block/dataplane/xen-block.c b/hw/block/dataplane/xen-block.c
> index b5bf8d359f..46bfd62f5a 100644
> --- a/hw/block/dataplane/xen-block.c
> +++ b/hw/block/dataplane/xen-block.c
> @@ -628,8 +628,8 @@ XenBlockDataPlane *xen_block_dataplane_create(XenDevice *xendev,
>          };
>  
>          dataplane->iothread = iothread;
> -        dataplane->ctx = iothread_ref_and_get_aio_context(dataplane->iothread,
> -                                                          &io_holder);
> +        dataplane->ctx = iothread_get_aio_context(dataplane->iothread,
> +                                                  &io_holder);
>      } else {
>          dataplane->ctx = qemu_get_aio_context();
>      }
> diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
> index d8dc1dd136..d1d2bd7025 100644
> --- a/hw/block/virtio-blk.c
> +++ b/hw/block/virtio-blk.c
> @@ -1502,8 +1502,8 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
>              .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
>              .u.qom_object.qom_path = (char *)path,
>          };
> -        AioContext *ctx = iothread_ref_and_get_aio_context(conf->iothread,
> -                                                           &io_holder);
> +        AioContext *ctx = iothread_get_aio_context(conf->iothread,
> +                                                   &io_holder);
>          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 c71f33b41e..76ab22610f 100644
> --- a/hw/scsi/virtio-scsi-dataplane.c
> +++ b/hw/scsi/virtio-scsi-dataplane.c
> @@ -78,8 +78,8 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
>              .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
>              .u.qom_object.qom_path = (char *)path,
>          };
> -        AioContext *ctx = iothread_ref_and_get_aio_context(vs->conf.iothread,
> -                                                           &io_holder);
> +        AioContext *ctx = iothread_get_aio_context(vs->conf.iothread,
> +                                                   &io_holder);
>          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 756f368849..24af3c791b 100644
> --- a/hw/vfio-user/proxy.c
> +++ b/hw/vfio-user/proxy.c
> @@ -942,8 +942,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,
> -                                                  &io_holder);
> +    proxy->ctx = iothread_get_aio_context(vfio_user_iothread, &io_holder);
>      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 2cb48dd387..727358a483 100644
> --- a/hw/virtio/iothread-vq-mapping.c
> +++ b/hw/virtio/iothread-vq-mapping.c
> @@ -99,8 +99,7 @@ bool iothread_vq_mapping_apply(
>              .u.qom_object.qom_path = (char *)holder,
>          };
>  
> -        AioContext *ctx = iothread_ref_and_get_aio_context(iothread,
> -                                                           &io_holder);
> +        AioContext *ctx = iothread_get_aio_context(iothread, &io_holder);
>  
>          if (node->value->vqs) {
>              uint16List *vq;
> diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
> index e9d023b4ec..4405a87ed0 100644
> --- a/hw/virtio/virtio-balloon.c
> +++ b/hw/virtio/virtio-balloon.c
> @@ -903,7 +903,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, &io_holder),
> +                      iothread_get_aio_context(s->iothread, &io_holder),
>                        virtio_ballloon_get_free_page_hints, s,
>                        &dev->mem_reentrancy_guard);
>      }
> diff --git a/include/system/iothread.h b/include/system/iothread.h
> index 064c05e78d..e5db215954 100644
> --- a/include/system/iothread.h
> +++ b/include/system/iothread.h
> @@ -70,15 +70,15 @@ 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);
> +
>  /*
>   * The iothread_unsafe_get_aio_context() is a low-level unsafe way of getting
>   * the AioContext, recommend migrating to the new API with IOThreadHolder
>   * as much as possible.
>   */
>  AioContext *iothread_unsafe_get_aio_context(IOThread *iothread);
> -AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
> -                                             const IOThreadHolder *holder);
> +AioContext *iothread_get_aio_context(IOThread *iothread,
> +                                     const IOThreadHolder *holder);
>  void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder);
>  GMainContext *iothread_get_g_main_context(IOThread *iothread);
>  
> diff --git a/iothread.c b/iothread.c
> index 5949785b32..9bc8e359c1 100644
> --- a/iothread.c
> +++ b/iothread.c
> @@ -469,13 +469,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 IOThreadHolder *holder)
> +AioContext *iothread_get_aio_context(IOThread *iothread,
> +                                     const IOThreadHolder *holder)
>  {
>      /* Add IOThreadHolder to the list */
>      iothread_ref(iothread, holder);
> diff --git a/monitor/monitor.c b/monitor/monitor.c
> index 7eec6f967d..4d00cf499c 100644
> --- a/monitor/monitor.c
> +++ b/monitor/monitor.c
> @@ -617,7 +617,7 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
>              .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
>              .u.monitor_name.monitor_name = (char *)mon->id,
>          };
> -        mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, &holder);
> +        mon->ctx = iothread_get_aio_context(mon_iothread, &holder);
>      }
>      qemu_mutex_init(&mon->mon_lock);
>      mon->is_qmp = is_qmp;
> diff --git a/net/colo-compare.c b/net/colo-compare.c
> index 4f180936e3..820ea4d824 100644
> --- a/net/colo-compare.c
> +++ b/net/colo-compare.c
> @@ -973,7 +973,7 @@ static void colo_compare_iothread(CompareState *s)
>          .u.qom_object.qom_path = (char *)path,
>      };
>  
> -    AioContext *ctx = iothread_ref_and_get_aio_context(s->iothread, &io_holder);
> +    AioContext *ctx = iothread_get_aio_context(s->iothread, &io_holder);
>  
>      s->iothread_ctx = ctx;
>      s->worker_context = iothread_get_g_main_context(s->iothread);


The patch appears to delete unused iothread_get_aio_context() from
iothread.[cg] and to rename iothread_ref_and_get_aio_context() to
iothread_get_aio_context().  It leaves iothread_get_aio_context() in
tests/unit/iothread.[ch] alone.  The result is confusing: test's
iothread_get_aio_context() takes now one parameter less than the regular
one.

Should you change both versions?



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors
  2026-06-02 17:02   ` Markus Armbruster
@ 2026-06-04  9:57     ` Zhang Chen
  0 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-06-04  9:57 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Michael S . Tsirkin, Stefan Hajnoczi

On Wed, Jun 3, 2026 at 1:02 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Currently, dynamically or implicitly created monitors (such as those
> > generated via '-monitor stdio', GDB stub terminals, or mux chardevs)
> > leave the 'id' field in the Monitor struct as NULL.
>
> There is no @id member in struct Monitor.

Yes, I try to add it in this patch.

>
> >                                                     But the original
> > @MonitorOptions have the @id field.
>
> QAPI type MonitorOptions has an optional member @id.
>
> Instances are only created by monitor_init_opts() from a QemuOpts, and
> member @id is set to the QemuOpts's @id, which is also optional.
>
> monitor_init_opts() is only called during startup on behalf of CLI
> option -mon, its shorthands -monitor, -qmp, -qmp-pretty, and for default
> monitors.
>
> -mon accepts an "id" parameter, but it's optional.
>
> The others make up an "id".  For instance, the default monitor usually
> gets "compat_monitor0".
>
> Aside: there's code that checks whether a non-null QemuOpts "id" starts
> with "compat_monitor".  That's horrifying.
>
> As far as I can tell, the only way to create a monitor with null @id in
> MonitorOptions is -mon without "id".
>
> This is a swamp.  Instead of messing with MonitorOptions member @id, ...

I don't quite understand here, the "compat_monitor0" has been saved in
the struct chardev's label field. I know each monitor need to bind to a chardev.
In principle, the "compat_monitor0" mark belonging to the chardev, not
the monitor.
Why is it forbidden to replace monitor with chardev?
So I think the monitor need to a unique ID field in the struct monitor.

>
> >                                     This makes it difficult to track,
> > debug, or associate monitors with specific iothreads or
> > infrastructure components.
> >
> > Fix this by introducing monitor_get_id(), which uses an atomic counter
> > to generate a unique fallback name ("mon_default_X") when 'id' is
> > not explicitly supplied in MonitorOptions. Update all internal HMP
> > and QMP initialization paths to propagate and store this string.
>
> ... you create yet another ID that is used for nothing but referring to
> monitors from IOThreadHolder.  Hmm.
>
> Is it really necessary that every monitor has an ID?  If a user want
> one, they can simply avoid -mon without id.

I think yes, the iothread ref/unref is the first user.

>
> If it's not necessary, ignore the remainder of my review.
>
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> > ---
> >  chardev/char.c             |  2 +-
> >  gdbstub/system.c           |  3 ++-
> >  include/monitor/monitor.h  |  6 ++++--
> >  monitor/hmp.c              | 20 +++++++++++++++++++-
> >  monitor/monitor-internal.h |  2 ++
> >  monitor/monitor.c          |  4 ++--
> >  monitor/qmp.c              | 18 +++++++++++++++++-
> >  stubs/monitor-internal.c   |  3 ++-
> >  8 files changed, 49 insertions(+), 9 deletions(-)
> >
> > diff --git a/chardev/char.c b/chardev/char.c
> > index ca8b37ed8d..f057247001 100644
> > --- a/chardev/char.c
> > +++ b/chardev/char.c
> > @@ -805,7 +805,7 @@ static Chardev *qemu_chr_new_from_name(const char *label, const char *filename,
> >
> >      if (qemu_opt_get_bool(opts, "mux", 0)) {
> >          assert(permit_mux_mon);
> > -        monitor_init_hmp(chr, true, &err);
> > +        monitor_init_hmp(chr, true, NULL, &err);
> >          if (err) {
> >              error_report_err(err);
> >              object_unparent(OBJECT(chr));
> > diff --git a/gdbstub/system.c b/gdbstub/system.c
> > index e86c5870ab..50f934fde3 100644
> > --- a/gdbstub/system.c
> > +++ b/gdbstub/system.c
> > @@ -388,7 +388,8 @@ bool gdbserver_start(const char *device, Error **errp)
> >          /* Initialize a monitor terminal for gdb */
> >          mon_chr = qemu_chardev_new(NULL, TYPE_CHARDEV_GDB,
> >                                     NULL, NULL, &error_abort);
> > -        monitor_init_hmp(mon_chr, false, &error_abort);
> > +
> > +        monitor_init_hmp(mon_chr, false, NULL, &error_abort);
> >      } else {
> >          qemu_chr_fe_deinit(&gdbserver_system_state.chr, true);
> >          mon_chr = gdbserver_system_state.mon_chr;
> > diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h
> > index 55649a8664..c6ccd34cda 100644
> > --- a/include/monitor/monitor.h
> > +++ b/include/monitor/monitor.h
> > @@ -19,11 +19,13 @@ bool monitor_cur_is_qmp(void);
> >
> >  void monitor_init_globals(void);
> >  void monitor_init_globals_core(void);
> > -void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp);
> > -void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp);
> > +void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, Error **errp);
> > +void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
> > +                      Error **errp);
> >  int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp);
> >  int monitor_init_opts(QemuOpts *opts, Error **errp);
> >  void monitor_cleanup(void);
> > +char *monitor_get_id(void);
> >
> >  int monitor_suspend(Monitor *mon);
> >  void monitor_resume(Monitor *mon);
> > diff --git a/monitor/hmp.c b/monitor/hmp.c
> > index cc4390486e..47f57f69e5 100644
> > --- a/monitor/hmp.c
> > +++ b/monitor/hmp.c
> > @@ -43,6 +43,8 @@
> >  #include "system/block-backend.h"
> >  #include "trace.h"
> >
> > +static int mon_hmp_id_counter;
> > +
> >  static void monitor_command_cb(void *opaque, const char *cmdline,
> >                                 void *readline_opaque)
> >  {
> > @@ -65,6 +67,14 @@ void monitor_read_command(MonitorHMP *mon, int show_prompt)
> >      }
> >  }
> >
> > +static char *monitor_hmp_get_id(void)
> > +{
> > +    int id = qatomic_fetch_inc(&mon_hmp_id_counter);
>
> Why atomic?  As far as I can see, this is only called during startup in
> the main thread.  Even if we merge dynamic monitor creation, the monitor
> command to create a monitor will also run in the main thread.

Yes, you are right.
Will change it to:
int id = mon_hmp_id_counter++;


>
> > +    char *name = g_strdup_printf("mon_default_hmp_%d", id);
>
> Can clash with the user's monitor IDs.

Just run it when the user didn't input thie monitor ID:

+void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
+                      Error **errp)
 {
     MonitorHMP *mon = g_new0(MonitorHMP, 1);

+    if (!id) {
+        g_autofree char *mon_id =  monitor_hmp_get_id();
+        mon->common.id = g_strdup(mon_id);
+    } else {
+        mon->common.id = g_strdup(id);
+    }

>
> > +
> > +    return name;
> > +}
> > +
> >  int monitor_read_password(MonitorHMP *mon, ReadLineFunc *readline_func,
> >                            void *opaque)
> >  {
> > @@ -1522,10 +1532,18 @@ static void monitor_readline_flush(void *opaque)
> >      monitor_flush(&mon->common);
> >  }
> >
> > -void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
> > +void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
> > +                      Error **errp)
> >  {
> >      MonitorHMP *mon = g_new0(MonitorHMP, 1);
> >
> > +    if (!id) {
> > +        g_autofree char *mon_id =  monitor_hmp_get_id();
> > +        mon->common.id = g_strdup(mon_id);
> > +    } else {
> > +        mon->common.id = g_strdup(id);
> > +    }
> > +
> >      if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
> >          g_free(mon);
> >          return;
> > diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> > index a5c4aba306..2060311b03 100644
> > --- a/monitor/monitor-internal.h
> > +++ b/monitor/monitor-internal.h
> > @@ -108,6 +108,8 @@ struct Monitor {
> >      bool skip_flush;
> >      bool use_io_thread;
> >
> > +    char *id;
>
> Needs a comment.  In case you doubt it: explaining monitor IDs in my
> review of the commit message took me ~120 words.

OK, I learned a lot from your comments~

Thanks~
Chen

>
> > +
> >      char *mon_cpu_path;
> >      QTAILQ_ENTRY(Monitor) entry;
> >
> > diff --git a/monitor/monitor.c b/monitor/monitor.c
> > index 00b93ed612..dafb4ad8b0 100644
> > --- a/monitor/monitor.c
> > +++ b/monitor/monitor.c
> > @@ -732,7 +732,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
> >
> >      switch (opts->mode) {
> >      case MONITOR_MODE_CONTROL:
> > -        monitor_init_qmp(chr, opts->pretty, errp);
> > +        monitor_init_qmp(chr, opts->pretty, opts->id, errp);
> >          break;
> >      case MONITOR_MODE_READLINE:
> >          if (!allow_hmp) {
> > @@ -743,7 +743,7 @@ int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp)
> >              error_setg(errp, "'pretty' is not compatible with HMP monitors");
> >              return -1;
> >          }
> > -        monitor_init_hmp(chr, true, errp);
> > +        monitor_init_hmp(chr, true, opts->id, errp);
> >          break;
> >      default:
> >          g_assert_not_reached();
> > diff --git a/monitor/qmp.c b/monitor/qmp.c
> > index 687019811f..b210850a15 100644
> > --- a/monitor/qmp.c
> > +++ b/monitor/qmp.c
> > @@ -56,6 +56,7 @@
> >   * Access must be atomic for thread safety.
> >   */
> >  static bool qmp_dispatcher_co_busy = true;
> > +static int mon_qmp_id_counter;
> >
> >  struct QMPRequest {
> >      /* Owner of the request */
> > @@ -76,6 +77,14 @@ static bool qmp_oob_enabled(MonitorQMP *mon)
> >      return mon->capab[QMP_CAPABILITY_OOB];
> >  }
> >
> > +static char *monitor_qmp_get_id(void)
> > +{
> > +    int id = qatomic_fetch_inc(&mon_qmp_id_counter);
>
> Again, why atomic?
>
> > +    char *name = g_strdup_printf("mon_default_qmp_%d", id);
>
> Again, can clash with the user's IDs.
>
> > +
> > +    return name;
> > +}
> > +
> >  static void monitor_qmp_caps_reset(MonitorQMP *mon)
> >  {
> >      memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
> > @@ -513,10 +522,17 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
> >      monitor_list_append(&mon->common);
> >  }
> >
> > -void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp)
> > +void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, Error **errp)
> >  {
> >      MonitorQMP *mon = g_new0(MonitorQMP, 1);
> >
> > +    if (!id) {
> > +        g_autofree char *mon_id =  monitor_qmp_get_id();
> > +        mon->common.id = g_strdup(mon_id);
> > +    } else {
> > +        mon->common.id = g_strdup(id);
> > +    }
> > +
> >      if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) {
> >          g_free(mon);
> >          return;
> > diff --git a/stubs/monitor-internal.c b/stubs/monitor-internal.c
> > index 4fece49d53..325a559e62 100644
> > --- a/stubs/monitor-internal.c
> > +++ b/stubs/monitor-internal.c
> > @@ -8,6 +8,7 @@ int monitor_get_fd(Monitor *mon, const char *name, Error **errp)
> >      return -1;
> >  }
> >
> > -void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp)
> > +void monitor_init_hmp(Chardev *chr, bool use_readline, const char *id,
> > +                      Error **errp)
> >  {
> >  }
>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 07/15] monitor: Update tracking iothread users with holder
  2026-06-02 17:20   ` Markus Armbruster
@ 2026-06-04 10:16     ` Zhang Chen
  0 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-06-04 10:16 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Michael S . Tsirkin, Stefan Hajnoczi

On Wed, Jun 3, 2026 at 1:20 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Based on monitor ID tracking iothread users with holder.
> > Introduce the AioContext in the Monitor struct to avoid repeated calls to
> > iothread_get_aio_context()
>
> iothread_get_aio_context(iothread) returns iothread->ctx.  I doubt
> avoiding the call is worth the complexity yet another variable adds.
>
> >                            and ensure symmetrical ref/unref during
> > monitor lifecycle.
>
> What do you mean by that?

As we discussed with Stefan before, the iothread_ref/unref need to
call in pairs, for example the virtio-blk. But the monitor implementation
is special, it have the monitor_suspend/resume with iothread.
So I try to introduce the mon->ctx to simplify it.

>
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> > ---
> >  monitor/monitor-internal.h |  3 +++
> >  monitor/monitor.c          | 20 ++++++++++++++++++--
> >  monitor/qmp.c              |  3 ++-
> >  3 files changed, 23 insertions(+), 3 deletions(-)
> >
> > diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> > index 2060311b03..92d7630921 100644
> > --- a/monitor/monitor-internal.h
> > +++ b/monitor/monitor-internal.h
> > @@ -127,6 +127,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 dafb4ad8b0..7eec6f967d 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,12 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
> >  {
> >      if (use_io_thread && !mon_iothread) {
> >          monitor_iothread_init();
> > +
> > +        IOThreadHolder holder = {
> > +            .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
> > +            .u.monitor_name.monitor_name = (char *)mon->id,
> > +        };
> > +        mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, &holder);
> >      }
> >      qemu_mutex_init(&mon->mon_lock);
> >      mon->is_qmp = is_qmp;
> > @@ -631,6 +637,16 @@ void monitor_data_destroy(Monitor *mon)
> >      }
> >      g_string_free(mon->outbuf, true);
> >      qemu_mutex_destroy(&mon->mon_lock);
> > +
> > +    if (mon->ctx && mon_iothread) {
> > +        IOThreadHolder holder = {
> > +            .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
> > +            .u.monitor_name.monitor_name = (char *)mon->id,
> > +        };
> > +
> > +        iothread_put_aio_context(mon_iothread, &holder);
> > +        mon->ctx = NULL;
> > +    }
> >  }
> >
> >  void monitor_cleanup(void)
> > diff --git a/monitor/qmp.c b/monitor/qmp.c
> > index b210850a15..b67674aef3 100644
> > --- a/monitor/qmp.c
> > +++ b/monitor/qmp.c
> > @@ -565,7 +565,8 @@ void monitor_init_qmp(Chardev *chr, bool pretty, const char *id, 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 {
>
> The patch does two things:
>
> (1) Replace iothread_get_aio_context(mon_iothread) by mon->ctx.
>
> (2) Call iothread_ref_and_get_aio_context() / iothread_put_aio_context().
>
> I challenged (1).  If we decide we want it, I'll ask you to split the
> patch.

It's OK for me~

Thanks
Chen

>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads
  2026-06-02 17:34   ` Markus Armbruster
@ 2026-06-04 11:49     ` Zhang Chen
  0 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-06-04 11:49 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Michael S . Tsirkin, Stefan Hajnoczi

On Wed, Jun 3, 2026 at 1:34 AM 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.
>
> Should we mention block nodes and monitors, too?
>

Good catch, will add the block nodes and monitors.

> >
> > New fields added to IOThreadInfo:
> >  @holders: IOThreadHolder 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.  (Since 11.1)
> >
> > 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 | 26 ++++++++++++++++++++++++++
> >  qapi/misc.json     | 11 +++++++++++
> >  3 files changed, 59 insertions(+)
> >
> > diff --git a/iothread.c b/iothread.c
> > index a85e960e45..5949785b32 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"
> >
> >  /*
> >   * iothread_ref:
> > @@ -119,6 +121,25 @@ static void iothread_unref(IOThread *iothread, const IOThreadHolder *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;
> > +    }
>
> If iothread->holders was a IOThreadHolderList instead of a GList, this
> loop could be QAPI_CLONE(IOThreadHolderList, iothread->holders) instead.

Nice comments, I will try it in next version.

>
> > +
> > +    return head;
> > +}
> > +
> >  static void *iothread_run(void *opaque)
> >  {
> >      IOThread *iothread = opaque;
> > @@ -488,6 +509,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 443b8c785d..1b5883147b 100644
> > --- a/monitor/hmp-cmds.c
> > +++ b/monitor/hmp-cmds.c
> > @@ -237,11 +237,37 @@ 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.node_name);
> > +                    break;
> > +                case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
> > +                    monitor_printf(mon, "[qom-path: %s]",
> > +                                   holder->u.qom_object.qom_path);
> > +                    break;
> > +                case IO_THREAD_HOLDER_KIND_MONITOR_NAME:
> > +                    monitor_printf(mon, "[monitor-name: %s]",
> > +                                   holder->u.monitor_name.monitor_name);
> > +                    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 d9f82f0922..4c16be3fd4 100644
> > --- a/qapi/misc.json
> > +++ b/qapi/misc.json
> > @@ -137,6 +137,13 @@
> >  #
> >  # @thread-id: ID of the underlying host thread
> >  #
> > +# @holders: IOThreadHolder of the devices currently associated with
>
> This is rendered like
>
>     • holders ([IOThreadHolder]) – IOThreadHolder of the devices currently associated with
>
> Better use something like
>
>    # @holders: the devices currently associated with ...
>
> I'm not sure about "devices", though.  It's QOM objects, block nodes,
> and monitors.  Perhaps the QOM objects are all be devices (subtypes of
> TYPE_DEVICE), but I'd have to dig through your patches to be sure.  Does
> it matter?

Yes, you are right, will change the comments from "devices" to "QOM
objects, block nodes or monitors".

>
> > +#     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.  (Since 11.1)
>
> This may need to be rewritten with block nodes and monitors in mind.

OK.

>
> > +#
> >  # @poll-max-ns: maximum polling time in ns, 0 means polling is
> >  #     disabled (since 2.9)
> >  #
> > @@ -159,6 +166,7 @@
> >  { 'struct': 'IOThreadInfo',
> >    'data': {'id': 'str',
> >             'thread-id': 'int',
> > +           'holders': ['IOThreadHolder'],
> >             'poll-max-ns': 'int',
> >             'poll-grow': 'int',
> >             'poll-shrink': 'int',
> > @@ -185,6 +193,8 @@
> >  #              {
> >  #                 "id":"iothread0",
> >  #                 "thread-id":3134,
> > +#                 "holders":[{"qom-path": "/machine/peripheral/blk1/virtio-backend", "type": "qom-object"},
> > +#                            {"qom-path": "/machine/peripheral/blk0/virtio-backend", "type": "qom-object"}],
> >  #                 "poll-max-ns":32768,
> >  #                 "poll-grow":0,
> >  #                 "poll-shrink":0,
> > @@ -193,6 +203,7 @@
> >  #              {
> >  #                 "id":"iothread1",
> >  #                 "thread-id":3135,
> > +#                 "holders":[{"qom-path": "/machine/peripheral/blk2/virtio-backend", "type": "qom-object"}],
> >  #                 "poll-max-ns":32768,
> >  #                 "poll-grow":0,
> >  #                 "poll-shrink":0,
>
> Would it be useful if one of these examples was a block node?

OK, will update a demo for block node.

Thanks
Chen

>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants
  2026-06-02 17:41   ` Markus Armbruster
@ 2026-06-04 11:53     ` Zhang Chen
  0 siblings, 0 replies; 25+ messages in thread
From: Zhang Chen @ 2026-06-04 11:53 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Dr . David Alan Gilbert, Eric Blake,
	Michael S . Tsirkin, Stefan Hajnoczi

On Wed, Jun 3, 2026 at 1:41 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > 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           | 5 ++---
> >  hw/block/dataplane/xen-block.c  | 4 ++--
> >  hw/block/virtio-blk.c           | 4 ++--
> >  hw/scsi/virtio-scsi-dataplane.c | 4 ++--
> >  hw/vfio-user/proxy.c            | 3 +--
> >  hw/virtio/iothread-vq-mapping.c | 3 +--
> >  hw/virtio/virtio-balloon.c      | 2 +-
> >  include/system/iothread.h       | 6 +++---
> >  iothread.c                      | 9 ++-------
> >  monitor/monitor.c               | 2 +-
> >  net/colo-compare.c              | 2 +-
> >  11 files changed, 18 insertions(+), 26 deletions(-)
> >
> > diff --git a/block/export/export.c b/block/export/export.c
> > index b6c07f69b5..acd061e86e 100644
> > --- a/block/export/export.c
> > +++ b/block/export/export.c
> > @@ -146,7 +146,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
> >              .u.block_node.node_name = (char *)holder_name,
> >          };
> >
> > -        new_ctx = iothread_ref_and_get_aio_context(iothread, &holder);
> > +        new_ctx = iothread_get_aio_context(iothread, &holder);
> >          multithread_count = 1;
> >          local_iothreads = g_new0(IOThread *, 1);
> >          local_iothreads[0] = iothread;
> > @@ -190,8 +190,7 @@ 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);
> > +            multithread_ctxs[i++] = iothread_get_aio_context(iothread, &holder);
> >          }
> >          assert(i == multithread_count);
> >      }
> > diff --git a/hw/block/dataplane/xen-block.c b/hw/block/dataplane/xen-block.c
> > index b5bf8d359f..46bfd62f5a 100644
> > --- a/hw/block/dataplane/xen-block.c
> > +++ b/hw/block/dataplane/xen-block.c
> > @@ -628,8 +628,8 @@ XenBlockDataPlane *xen_block_dataplane_create(XenDevice *xendev,
> >          };
> >
> >          dataplane->iothread = iothread;
> > -        dataplane->ctx = iothread_ref_and_get_aio_context(dataplane->iothread,
> > -                                                          &io_holder);
> > +        dataplane->ctx = iothread_get_aio_context(dataplane->iothread,
> > +                                                  &io_holder);
> >      } else {
> >          dataplane->ctx = qemu_get_aio_context();
> >      }
> > diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
> > index d8dc1dd136..d1d2bd7025 100644
> > --- a/hw/block/virtio-blk.c
> > +++ b/hw/block/virtio-blk.c
> > @@ -1502,8 +1502,8 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
> >              .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> >              .u.qom_object.qom_path = (char *)path,
> >          };
> > -        AioContext *ctx = iothread_ref_and_get_aio_context(conf->iothread,
> > -                                                           &io_holder);
> > +        AioContext *ctx = iothread_get_aio_context(conf->iothread,
> > +                                                   &io_holder);
> >          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 c71f33b41e..76ab22610f 100644
> > --- a/hw/scsi/virtio-scsi-dataplane.c
> > +++ b/hw/scsi/virtio-scsi-dataplane.c
> > @@ -78,8 +78,8 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
> >              .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> >              .u.qom_object.qom_path = (char *)path,
> >          };
> > -        AioContext *ctx = iothread_ref_and_get_aio_context(vs->conf.iothread,
> > -                                                           &io_holder);
> > +        AioContext *ctx = iothread_get_aio_context(vs->conf.iothread,
> > +                                                   &io_holder);
> >          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 756f368849..24af3c791b 100644
> > --- a/hw/vfio-user/proxy.c
> > +++ b/hw/vfio-user/proxy.c
> > @@ -942,8 +942,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,
> > -                                                  &io_holder);
> > +    proxy->ctx = iothread_get_aio_context(vfio_user_iothread, &io_holder);
> >      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 2cb48dd387..727358a483 100644
> > --- a/hw/virtio/iothread-vq-mapping.c
> > +++ b/hw/virtio/iothread-vq-mapping.c
> > @@ -99,8 +99,7 @@ bool iothread_vq_mapping_apply(
> >              .u.qom_object.qom_path = (char *)holder,
> >          };
> >
> > -        AioContext *ctx = iothread_ref_and_get_aio_context(iothread,
> > -                                                           &io_holder);
> > +        AioContext *ctx = iothread_get_aio_context(iothread, &io_holder);
> >
> >          if (node->value->vqs) {
> >              uint16List *vq;
> > diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
> > index e9d023b4ec..4405a87ed0 100644
> > --- a/hw/virtio/virtio-balloon.c
> > +++ b/hw/virtio/virtio-balloon.c
> > @@ -903,7 +903,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, &io_holder),
> > +                      iothread_get_aio_context(s->iothread, &io_holder),
> >                        virtio_ballloon_get_free_page_hints, s,
> >                        &dev->mem_reentrancy_guard);
> >      }
> > diff --git a/include/system/iothread.h b/include/system/iothread.h
> > index 064c05e78d..e5db215954 100644
> > --- a/include/system/iothread.h
> > +++ b/include/system/iothread.h
> > @@ -70,15 +70,15 @@ 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);
> > +
> >  /*
> >   * The iothread_unsafe_get_aio_context() is a low-level unsafe way of getting
> >   * the AioContext, recommend migrating to the new API with IOThreadHolder
> >   * as much as possible.
> >   */
> >  AioContext *iothread_unsafe_get_aio_context(IOThread *iothread);
> > -AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
> > -                                             const IOThreadHolder *holder);
> > +AioContext *iothread_get_aio_context(IOThread *iothread,
> > +                                     const IOThreadHolder *holder);
> >  void iothread_put_aio_context(IOThread *iothread, const IOThreadHolder *holder);
> >  GMainContext *iothread_get_g_main_context(IOThread *iothread);
> >
> > diff --git a/iothread.c b/iothread.c
> > index 5949785b32..9bc8e359c1 100644
> > --- a/iothread.c
> > +++ b/iothread.c
> > @@ -469,13 +469,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 IOThreadHolder *holder)
> > +AioContext *iothread_get_aio_context(IOThread *iothread,
> > +                                     const IOThreadHolder *holder)
> >  {
> >      /* Add IOThreadHolder to the list */
> >      iothread_ref(iothread, holder);
> > diff --git a/monitor/monitor.c b/monitor/monitor.c
> > index 7eec6f967d..4d00cf499c 100644
> > --- a/monitor/monitor.c
> > +++ b/monitor/monitor.c
> > @@ -617,7 +617,7 @@ void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush,
> >              .type = IO_THREAD_HOLDER_KIND_MONITOR_NAME,
> >              .u.monitor_name.monitor_name = (char *)mon->id,
> >          };
> > -        mon->ctx = iothread_ref_and_get_aio_context(mon_iothread, &holder);
> > +        mon->ctx = iothread_get_aio_context(mon_iothread, &holder);
> >      }
> >      qemu_mutex_init(&mon->mon_lock);
> >      mon->is_qmp = is_qmp;
> > diff --git a/net/colo-compare.c b/net/colo-compare.c
> > index 4f180936e3..820ea4d824 100644
> > --- a/net/colo-compare.c
> > +++ b/net/colo-compare.c
> > @@ -973,7 +973,7 @@ static void colo_compare_iothread(CompareState *s)
> >          .u.qom_object.qom_path = (char *)path,
> >      };
> >
> > -    AioContext *ctx = iothread_ref_and_get_aio_context(s->iothread, &io_holder);
> > +    AioContext *ctx = iothread_get_aio_context(s->iothread, &io_holder);
> >
> >      s->iothread_ctx = ctx;
> >      s->worker_context = iothread_get_g_main_context(s->iothread);
>
>
> The patch appears to delete unused iothread_get_aio_context() from
> iothread.[cg] and to rename iothread_ref_and_get_aio_context() to
> iothread_get_aio_context().  It leaves iothread_get_aio_context() in
> tests/unit/iothread.[ch] alone.  The result is confusing: test's
> iothread_get_aio_context() takes now one parameter less than the regular
> one.
>
> Should you change both versions?
>

Yes, I already noticed the iothread test, we can change it after
merging this series.

Thanks
Chen


^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2026-06-04 11:54 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-29 21:33 [PATCH V8 00/15] iothread: Support tracking and querying IOThread holders Zhang Chen
2026-05-29 21:33 ` [PATCH V8 01/15] qapi/misc: Fix missed query-iothreads items Zhang Chen
2026-05-29 21:33 ` [PATCH V8 02/15] iothread: introduce iothread_ref/unref to track attached devices Zhang Chen
2026-06-02 11:29   ` Markus Armbruster
2026-05-29 21:33 ` [PATCH V8 03/15] iothread: tracking iothread users with holder name Zhang Chen
2026-05-29 21:33 ` [PATCH V8 04/15] iothread: introduce iothread_unsafe_get_aio_context() Zhang Chen
2026-05-29 21:33 ` [PATCH V8 05/15] block/export: track IOThread reference in BlockExport Zhang Chen
2026-05-29 21:33 ` [PATCH V8 06/15] monitor: assign unique default ID to anonymous monitors Zhang Chen
2026-06-02 17:02   ` Markus Armbruster
2026-06-04  9:57     ` Zhang Chen
2026-05-29 21:33 ` [PATCH V8 07/15] monitor: Update tracking iothread users with holder Zhang Chen
2026-06-02 17:20   ` Markus Armbruster
2026-06-04 10:16     ` Zhang Chen
2026-05-29 21:33 ` [PATCH V8 08/15] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
2026-05-29 21:33 ` [PATCH V8 09/15] virtio: use iothread_get/put_aio_context for thread pinning Zhang Chen
2026-05-29 21:33 ` [PATCH V8 10/15] net/colo: track IOThread references using path-based holder Zhang Chen
2026-05-29 21:33 ` [PATCH V8 11/15] virtio-balloon: Update tracking iothread users with holder Zhang Chen
2026-05-29 21:33 ` [PATCH V8 12/15] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
2026-05-29 21:33 ` [PATCH V8 13/15] xen-block: " Zhang Chen
2026-05-29 21:33 ` [PATCH V8 14/15] qapi: examine IOThread attachment status via query-iothreads Zhang Chen
2026-06-02 17:34   ` Markus Armbruster
2026-06-04 11:49     ` Zhang Chen
2026-05-29 21:33 ` [PATCH V8 15/15] iothread: simplify API by merging iothread_get_aio_context variants Zhang Chen
2026-06-02 17:41   ` Markus Armbruster
2026-06-04 11:53     ` Zhang Chen

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.