All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder
@ 2026-09-15 10:16 Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 01/13] iothread: introduce holder tracking Zhang Chen
                   ` (12 more replies)
  0 siblings, 13 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  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 or
block exports 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
* Holder tracking

  Add a QAPI holder list to each IOThread.  A holder identifies either
  a QOM object by its canonical path or a block export by its unique
  BlockExportOptions ID.

* Explicit reference lifecycle

  Add iothread_ref_and_get_aio_context() and the matching
  iothread_unref_and_put_aio_context().  Acquiring an AioContext registers a
  holder and keeps the IOThread alive until the matching put.

  Legacy callers without a matching lifecycle use the explicitly named
  iothread_unsafe_get_aio_context().  The ambiguous legacy
  iothread_get_aio_context() is removed after all production callers
  are migrated.

* Subsystem integration

  Track holders for block exports, monitors, virtio-blk, virtio-scsi,
  virtio queue mappings, virtio balloon thread pinning, COLO compare,
  vfio-user proxy, and xen-block.

* Introspection

  Extend IOThreadInfo with a holders array returned by
  query-iothreads.  Display the same information with info iothreads.


### V17 -> V16 Changelog
  - Rebase code to resolve code conflicts for patch 8:
    "virtio-balloon: Update tracking iothread users with holder".
    This issue caused by latest merged patch "fdc3f9a83d".

### V16 -> V15 Changelog
  - Unified per-monitor IOThread checks and accesses through
    mon->iothread after initialization.
  - Simplified PATCH 13's commit message and clarified why blockdev
    cannot use the holder-aware get/put APIs.
  - Dropped the v15 Patch 1 in this series, It will be taken away
    independently by trivial-patches tree.

### V15 -> V14 Changelog
  - Moved monitor IOThread cleanup from unparent() to monitor_finalize(),
    pairing complete() with instance finalization.
  - Squashed V14 PATCH 04 and PATCH 14 into PATCH 13 and clarified the
    blockdev lifetime assumption. Since the additional changes are minimal,
    PATCH 13 retains Stefan's Reviewed-by from V14 PATCH 04.
  - Rebased and adapted info iothreads to the MonitorHMP interface.

### V14 -> V13 Changelog
  - Renamed patch 3 and clarified that it introduces holder-tracking
    infrastructure, with users converted in subsequent patches.
  - Documented that monitor holders are not visible to management
    applications, while retaining the tracking for a consistent holder
    and IOThread reference lifecycle.
  - Rebased on the latest upstream.

### V13 -> V12 Changelog
  - Refactored block export IOThread reference handling with shared
    init/cleanup helpers and preserved the existing single-IOThread
    driver arguments.
  - Moved monitor IOThread cleanup to the QOM unparent callback,
    where the canonical path is still available.
  - Kept internal IOThreads hidden from query-iothreads.
  - Made virtio queue-mapping holder arguments const.
  - Improved some detail comments.

### V12 -> V11 Changelog
  - Change the function name from the "iothread_put_aio_context()"
    to "iothread_unref_and_put_aio_context()" match the
    "iothread_unref_and_put_aio_context".
  - Fix unnecessary char * cast since the holder variable is
    declared const.
  - Add detailed commit msg in patch 06/15.
  - Optimize the HMP print in patch 13/15.
  - Rebase code on upstream.



Zhang Chen (13):
  iothread: introduce holder tracking
  iothread: add holder-aware AioContext accessors
  block/export: track IOThread references
  monitor: track IOThread users with QOM paths
  virtio-vq-mapping: track iothread-vq-mapping references using device
    path
  virtio: track IOThread references 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
  monitor/hmp: display IOThread holders
  iothread: make legacy AioContext getter explicitly unsafe
  tests/unit/iothread: update AioContext ref/put helpers

 block/export/export.c                   | 62 +++++++++++++---
 blockdev.c                              |  9 ++-
 hw/block/dataplane/xen-block.c          | 19 ++++-
 hw/block/virtio-blk.c                   | 21 ++++--
 hw/scsi/virtio-scsi-dataplane.c         | 22 ++++--
 hw/vfio-user/proxy.c                    | 16 ++++-
 hw/virtio/iothread-vq-mapping.c         | 20 ++++--
 hw/virtio/virtio-balloon.c              | 21 ++++--
 include/block/export.h                  |  5 ++
 include/hw/virtio/iothread-vq-mapping.h |  6 +-
 include/hw/virtio/virtio-balloon.h      |  1 +
 include/system/iothread.h               | 38 +++++++++-
 iothread.c                              | 89 ++++++++++++++++++++++-
 monitor/hmp-cmds.c                      | 20 ++++++
 monitor/monitor-internal.h              |  7 +-
 monitor/monitor.c                       | 36 +++++++---
 monitor/qmp.c                           | 14 ++--
 net/colo-compare.c                      | 29 +++++---
 qapi/misc.json                          | 94 +++++++++++++++++++++----
 tests/unit/iothread.c                   | 18 +++--
 tests/unit/iothread.h                   |  7 +-
 tests/unit/test-aio-multithread.c       |  5 +-
 tests/unit/test-bdrv-drain.c            | 18 +++--
 tests/unit/test-block-iothread.c        | 21 ++++--
 24 files changed, 496 insertions(+), 102 deletions(-)

-- 
2.43.0



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

* [PATCH V17 01/13] iothread: introduce holder tracking
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 02/13] iothread: add holder-aware AioContext accessors Zhang Chen
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

IOThreads do not record the QOM objects and block exports that use
them, making runtime introspection and hotplug debugging difficult.

Add:

- holders: a list describing what is holding an iothread
- iothread_ref(): add a holder to the list
- iothread_unref(): remove a holder from the list

A holder can identify either a QOM object or a block export. A later
patch will expose this information through query-iothreads.

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 include/system/iothread.h |  5 +++
 iothread.c                | 63 ++++++++++++++++++++++++++
 qapi/misc.json            | 94 ++++++++++++++++++++++++++++++++++-----
 3 files changed, 150 insertions(+), 12 deletions(-)

diff --git a/include/system/iothread.h b/include/system/iothread.h
index a1ef7696cb..b8aeb32b0e 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"
 
@@ -50,6 +51,7 @@ struct IOThread {
     bool stopping;              /* has iothread_stop() been called? */
     bool running;               /* should iothread_run() continue? */
     int thread_id;
+    IOThreadHolderList *holders;
 
     /* AioContext poll parameters */
     int64_t poll_max_ns;
@@ -82,4 +84,7 @@ void iothread_destroy(IOThread *iothread);
  */
 bool qemu_in_iothread(void);
 
+void iothread_ref(IOThread *iothread, const IOThreadHolder *holder);
+void iothread_unref(IOThread *iothread, const IOThreadHolder *holder);
+
 #endif /* IOTHREAD_H */
diff --git a/iothread.c b/iothread.c
index 76eea6df38..6b0a7c557a 100644
--- a/iothread.c
+++ b/iothread.c
@@ -21,10 +21,71 @@
 #include "system/iothread.h"
 #include "qapi/error.h"
 #include "qapi/qapi-commands-misc.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qapi-visit-misc.h"
 #include "qemu/error-report.h"
 #include "qemu/rcu.h"
 #include "qemu/main-loop.h"
 
+/* Add a deep copy of @holder to @iothread's list of holders. */
+void iothread_ref(IOThread *iothread, const IOThreadHolder *holder)
+{
+    assert(holder);
+
+    QAPI_LIST_PREPEND(iothread->holders, QAPI_CLONE(IOThreadHolder, holder));
+}
+
+static int iothread_holder_compare(const IOThreadHolder *holder_a,
+                                   const IOThreadHolder *holder_b)
+{
+    const char *name_a, *name_b;
+
+    if (holder_a->type != holder_b->type) {
+        return holder_a->type - holder_b->type;
+    }
+
+    switch (holder_a->type) {
+    case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
+        name_a = holder_a->u.qom_object.qom_path;
+        name_b = holder_b->u.qom_object.qom_path;
+        break;
+    case IO_THREAD_HOLDER_KIND_BLOCK_EXPORT:
+        name_a = holder_a->u.block_export.export_id;
+        name_b = holder_b->u.block_export.export_id;
+        break;
+    default:
+        g_assert_not_reached();
+    }
+
+    return strcmp(name_a, name_b);
+}
+
+/*
+ * Remove @holder from @iothread's list of holders.
+ *
+ * It is a programming error if @holder is not present.
+ */
+void iothread_unref(IOThread *iothread, const IOThreadHolder *holder)
+{
+    IOThreadHolderList **prev = &iothread->holders;
+    IOThreadHolderList *curr;
+
+    assert(holder);
+
+    while (*prev) {
+        curr = *prev;
+        if (iothread_holder_compare(curr->value, holder) == 0) {
+            *prev = curr->next;
+            curr->next = NULL;
+            qapi_free_IOThreadHolderList(curr);
+            return;
+        }
+        prev = &curr->next;
+    }
+
+    g_assert_not_reached();
+}
+
 static void *iothread_run(void *opaque)
 {
     IOThread *iothread = opaque;
@@ -129,6 +190,7 @@ static void iothread_instance_finalize(Object *obj)
         iothread->main_loop = NULL;
     }
     qemu_sem_destroy(&iothread->init_done_sem);
+    qapi_free_IOThreadHolderList(iothread->holders);
 }
 
 static void iothread_init_gcontext(IOThread *iothread, const char *thread_name)
@@ -373,6 +435,7 @@ static int query_one_iothread(Object *object, void *opaque)
     info = g_new0(IOThreadInfo, 1);
     info->id = iothread_get_id(iothread);
     info->thread_id = iothread->thread_id;
+    info->holders = QAPI_CLONE(IOThreadHolderList, iothread->holders);
     info->poll_max_ns = iothread->poll_max_ns;
     info->poll_grow = iothread->poll_grow;
     info->poll_shrink = iothread->poll_shrink;
diff --git a/qapi/misc.json b/qapi/misc.json
index baa50cc844..bb49969b4b 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -67,6 +67,55 @@
 ##
 { 'command': 'query-name', 'returns': 'NameInfo', 'allow-preconfig': true }
 
+##
+# @IOThreadHolderBlockExport:
+#
+# @export-id: The unique block export identifier.
+#
+# Since: 11.2
+#
+##
+{ 'struct': 'IOThreadHolderBlockExport',
+  'data': { 'export-id': 'str' } }
+
+##
+# @IOThreadHolderQomObject:
+#
+# @qom-path: Path to the object in the QOM tree.
+#
+# Since: 11.2
+#
+##
+{ 'struct': 'IOThreadHolderQomObject',
+  'data': { 'qom-path': 'str' } }
+
+##
+# @IOThreadHolderKind:
+#
+# @block-export: A block export.
+# @qom-object: A QOM object.
+#
+# Since: 11.2
+##
+{ 'enum': 'IOThreadHolderKind',
+  'data': [ 'block-export', 'qom-object' ] }
+
+##
+# @IOThreadHolder:
+#
+# The block export or QOM object holding the iothread.
+#
+# @type: the kind of iothread holder.
+#
+# Since: 11.2
+##
+{ 'union': 'IOThreadHolder',
+  'base': { 'type': 'IOThreadHolderKind' },
+  'discriminator': 'type',
+  'data': {
+    'block-export': 'IOThreadHolderBlockExport',
+    'qom-object': 'IOThreadHolderQomObject' } }
+
 ##
 # @IOThreadInfo:
 #
@@ -76,6 +125,10 @@
 #
 # @thread-id: ID of the underlying host thread
 #
+# @holders: the QOM objects or block exports currently holding this
+#     iothread.  When a holder is detached or destroyed, it is removed
+#     from this list.  (Since 11.2)
+#
 # @poll-max-ns: maximum polling time in ns, 0 means polling is
 #     disabled (since 2.9)
 #
@@ -98,6 +151,7 @@
 { 'struct': 'IOThreadInfo',
   'data': {'id': 'str',
            'thread-id': 'int',
+           'holders': ['IOThreadHolder'],
            'poll-max-ns': 'int',
            'poll-grow': 'int',
            'poll-shrink': 'int',
@@ -122,20 +176,36 @@
 #     -> { "execute": "query-iothreads" }
 #     <- { "return": [
 #              {
-#                 "id":"iothread0",
-#                 "thread-id":3134,
-#                 "poll-max-ns":32768,
-#                 "poll-grow":0,
-#                 "poll-shrink":0,
-#                 "aio-max-batch":0
+#                 "id": "iothread0",
+#                 "thread-id": 3134,
+#                 "holders": [
+#                     {
+#                         "qom-path": "/machine/peripheral/blk1/virtio-backend",
+#                         "type": "qom-object"
+#                     },
+#                     {
+#                         "qom-path": "/machine/peripheral/blk2/virtio-backend",
+#                         "type": "qom-object"
+#                     }
+#                 ],
+#                 "poll-max-ns": 32768,
+#                 "poll-grow": 0,
+#                 "poll-shrink": 0,
+#                 "aio-max-batch": 0
 #              },
 #              {
-#                 "id":"iothread1",
-#                 "thread-id":3135,
-#                 "poll-max-ns":32768,
-#                 "poll-grow":0,
-#                 "poll-shrink":0,
-#                 "aio-max-batch":0
+#                 "id": "iothread1",
+#                 "thread-id": 3135,
+#                 "holders": [
+#                     {
+#                         "export-id": "export0",
+#                         "type": "block-export"
+#                     }
+#                 ],
+#                 "poll-max-ns": 32768,
+#                 "poll-grow": 0,
+#                 "poll-shrink": 0,
+#                 "aio-max-batch": 0
 #              }
 #           ]
 #        }
-- 
2.43.0



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

* [PATCH V17 02/13] iothread: add holder-aware AioContext accessors
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 01/13] iothread: introduce holder tracking Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 03/13] block/export: track IOThread references Zhang Chen
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Introduce iothread_ref_and_get_aio_context() with a holder argument
and its counterpart iothread_unref_and_put_aio_context().

Previously, users of an IOThread AioContext did not explicitly record
their identity, making it difficult to debug which devices or
subsystems were pinning an IOThread. These new helpers provide the
infrastructure for callers to register their identity. Subsequent
patches convert existing users to the new helpers.

Registering a holder takes an IOThread object reference so that the
IOThread and its AioContext stay alive until the matching put
operation. Document the ownership and BQL requirements.

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 include/system/iothread.h | 22 ++++++++++++++++++++++
 iothread.c                | 24 +++++++++++++++++++++++-
 2 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/include/system/iothread.h b/include/system/iothread.h
index b8aeb32b0e..f6c5f95dd9 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -67,6 +67,28 @@ 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);
+
+/*
+ * Register @holder and return @iothread's AioContext.  The holder is copied,
+ * and a reference is taken on @iothread so that both the IOThread and its
+ * AioContext remain alive.
+ *
+ * The caller must eventually call iothread_unref_and_put_aio_context() with an
+ * equivalent holder.  This function is not thread-safe and must be called
+ * under the Big QEMU Lock (BQL).
+ */
+AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
+                                             const IOThreadHolder *holder);
+
+/*
+ * Unregister @holder and release the corresponding reference on @iothread.
+ * Calling this function without a matching
+ * iothread_ref_and_get_aio_context() call is a programming error.
+ *
+ * This function is not thread-safe and must be called under the BQL.
+ */
+void iothread_unref_and_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 6b0a7c557a..5a4291a2f0 100644
--- a/iothread.c
+++ b/iothread.c
@@ -33,6 +33,11 @@ void iothread_ref(IOThread *iothread, const IOThreadHolder *holder)
     assert(holder);
 
     QAPI_LIST_PREPEND(iothread->holders, QAPI_CLONE(IOThreadHolder, holder));
+    /*
+     * 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(const IOThreadHolder *holder_a,
@@ -78,6 +83,7 @@ void iothread_unref(IOThread *iothread, const IOThreadHolder *holder)
             *prev = curr->next;
             curr->next = NULL;
             qapi_free_IOThreadHolderList(curr);
+            object_unref(OBJECT(iothread));
             return;
         }
         prev = &curr->next;
@@ -199,7 +205,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);
@@ -421,6 +427,22 @@ AioContext *iothread_get_aio_context(IOThread *iothread)
     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_unref_and_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.43.0



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

* [PATCH V17 03/13] block/export: track IOThread references
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 01/13] iothread: introduce holder tracking Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 02/13] iothread: add holder-aware AioContext accessors Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 04/13] monitor: track IOThread users with QOM paths Zhang Chen
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Track the IOThreads used by a block export and identify the holder
with the unique BlockExportOptions id.

Acquire holder-aware references during export creation and release
them on error or export deletion.  Support both single- and
multi-iothread exports.

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 block/export/export.c  | 62 ++++++++++++++++++++++++++++++++++++------
 include/block/export.h |  5 ++++
 2 files changed, 58 insertions(+), 9 deletions(-)

diff --git a/block/export/export.c b/block/export/export.c
index b733f269f3..0e019545ef 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"
@@ -72,6 +71,32 @@ static const BlockExportDriver *blk_exp_find_driver(BlockExportType type)
     return NULL;
 }
 
+static void init_iothreads(const char *holder_id, IOThread **iothreads,
+                           size_t num_iothreads, AioContext **aio_ctxs)
+{
+    const IOThreadHolder holder = {
+        .type = IO_THREAD_HOLDER_KIND_BLOCK_EXPORT,
+        .u.block_export.export_id = (char *)holder_id,
+    };
+
+    for (size_t i = 0; i < num_iothreads; i++) {
+        aio_ctxs[i] = iothread_ref_and_get_aio_context(iothreads[i], &holder);
+    }
+}
+
+static void cleanup_iothreads(const char *holder_id, IOThread **iothreads,
+                              size_t num_iothreads)
+{
+    const IOThreadHolder holder = {
+        .type = IO_THREAD_HOLDER_KIND_BLOCK_EXPORT,
+        .u.block_export.export_id = (char *)holder_id,
+    };
+
+    for (size_t i = 0; i < num_iothreads; i++) {
+        iothread_unref_and_put_aio_context(iothreads[i], &holder);
+    }
+}
+
 BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
 {
     bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread;
@@ -85,6 +110,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;
+    size_t iothread_count = 0;
     uint64_t perm;
     int ret;
 
@@ -139,7 +166,10 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
             goto fail;
         }
 
-        new_ctx = iothread_get_aio_context(iothread);
+        local_iothreads = g_new0(IOThread *, 1);
+        local_iothreads[0] = iothread;
+        init_iothreads(export->id, local_iothreads, 1, &new_ctx);
+        iothread_count = 1;
 
         /* Ignore errors with fixed-iothread=false */
         set_context_errp = fixed_iothread ? errp : NULL;
@@ -163,8 +193,10 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
             return NULL;
         }
 
+        local_iothreads = g_new0(IOThread *, multithread_count);
         multithread_ctxs = g_new(AioContext *, multithread_count);
         i = 0;
+
         for (strList *e = iothread_list; e; e = e->next) {
             IOThread *iothread = iothread_by_id(e->value);
 
@@ -172,9 +204,12 @@ 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;
         }
         assert(i == multithread_count);
+        init_iothreads(export->id, local_iothreads, multithread_count,
+                       multithread_ctxs);
+        iothread_count = multithread_count;
     }
 
     bdrv_graph_rdlock_main_loop();
@@ -225,12 +260,14 @@ 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       = iothread_count,
     };
 
     ret = drv->create(exp, export, multithread_ctxs, multithread_count, errp);
@@ -250,8 +287,12 @@ fail:
         blk_unref(blk);
     }
     if (exp) {
+        cleanup_iothreads(exp->id, exp->iothreads, exp->iothread_count);
+        g_free(exp->iothreads);
         g_free(exp->id);
         g_free(exp);
+    } else {
+        cleanup_iothreads(export->id, local_iothreads, iothread_count);
     }
     g_free(multithread_ctxs);
     return NULL;
@@ -273,6 +314,9 @@ static void blk_exp_delete_bh(void *opaque)
     exp->drv->delete(exp);
     blk_set_dev_ops(exp->blk, NULL, NULL);
     blk_unref(exp->blk);
+
+    cleanup_iothreads(exp->id, exp->iothreads, exp->iothread_count);
+    g_free(exp->iothreads);
     qapi_event_send_block_export_deleted(exp->id);
     g_free(exp->id);
     g_free(exp);
diff --git a/include/block/export.h b/include/block/export.h
index ca45da928c..ce8e4eb604 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,10 @@ struct BlockExport {
 
     /* List entry for block_exports */
     QLIST_ENTRY(BlockExport) next;
+
+    /* The IOThreads utilized by this specific block export */
+    IOThread **iothreads;
+    size_t iothread_count;
 };
 
 BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp);
-- 
2.43.0



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

* [PATCH V17 04/13] monitor: track IOThread users with QOM paths
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (2 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 03/13] block/export: track IOThread references Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-15 10:49   ` Markus Armbruster
  2026-09-16 19:33   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 05/13] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
                   ` (8 subsequent siblings)
  12 siblings, 2 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Monitors that require an IOThread share the internal mon_iothread.
Account for each monitor explicitly in IOThread holder tracking, using
the monitor's canonical QOM path to distinguish holders.

Acquire the shared monitor IOThread AioContext once and store the
IOThread and canonical QOM path so the holder can be released after the
monitor has been removed from the QOM tree. Use the stored IOThread for
per-monitor checks and accesses after initialization, and reuse the
stored context for later operations.

Release the holder from monitor_finalize(), after resources associated
with the AioContext have been destroyed. This pairs the reference taken
in complete() with instance finalization and keeps the IOThread alive
throughout monitor teardown.

The internal mon_iothread remains hidden from query-iothreads, as before.
Management applications therefore cannot use it to determine which
monitors are using the IOThread. Nevertheless, track the monitor
holders so that they follow the same explicit holder and
IOThread reference lifecycle as other AioContext users.

Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 monitor/monitor-internal.h |  7 +++++--
 monitor/monitor.c          | 36 ++++++++++++++++++++++++++----------
 monitor/qmp.c              | 14 +++++++-------
 3 files changed, 38 insertions(+), 19 deletions(-)

diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
index 822a66d06d..020afdaebf 100644
--- a/monitor/monitor-internal.h
+++ b/monitor/monitor-internal.h
@@ -77,6 +77,11 @@ struct Monitor {
     GString *outbuf;
     guint out_watch;
     int mux_out;
+
+    /* iothread context and holder identity */
+    IOThread *iothread;
+    char *iothread_qom_path;
+    AioContext *ctx;
 };
 
 struct MonitorQMPClass {
@@ -109,14 +114,12 @@ struct MonitorQMP {
 };
 
 typedef QTAILQ_HEAD(MonitorList, Monitor) MonitorList;
-extern IOThread *mon_iothread;
 extern Coroutine *qmp_dispatcher_co;
 extern bool qmp_dispatcher_co_shutdown;
 extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
 extern QemuMutex monitor_lock;
 extern MonitorList mon_list;
 
-bool monitor_requires_iothread(const Monitor *mon);
 int monitor_can_read(void *opaque);
 void monitor_cancel_out_watch(Monitor *mon);
 void monitor_list_append(Monitor *mon);
diff --git a/monitor/monitor.c b/monitor/monitor.c
index 2654b81890..a80d8dbf45 100644
--- a/monitor/monitor.c
+++ b/monitor/monitor.c
@@ -55,7 +55,7 @@ typedef struct {
 } MonitorQAPIEventConf;
 
 /* Shared monitor I/O thread */
-IOThread *mon_iothread;
+static IOThread *mon_iothread;
 
 /* Coroutine to dispatch the requests received from I/O thread */
 Coroutine *qmp_dispatcher_co;
@@ -93,6 +93,16 @@ static void monitor_finalize(Object *obj)
     qemu_chr_fe_deinit(&mon->chr, false);
     g_string_free(mon->outbuf, true);
     qemu_mutex_destroy(&mon->mon_lock);
+
+    if (mon->iothread) {
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = mon->iothread_qom_path,
+        };
+
+        iothread_unref_and_put_aio_context(mon->iothread, &io_holder);
+    }
+    g_free(mon->iothread_qom_path);
 }
 
 static char *monitor_get_chardev_id(Object *obj, Error **errp)
@@ -164,7 +174,7 @@ Monitor *monitor_set_cur(Coroutine *co, Monitor *mon)
     return old_monitor;
 }
 
-bool monitor_requires_iothread(const Monitor *mon)
+static bool monitor_requires_iothread(const Monitor *mon)
 {
     MonitorClass *cls = MONITOR_GET_CLASS(mon);
     return cls->requires_iothread && cls->requires_iothread(mon);
@@ -188,8 +198,8 @@ void monitor_cancel_out_watch(Monitor *mon)
         GMainContext *ctx = NULL;
         GSource *src;
 
-        if (monitor_requires_iothread(mon)) {
-            ctx = iothread_get_g_main_context(mon_iothread);
+        if (mon->iothread) {
+            ctx = iothread_get_g_main_context(mon->iothread);
         }
         src = g_main_context_find_source_by_id(ctx, mon->out_watch);
         if (!src && ctx) {
@@ -516,12 +526,12 @@ void monitor_suspend(Monitor *mon)
 {
     qatomic_inc(&mon->suspend_cnt);
 
-    if (monitor_requires_iothread(mon)) {
+    if (mon->iothread) {
         /*
          * 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);
@@ -661,7 +671,6 @@ char *monitor_compat_id(void)
 static void monitor_complete(UserCreatable *uc, Error **errp)
 {
     Monitor *mon = MONITOR(uc);
-    AioContext *ctx;
 
     if (mon->chardev_id) {
         Chardev *chr = qemu_chr_find(mon->chardev_id);
@@ -680,11 +689,18 @@ static void monitor_complete(UserCreatable *uc, Error **errp)
             mon_iothread = iothread_create("mon_iothread", &error_abort);
         }
 
-        ctx = iothread_get_aio_context(mon_iothread);
+        mon->iothread = mon_iothread;
+        mon->iothread_qom_path = object_get_canonical_path(OBJECT(mon));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = mon->iothread_qom_path,
+        };
+
+        mon->ctx = iothread_ref_and_get_aio_context(mon->iothread, &io_holder);
     } else {
-        ctx = qemu_get_aio_context();
+        mon->ctx = qemu_get_aio_context();
     }
-    mon->accept_input_bh = aio_bh_new(ctx, monitor_accept_input, mon);
+    mon->accept_input_bh = aio_bh_new(mon->ctx, monitor_accept_input, mon);
 }
 
 int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
diff --git a/monitor/qmp.c b/monitor/qmp.c
index aec0315775..e235302fb3 100644
--- a/monitor/qmp.c
+++ b/monitor/qmp.c
@@ -187,7 +187,7 @@ static void monitor_qmp_caps_reset(MonitorQMP *mon)
     memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
     memset(mon->capab, 0, sizeof(mon->capab));
     mon->capab_offered[QMP_CAPABILITY_OOB] =
-        monitor_requires_iothread(MONITOR(mon));
+        MONITOR(mon)->iothread != NULL;
 }
 
 static void qmp_request_free(QMPRequest *req)
@@ -678,8 +678,8 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
     MonitorQMP *mon = opaque;
     GMainContext *context;
 
-    assert(monitor_requires_iothread(MONITOR(mon)));
-    context = iothread_get_g_main_context(mon_iothread);
+    assert(MONITOR(mon)->iothread);
+    context = iothread_get_g_main_context(MONITOR(mon)->iothread);
     assert(context);
     qemu_chr_fe_set_handlers(&mon->parent_obj.chr, monitor_can_read,
                              monitor_qmp_read, monitor_qmp_event,
@@ -717,7 +717,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
 
     qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
 
-    if (monitor_requires_iothread(MONITOR(mon))) {
+    if (MONITOR(mon)->iothread) {
         /*
          * Make sure the old iowatch is gone.  It's possible when
          * e.g. the chardev is in client mode, with wait=on.
@@ -734,7 +734,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
          * thread.  Schedule a bottom half.
          */
         mon->setup_pending = true;
-        aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
+        aio_bh_schedule_oneshot(MONITOR(mon)->ctx,
                                 monitor_qmp_setup_handlers_bh, mon);
         /* The bottom half will add @mon to @mon_list */
     } else {
@@ -788,8 +788,8 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
     }
 
     /* Synchronize with in-flight iothread callbacks. */
-    if (monitor_requires_iothread(mon)) {
-        aio_wait_bh_oneshot(iothread_get_aio_context(mon_iothread),
+    if (mon->iothread) {
+        aio_wait_bh_oneshot(mon->ctx,
                             monitor_qmp_iothread_quiesce, NULL);
     }
 
-- 
2.43.0



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

* [PATCH V17 05/13] virtio-vq-mapping: track iothread-vq-mapping references using device path
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (3 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 04/13] monitor: track IOThread users with QOM paths Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 19:36   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 06/13] virtio: track IOThread references for thread pinning Zhang Chen
                   ` (7 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Replace raw object_ref/unref calls with iothread_ref_and_get/
unref_and_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 61c341ce2c..b2887d74f9 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1499,9 +1499,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;
@@ -1533,7 +1536,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 bcb6e44133..ac13f4a1ca 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..60e1d55005 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);
+        const 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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = (char *)holder,
+        };
+
+        iothread_unref_and_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.43.0



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

* [PATCH V17 06/13] virtio: track IOThread references for thread pinning
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (4 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 05/13] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 19:38   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 07/13] net/colo: track IOThread references using path-based holder Zhang Chen
                   ` (6 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Use the virtio device canonical QOM path as the holder when virtio-blk
and virtio-scsi acquire an IOThread AioContext.

Replace the raw object references with the holder-aware
acquire/release helpers so the users are visible through IOThread
introspection.

Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 hw/block/virtio-blk.c           | 23 +++++++++++++----------
 hw/scsi/virtio-scsi-dataplane.c | 23 +++++++++++++----------
 2 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
index b2887d74f9..9f567f4c65 100644
--- a/hw/block/virtio-blk.c
+++ b/hw/block/virtio-blk.c
@@ -1475,6 +1475,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,
@@ -1499,8 +1500,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,
@@ -1511,13 +1510,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);
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = 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++) {
@@ -1532,18 +1533,20 @@ 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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = path,
+        };
+        iothread_unref_and_put_aio_context(conf->iothread, &io_holder);
     }
 
     g_free(s->vq_aio_context);
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
index ac13f4a1ca..d66946f279 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);
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = 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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = path,
+        };
+        iothread_unref_and_put_aio_context(vs->conf.iothread, &io_holder);
     }
 
     g_free(s->vq_aio_context);
-- 
2.43.0



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

* [PATCH V17 07/13] net/colo: track IOThread references using path-based holder
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (5 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 06/13] virtio: track IOThread references for thread pinning Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 19:47   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 08/13] virtio-balloon: Update tracking iothread users with holder Zhang Chen
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Convert colo-compare to use the iothread_ref_and_get_aio_context() and
iothread_unref_and_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 5986fb1e88..361ee32729 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;
 
@@ -922,9 +923,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) +
@@ -964,8 +963,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));
+    const IOThreadHolder io_holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = 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,
@@ -1404,6 +1410,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) {
@@ -1430,18 +1437,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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = path,
+        };
+        iothread_unref_and_put_aio_context(s->iothread, &io_holder);
     }
 
     g_queue_clear(&s->conn_list);
-- 
2.43.0



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

* [PATCH V17 08/13] virtio-balloon: Update tracking iothread users with holder
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (6 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 07/13] net/colo: track IOThread references using path-based holder Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 19:48   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Replace raw object_ref/unref calls with
iothread_ref_and_get/unref_and_put_aio_context.

Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 hw/virtio/virtio-balloon.c         | 21 ++++++++++++++++-----
 include/hw/virtio/virtio-balloon.h |  1 +
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c
index 07d519cf6d..cfc581940e 100644
--- a/hw/virtio/virtio-balloon.c
+++ b/hw/virtio/virtio-balloon.c
@@ -896,12 +896,19 @@ 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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = 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),
+        s->free_page_ctx = iothread_ref_and_get_aio_context(s->iothread,
+                                                          &io_holder);
+        s->free_page_bh = aio_bh_new_guarded(s->free_page_ctx,
                                              virtio_ballloon_get_free_page_hints, s,
                                              &dev->mem_reentrancy_guard);
     }
@@ -928,7 +935,11 @@ static void virtio_balloon_device_unrealize(DeviceState *dev)
 
     qemu_unregister_resettable(OBJECT(dev));
     if (s->free_page_bh) {
-        AioContext *ctx = iothread_get_aio_context(s->iothread);
+        g_autofree char *path = object_get_canonical_path(OBJECT(s));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = path,
+        };
 
         qemu_bh_delete(s->free_page_bh);
 
@@ -936,9 +947,9 @@ static void virtio_balloon_device_unrealize(DeviceState *dev)
         s->free_page_hint_status = FREE_PAGE_HINT_S_UNREALIZE;
         qemu_cond_signal(&s->free_page_cond);
         qemu_mutex_unlock(&s->free_page_lock);
-        aio_wait_bh_oneshot(ctx, dummy_bh, NULL);
+        aio_wait_bh_oneshot(s->free_page_ctx, dummy_bh, NULL);
 
-        object_unref(OBJECT(s->iothread));
+        iothread_unref_and_put_aio_context(s->iothread, &io_holder);
         precopy_remove_notifier(&s->free_page_hint_notify);
     }
     balloon_stats_destroy_timer(s);
diff --git a/include/hw/virtio/virtio-balloon.h b/include/hw/virtio/virtio-balloon.h
index 53bbaa4c9a..5c3731b3e9 100644
--- a/include/hw/virtio/virtio-balloon.h
+++ b/include/hw/virtio/virtio-balloon.h
@@ -54,6 +54,7 @@ struct VirtIOBalloon {
     size_t stats_vq_offset;
     QEMUTimer *stats_timer;
     IOThread *iothread;
+    AioContext *free_page_ctx;
     QEMUBH *free_page_bh;
     /*
      * Lock to synchronize threads to access the free page reporting related
-- 
2.43.0



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

* [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (7 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 08/13] virtio-balloon: Update tracking iothread users with holder Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 19:54   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 10/13] xen-block: " Zhang Chen
                   ` (3 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Add object_ref/unref calls with
iothread_ref_and_get/unref_and_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 197aee07bf..e642fb0ad3 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 = NULL;
 
     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));
+    const IOThreadHolder io_holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = 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));
+    const IOThreadHolder io_holder = {
+        .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+        .u.qom_object.qom_path = 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_unref_and_put_aio_context(vfio_user_iothread, &io_holder);
+
     QLIST_REMOVE(proxy, next);
     if (QLIST_EMPTY(&vfio_user_sockets)) {
         iothread_destroy(vfio_user_iothread);
-- 
2.43.0



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

* [PATCH V17 10/13] xen-block: Update tracking iothread users with holder name
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (8 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 19:56   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 11/13] monitor/hmp: display IOThread holders Zhang Chen
                   ` (2 subsequent siblings)
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Replace raw object_ref/unref calls with
iothread_ref_and_get/unref_and_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..9dfe06e1be 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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = 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));
+        const IOThreadHolder io_holder = {
+            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
+            .u.qom_object.qom_path = path,
+        };
+
+        iothread_unref_and_put_aio_context(dataplane->iothread, &io_holder);
     }
 
     g_free(dataplane);
-- 
2.43.0



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

* [PATCH V17 11/13] monitor/hmp: display IOThread holders
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (9 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 10/13] xen-block: " Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-16 20:01   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe Zhang Chen
  2026-09-15 10:16 ` [PATCH V17 13/13] tests/unit/iothread: update AioContext ref/put helpers Zhang Chen
  12 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Display holder information in info iothreads to aid manual debugging.

Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 monitor/hmp-cmds.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index 91701ddf33..d7dd24d951 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -246,11 +246,31 @@ void hmp_info_iothreads(MonitorHMP *hmp, 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_hmp_printf(hmp, "%s:\n", value->id);
         monitor_hmp_printf(hmp, "  thread_id=%" PRId64 "\n", value->thread_id);
+        monitor_hmp_printf(hmp, "  holders=");
+        for (h = value->holders; h; h = h->next) {
+            IOThreadHolder *holder = h->value;
+
+            switch (holder->type) {
+            case IO_THREAD_HOLDER_KIND_BLOCK_EXPORT:
+                monitor_hmp_printf(hmp, "[block-export: %s]",
+                                   holder->u.block_export.export_id);
+                break;
+            case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
+                monitor_hmp_printf(hmp, "[qom-path: %s]",
+                                   holder->u.qom_object.qom_path);
+                break;
+            default:
+                g_assert_not_reached();
+            }
+        }
+        monitor_hmp_printf(hmp, "\n");
+
         monitor_hmp_printf(hmp, "  poll-max-ns=%" PRId64 "\n", value->poll_max_ns);
         monitor_hmp_printf(hmp, "  poll-grow=%" PRId64 "\n", value->poll_grow);
         monitor_hmp_printf(hmp, "  poll-shrink=%" PRId64 "\n", value->poll_shrink);
-- 
2.43.0



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

* [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (10 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 11/13] monitor/hmp: display IOThread holders Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  2026-09-15 10:59   ` Markus Armbruster
  2026-09-16 20:09   ` Stefan Hajnoczi
  2026-09-15 10:16 ` [PATCH V17 13/13] tests/unit/iothread: update AioContext ref/put helpers Zhang Chen
  12 siblings, 2 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Now that holder-aware callers have been converted, rename
iothread_get_aio_context() to iothread_unsafe_get_aio_context() and
update qmp_x_blockdev_set_iothread().

The block graph retains only the AioContext, while detachment happens
outside qmp_x_blockdev_set_iothread(), so this call site cannot provide
a matching holder lifecycle. The new name makes its existing lifetime
assumption explicit.

New code should use iothread_ref_and_get_aio_context().

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 blockdev.c                |  9 ++++++++-
 include/system/iothread.h | 11 ++++++++++-
 iothread.c                |  2 +-
 3 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/blockdev.c b/blockdev.c
index 6e86c6262f..3bd32feb39 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3683,7 +3683,14 @@ void qmp_x_blockdev_set_iothread(const char *node_name, StrOrNull *iothread,
             goto out;
         }
 
-        new_context = iothread_get_aio_context(obj);
+        /*
+         * We cannot use iothread_ref_and_get_aio_context() /
+         * iothread_unref_and_put_aio_context() here. The block graph stores
+         * the AioContext but not the IOThread, and continues using the
+         * context after this command returns. A matching put would need
+         * to be tied to the block graph's context lifecycle.
+         */
+        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 f6c5f95dd9..9850cdb3dc 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -66,7 +66,16 @@ 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);
+
+/*
+ * Return @iothread's AioContext without registering a holder or taking a
+ * reference on @iothread.  The caller must ensure that the IOThread remains
+ * alive for as long as the returned AioContext is used.
+ *
+ * This API exists for legacy callers without a clear ref/unref lifecycle.  Do
+ * not use it in new code; use iothread_ref_and_get_aio_context() instead.
+ */
+AioContext *iothread_unsafe_get_aio_context(IOThread *iothread);
 
 /*
  * Register @holder and return @iothread's AioContext.  The holder is copied,
diff --git a/iothread.c b/iothread.c
index 5a4291a2f0..28460afb3f 100644
--- a/iothread.c
+++ b/iothread.c
@@ -422,7 +422,7 @@ char *iothread_get_id(IOThread *iothread)
     return g_strdup(object_get_canonical_path_component(OBJECT(iothread)));
 }
 
-AioContext *iothread_get_aio_context(IOThread *iothread)
+AioContext *iothread_unsafe_get_aio_context(IOThread *iothread)
 {
     return iothread->ctx;
 }
-- 
2.43.0



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

* [PATCH V17 13/13] tests/unit/iothread: update AioContext ref/put helpers
  2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
                   ` (11 preceding siblings ...)
  2026-09-15 10:16 ` [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe Zhang Chen
@ 2026-09-15 10:16 ` Zhang Chen
  12 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-15 10:16 UTC (permalink / raw)
  To: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang
  Cc: Zhang Chen

Update the unit-test IOThread stubs and callers to use
iothread_ref_and_get_aio_context() together with the matching
iothread_unref_and_put_aio_context().

Signed-off-by: Zhang Chen <zhangckid@gmail.com>
---
 tests/unit/iothread.c             | 18 ++++++++++++------
 tests/unit/iothread.h             |  7 ++++++-
 tests/unit/test-aio-multithread.c |  5 ++++-
 tests/unit/test-bdrv-drain.c      | 18 +++++++++++++-----
 tests/unit/test-block-iothread.c  | 21 ++++++++++++++-------
 5 files changed, 49 insertions(+), 20 deletions(-)

diff --git a/tests/unit/iothread.c b/tests/unit/iothread.c
index a363bf8f70..790cd2dce7 100644
--- a/tests/unit/iothread.c
+++ b/tests/unit/iothread.c
@@ -30,12 +30,23 @@ struct IOThread {
     bool stopping;
 };
 
+AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
+                                             IOThreadHolder *holder)
+{
+    return iothread->ctx;
+}
+
+void iothread_unref_and_put_aio_context(IOThread *iothread,
+                                        IOThreadHolder *holder)
+{
+}
+
 static void iothread_init_gcontext(IOThread *iothread)
 {
     GSource *source;
 
     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_attach(source, iothread->worker_context);
     g_source_unref(source);
     iothread->main_loop = g_main_loop_new(iothread->worker_context, TRUE);
@@ -113,8 +124,3 @@ IOThread *iothread_new(void)
     qemu_mutex_unlock(&iothread->init_done_lock);
     return iothread;
 }
-
-AioContext *iothread_get_aio_context(IOThread *iothread)
-{
-    return iothread->ctx;
-}
diff --git a/tests/unit/iothread.h b/tests/unit/iothread.h
index eb4d0c77f8..9bdac074c0 100644
--- a/tests/unit/iothread.h
+++ b/tests/unit/iothread.h
@@ -17,9 +17,14 @@
 #include "qemu/thread.h"
 
 typedef struct IOThread IOThread;
+typedef struct IOThreadHolder IOThreadHolder;
+
+AioContext *iothread_ref_and_get_aio_context(IOThread *iothread,
+                                             IOThreadHolder *holder);
+void iothread_unref_and_put_aio_context(IOThread *iothread,
+                                        IOThreadHolder *holder);
 
 IOThread *iothread_new(void);
 void iothread_join(IOThread *iothread);
-AioContext *iothread_get_aio_context(IOThread *iothread);
 
 #endif
diff --git a/tests/unit/test-aio-multithread.c b/tests/unit/test-aio-multithread.c
index 9179cdc6a3..66ba5a466b 100644
--- a/tests/unit/test-aio-multithread.c
+++ b/tests/unit/test-aio-multithread.c
@@ -69,7 +69,7 @@ static void create_aio_contexts(void)
 
     for (i = 0; i < NUM_CONTEXTS; i++) {
         threads[i] = iothread_new();
-        ctx[i] = iothread_get_aio_context(threads[i]);
+        ctx[i] = iothread_ref_and_get_aio_context(threads[i], NULL);
     }
 
     qemu_event_init(&done_event, false);
@@ -87,6 +87,9 @@ static void join_aio_contexts(void)
     for (i = 0; i < NUM_CONTEXTS; i++) {
         aio_context_ref(ctx[i]);
     }
+    for (i = 0; i < NUM_CONTEXTS; i++) {
+        iothread_unref_and_put_aio_context(threads[i], NULL);
+    }
     for (i = 0; i < NUM_CONTEXTS; i++) {
         iothread_join(threads[i]);
     }
diff --git a/tests/unit/test-bdrv-drain.c b/tests/unit/test-bdrv-drain.c
index 43b0ba8648..05c31ffca8 100644
--- a/tests/unit/test-bdrv-drain.c
+++ b/tests/unit/test-bdrv-drain.c
@@ -537,8 +537,8 @@ static void test_iothread_common(enum drain_type drain_type, int drain_thread)
 
     IOThread *a = iothread_new();
     IOThread *b = iothread_new();
-    AioContext *ctx_a = iothread_get_aio_context(a);
-    AioContext *ctx_b = iothread_get_aio_context(b);
+    AioContext *ctx_a = iothread_ref_and_get_aio_context(a, NULL);
+    AioContext *ctx_b = iothread_ref_and_get_aio_context(b, NULL);
 
     QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, NULL, 0);
 
@@ -612,6 +612,8 @@ static void test_iothread_common(enum drain_type drain_type, int drain_thread)
 
     bdrv_unref(bs);
     blk_unref(blk);
+    iothread_unref_and_put_aio_context(a, NULL);
+    iothread_unref_and_put_aio_context(b, NULL);
 
 out:
     iothread_join(a);
@@ -762,7 +764,7 @@ static void test_blockjob_common_drain_node(enum drain_type drain_type,
         AioContext *ctx;
 
         iothread = iothread_new();
-        ctx = iothread_get_aio_context(iothread);
+        ctx = iothread_ref_and_get_aio_context(iothread, NULL);
         blk_set_aio_context(blk_src, ctx, &error_abort);
     }
 
@@ -892,6 +894,10 @@ static void test_blockjob_common_drain_node(enum drain_type drain_type,
     bdrv_unref(src_overlay);
     bdrv_unref(target);
 
+    if (use_iothread) {
+        iothread_unref_and_put_aio_context(iothread, NULL);
+    }
+
     if (iothread) {
         iothread_join(iothread);
     }
@@ -1398,8 +1404,8 @@ static void test_set_aio_context(void)
     BlockDriverState *bs;
     IOThread *a = iothread_new();
     IOThread *b = iothread_new();
-    AioContext *ctx_a = iothread_get_aio_context(a);
-    AioContext *ctx_b = iothread_get_aio_context(b);
+    AioContext *ctx_a = iothread_ref_and_get_aio_context(a, NULL);
+    AioContext *ctx_b = iothread_ref_and_get_aio_context(b, NULL);
 
     bs = bdrv_new_open_driver(&bdrv_test, "test-node", BDRV_O_RDWR,
                               &error_abort);
@@ -1410,6 +1416,8 @@ static void test_set_aio_context(void)
     bdrv_try_change_aio_context(bs, qemu_get_aio_context(), NULL, &error_abort);
 
     bdrv_unref(bs);
+    iothread_unref_and_put_aio_context(a, NULL);
+    iothread_unref_and_put_aio_context(b, NULL);
     iothread_join(a);
     iothread_join(b);
 }
diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c
index 5273ff235a..fc9735d056 100644
--- a/tests/unit/test-block-iothread.c
+++ b/tests/unit/test-block-iothread.c
@@ -466,7 +466,7 @@ static void test_sync_op(const void *opaque)
 {
     const SyncOpTest *t = opaque;
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     BlockBackend *blk;
     BlockDriverState *bs;
     BdrvChild *c;
@@ -493,6 +493,7 @@ static void test_sync_op(const void *opaque)
 
     bdrv_unref(bs);
     blk_unref(blk);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 typedef struct TestBlockJob {
@@ -549,7 +550,7 @@ BlockJobDriver test_job_driver = {
 static void test_attach_blockjob(void)
 {
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     BlockBackend *blk;
     BlockDriverState *bs;
     TestBlockJob *tjob;
@@ -595,6 +596,7 @@ static void test_attach_blockjob(void)
 
     bdrv_unref(bs);
     blk_unref(blk);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 /*
@@ -612,7 +614,7 @@ static void test_attach_blockjob(void)
 static void test_propagate_basic(void)
 {
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     AioContext *main_ctx;
     BlockBackend *blk;
     BlockDriverState *bs_a, *bs_b, *bs_verify;
@@ -658,6 +660,7 @@ static void test_propagate_basic(void)
     bdrv_unref(bs_b);
     bdrv_unref(bs_a);
     blk_unref(blk);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 /*
@@ -676,7 +679,7 @@ static void test_propagate_basic(void)
 static void test_propagate_diamond(void)
 {
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     AioContext *main_ctx;
     BlockBackend *blk;
     BlockDriverState *bs_a, *bs_b, *bs_c, *bs_verify;
@@ -736,12 +739,13 @@ static void test_propagate_diamond(void)
     bdrv_unref(bs_c);
     bdrv_unref(bs_b);
     bdrv_unref(bs_a);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 static void test_propagate_mirror(void)
 {
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     AioContext *main_ctx = qemu_get_aio_context();
     BlockDriverState *src, *target, *filter;
     BlockBackend *blk;
@@ -807,12 +811,13 @@ static void test_propagate_mirror(void)
     blk_unref(blk);
     bdrv_unref(src);
     bdrv_unref(target);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 static void test_attach_second_node(void)
 {
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     AioContext *main_ctx = qemu_get_aio_context();
     BlockBackend *blk;
     BlockDriverState *bs, *filter;
@@ -840,12 +845,13 @@ static void test_attach_second_node(void)
     bdrv_unref(filter);
     bdrv_unref(bs);
     blk_unref(blk);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 static void test_attach_preserve_blk_ctx(void)
 {
     IOThread *iothread = iothread_new();
-    AioContext *ctx = iothread_get_aio_context(iothread);
+    AioContext *ctx = iothread_ref_and_get_aio_context(iothread, NULL);
     BlockBackend *blk;
     BlockDriverState *bs;
 
@@ -871,6 +877,7 @@ static void test_attach_preserve_blk_ctx(void)
     blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
     bdrv_unref(bs);
     blk_unref(blk);
+    iothread_unref_and_put_aio_context(iothread, NULL);
 }
 
 int main(int argc, char **argv)
-- 
2.43.0



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

* Re: [PATCH V17 04/13] monitor: track IOThread users with QOM paths
  2026-09-15 10:16 ` [PATCH V17 04/13] monitor: track IOThread users with QOM paths Zhang Chen
@ 2026-09-15 10:49   ` Markus Armbruster
  2026-09-16  2:13     ` Zhang Chen
  2026-09-16 19:33   ` Stefan Hajnoczi
  1 sibling, 1 reply; 30+ messages in thread
From: Markus Armbruster @ 2026-09-15 10:49 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

Zhang Chen <zhangckid@gmail.com> writes:

> Monitors that require an IOThread share the internal mon_iothread.
> Account for each monitor explicitly in IOThread holder tracking, using
> the monitor's canonical QOM path to distinguish holders.
>
> Acquire the shared monitor IOThread AioContext once and store the
> IOThread and canonical QOM path so the holder can be released after the
> monitor has been removed from the QOM tree. Use the stored IOThread for
> per-monitor checks and accesses after initialization, and reuse the
> stored context for later operations.
>
> Release the holder from monitor_finalize(), after resources associated
> with the AioContext have been destroyed. This pairs the reference taken
> in complete() with instance finalization and keeps the IOThread alive
> throughout monitor teardown.
>
> The internal mon_iothread remains hidden from query-iothreads, as before.
> Management applications therefore cannot use it to determine which
> monitors are using the IOThread. Nevertheless, track the monitor
> holders so that they follow the same explicit holder and
> IOThread reference lifecycle as other AioContext users.
>
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  monitor/monitor-internal.h |  7 +++++--
>  monitor/monitor.c          | 36 ++++++++++++++++++++++++++----------
>  monitor/qmp.c              | 14 +++++++-------
>  3 files changed, 38 insertions(+), 19 deletions(-)
>
> diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> index 822a66d06d..020afdaebf 100644
> --- a/monitor/monitor-internal.h
> +++ b/monitor/monitor-internal.h
> @@ -77,6 +77,11 @@ struct Monitor {
>      GString *outbuf;
>      guint out_watch;
>      int mux_out;
> +
> +    /* iothread context and holder identity */
> +    IOThread *iothread;
> +    char *iothread_qom_path;
> +    AioContext *ctx;
>  };
>  
>  struct MonitorQMPClass {
> @@ -109,14 +114,12 @@ struct MonitorQMP {
>  };
>  
>  typedef QTAILQ_HEAD(MonitorList, Monitor) MonitorList;
> -extern IOThread *mon_iothread;
>  extern Coroutine *qmp_dispatcher_co;
>  extern bool qmp_dispatcher_co_shutdown;
>  extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
>  extern QemuMutex monitor_lock;
>  extern MonitorList mon_list;
>  
> -bool monitor_requires_iothread(const Monitor *mon);
>  int monitor_can_read(void *opaque);
>  void monitor_cancel_out_watch(Monitor *mon);
>  void monitor_list_append(Monitor *mon);
> diff --git a/monitor/monitor.c b/monitor/monitor.c
> index 2654b81890..a80d8dbf45 100644
> --- a/monitor/monitor.c
> +++ b/monitor/monitor.c
> @@ -55,7 +55,7 @@ typedef struct {
>  } MonitorQAPIEventConf;
>  
>  /* Shared monitor I/O thread */
> -IOThread *mon_iothread;
> +static IOThread *mon_iothread;
>  
>  /* Coroutine to dispatch the requests received from I/O thread */
>  Coroutine *qmp_dispatcher_co;
> @@ -93,6 +93,16 @@ static void monitor_finalize(Object *obj)
>      qemu_chr_fe_deinit(&mon->chr, false);
>      g_string_free(mon->outbuf, true);
>      qemu_mutex_destroy(&mon->mon_lock);
> +
> +    if (mon->iothread) {
> +        const IOThreadHolder io_holder = {
> +            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> +            .u.qom_object.qom_path = mon->iothread_qom_path,
> +        };
> +
> +        iothread_unref_and_put_aio_context(mon->iothread, &io_holder);
> +    }
> +    g_free(mon->iothread_qom_path);
>  }
>  
>  static char *monitor_get_chardev_id(Object *obj, Error **errp)
> @@ -164,7 +174,7 @@ Monitor *monitor_set_cur(Coroutine *co, Monitor *mon)
>      return old_monitor;
>  }
>  
> -bool monitor_requires_iothread(const Monitor *mon)
> +static bool monitor_requires_iothread(const Monitor *mon)
>  {
>      MonitorClass *cls = MONITOR_GET_CLASS(mon);
>      return cls->requires_iothread && cls->requires_iothread(mon);
> @@ -188,8 +198,8 @@ void monitor_cancel_out_watch(Monitor *mon)
>          GMainContext *ctx = NULL;
>          GSource *src;
>  
> -        if (monitor_requires_iothread(mon)) {
> -            ctx = iothread_get_g_main_context(mon_iothread);
> +        if (mon->iothread) {
> +            ctx = iothread_get_g_main_context(mon->iothread);
>          }
>          src = g_main_context_find_source_by_id(ctx, mon->out_watch);
>          if (!src && ctx) {
> @@ -516,12 +526,12 @@ void monitor_suspend(Monitor *mon)
>  {
>      qatomic_inc(&mon->suspend_cnt);
>  
> -    if (monitor_requires_iothread(mon)) {
> +    if (mon->iothread) {
>          /*
>           * 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);
> @@ -661,7 +671,6 @@ char *monitor_compat_id(void)
>  static void monitor_complete(UserCreatable *uc, Error **errp)
>  {
>      Monitor *mon = MONITOR(uc);
> -    AioContext *ctx;
>  
>      if (mon->chardev_id) {
>          Chardev *chr = qemu_chr_find(mon->chardev_id);
> @@ -680,11 +689,18 @@ static void monitor_complete(UserCreatable *uc, Error **errp)
>              mon_iothread = iothread_create("mon_iothread", &error_abort);
>          }
>  
> -        ctx = iothread_get_aio_context(mon_iothread);
> +        mon->iothread = mon_iothread;
> +        mon->iothread_qom_path = object_get_canonical_path(OBJECT(mon));

Hmm, this is the monitor's QOM path, not the I/O threads.  Let's rename
Monitor member iothread_qom_path to monitor_qom_path or just qom_path.

> +        const IOThreadHolder io_holder = {
> +            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> +            .u.qom_object.qom_path = mon->iothread_qom_path,
> +        };
> +
> +        mon->ctx = iothread_ref_and_get_aio_context(mon->iothread, &io_holder);
>      } else {
> -        ctx = qemu_get_aio_context();
> +        mon->ctx = qemu_get_aio_context();
>      }
> -    mon->accept_input_bh = aio_bh_new(ctx, monitor_accept_input, mon);
> +    mon->accept_input_bh = aio_bh_new(mon->ctx, monitor_accept_input, mon);
>  }
>  
>  int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
> diff --git a/monitor/qmp.c b/monitor/qmp.c
> index aec0315775..e235302fb3 100644
> --- a/monitor/qmp.c
> +++ b/monitor/qmp.c
> @@ -187,7 +187,7 @@ static void monitor_qmp_caps_reset(MonitorQMP *mon)
>      memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
>      memset(mon->capab, 0, sizeof(mon->capab));
>      mon->capab_offered[QMP_CAPABILITY_OOB] =
> -        monitor_requires_iothread(MONITOR(mon));
> +        MONITOR(mon)->iothread != NULL;
>  }
>  
>  static void qmp_request_free(QMPRequest *req)
> @@ -678,8 +678,8 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
>      MonitorQMP *mon = opaque;
>      GMainContext *context;
>  
> -    assert(monitor_requires_iothread(MONITOR(mon)));
> -    context = iothread_get_g_main_context(mon_iothread);
> +    assert(MONITOR(mon)->iothread);
> +    context = iothread_get_g_main_context(MONITOR(mon)->iothread);
>      assert(context);
>      qemu_chr_fe_set_handlers(&mon->parent_obj.chr, monitor_can_read,
>                               monitor_qmp_read, monitor_qmp_event,
> @@ -717,7 +717,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
>  
>      qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
>  
> -    if (monitor_requires_iothread(MONITOR(mon))) {
> +    if (MONITOR(mon)->iothread) {
>          /*
>           * Make sure the old iowatch is gone.  It's possible when
>           * e.g. the chardev is in client mode, with wait=on.
> @@ -734,7 +734,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
>           * thread.  Schedule a bottom half.
>           */
>          mon->setup_pending = true;
> -        aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
> +        aio_bh_schedule_oneshot(MONITOR(mon)->ctx,
>                                  monitor_qmp_setup_handlers_bh, mon);
>          /* The bottom half will add @mon to @mon_list */
>      } else {
> @@ -788,8 +788,8 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
>      }
>  
>      /* Synchronize with in-flight iothread callbacks. */
> -    if (monitor_requires_iothread(mon)) {
> -        aio_wait_bh_oneshot(iothread_get_aio_context(mon_iothread),
> +    if (mon->iothread) {
> +        aio_wait_bh_oneshot(mon->ctx,
>                              monitor_qmp_iothread_quiesce, NULL);
>      }

With the rename
Reviewed-by: Markus Armbruster <armbru@redhat.com>



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

* Re: [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe
  2026-09-15 10:16 ` [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe Zhang Chen
@ 2026-09-15 10:59   ` Markus Armbruster
  2026-09-16  2:24     ` Zhang Chen
  2026-09-16 20:09   ` Stefan Hajnoczi
  1 sibling, 1 reply; 30+ messages in thread
From: Markus Armbruster @ 2026-09-15 10:59 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, 'Michael S . Tsirkin',
	Stefan Hajnoczi, Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

Zhang Chen <zhangckid@gmail.com> writes:

> Now that holder-aware callers have been converted, rename
> iothread_get_aio_context() to iothread_unsafe_get_aio_context() and
> update qmp_x_blockdev_set_iothread().
>
> The block graph retains only the AioContext, while detachment happens
> outside qmp_x_blockdev_set_iothread(), so this call site cannot provide
> a matching holder lifecycle. The new name makes its existing lifetime
> assumption explicit.

Suggest something like

  The only remaining user is qmp_x_blockdev_set_iothread().  It cannot
  use iothread_ref_and_get_aio_context() as is, because it ties the
  AioContext to the block node, and iothread_unref_and_put_aio_context()
  would have to be called when that tie is broken.

> New code should use iothread_ref_and_get_aio_context().
>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>

Reviewed-by: Markus Armbruster <armbru@redhat.com>



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

* Re: [PATCH V17 04/13] monitor: track IOThread users with QOM paths
  2026-09-15 10:49   ` Markus Armbruster
@ 2026-09-16  2:13     ` Zhang Chen
  0 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-16  2:13 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Eric Blake, Michael S . Tsirkin, Stefan Hajnoczi,
	Paolo Bonzini, Daniel P . Berrangé, Jason Wang

On Tue, Sep 15, 2026 at 6:49 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Monitors that require an IOThread share the internal mon_iothread.
> > Account for each monitor explicitly in IOThread holder tracking, using
> > the monitor's canonical QOM path to distinguish holders.
> >
> > Acquire the shared monitor IOThread AioContext once and store the
> > IOThread and canonical QOM path so the holder can be released after the
> > monitor has been removed from the QOM tree. Use the stored IOThread for
> > per-monitor checks and accesses after initialization, and reuse the
> > stored context for later operations.
> >
> > Release the holder from monitor_finalize(), after resources associated
> > with the AioContext have been destroyed. This pairs the reference taken
> > in complete() with instance finalization and keeps the IOThread alive
> > throughout monitor teardown.
> >
> > The internal mon_iothread remains hidden from query-iothreads, as before.
> > Management applications therefore cannot use it to determine which
> > monitors are using the IOThread. Nevertheless, track the monitor
> > holders so that they follow the same explicit holder and
> > IOThread reference lifecycle as other AioContext users.
> >
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> > ---
> >  monitor/monitor-internal.h |  7 +++++--
> >  monitor/monitor.c          | 36 ++++++++++++++++++++++++++----------
> >  monitor/qmp.c              | 14 +++++++-------
> >  3 files changed, 38 insertions(+), 19 deletions(-)
> >
> > diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h
> > index 822a66d06d..020afdaebf 100644
> > --- a/monitor/monitor-internal.h
> > +++ b/monitor/monitor-internal.h
> > @@ -77,6 +77,11 @@ struct Monitor {
> >      GString *outbuf;
> >      guint out_watch;
> >      int mux_out;
> > +
> > +    /* iothread context and holder identity */
> > +    IOThread *iothread;
> > +    char *iothread_qom_path;
> > +    AioContext *ctx;
> >  };
> >
> >  struct MonitorQMPClass {
> > @@ -109,14 +114,12 @@ struct MonitorQMP {
> >  };
> >
> >  typedef QTAILQ_HEAD(MonitorList, Monitor) MonitorList;
> > -extern IOThread *mon_iothread;
> >  extern Coroutine *qmp_dispatcher_co;
> >  extern bool qmp_dispatcher_co_shutdown;
> >  extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
> >  extern QemuMutex monitor_lock;
> >  extern MonitorList mon_list;
> >
> > -bool monitor_requires_iothread(const Monitor *mon);
> >  int monitor_can_read(void *opaque);
> >  void monitor_cancel_out_watch(Monitor *mon);
> >  void monitor_list_append(Monitor *mon);
> > diff --git a/monitor/monitor.c b/monitor/monitor.c
> > index 2654b81890..a80d8dbf45 100644
> > --- a/monitor/monitor.c
> > +++ b/monitor/monitor.c
> > @@ -55,7 +55,7 @@ typedef struct {
> >  } MonitorQAPIEventConf;
> >
> >  /* Shared monitor I/O thread */
> > -IOThread *mon_iothread;
> > +static IOThread *mon_iothread;
> >
> >  /* Coroutine to dispatch the requests received from I/O thread */
> >  Coroutine *qmp_dispatcher_co;
> > @@ -93,6 +93,16 @@ static void monitor_finalize(Object *obj)
> >      qemu_chr_fe_deinit(&mon->chr, false);
> >      g_string_free(mon->outbuf, true);
> >      qemu_mutex_destroy(&mon->mon_lock);
> > +
> > +    if (mon->iothread) {
> > +        const IOThreadHolder io_holder = {
> > +            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> > +            .u.qom_object.qom_path = mon->iothread_qom_path,
> > +        };
> > +
> > +        iothread_unref_and_put_aio_context(mon->iothread, &io_holder);
> > +    }
> > +    g_free(mon->iothread_qom_path);
> >  }
> >
> >  static char *monitor_get_chardev_id(Object *obj, Error **errp)
> > @@ -164,7 +174,7 @@ Monitor *monitor_set_cur(Coroutine *co, Monitor *mon)
> >      return old_monitor;
> >  }
> >
> > -bool monitor_requires_iothread(const Monitor *mon)
> > +static bool monitor_requires_iothread(const Monitor *mon)
> >  {
> >      MonitorClass *cls = MONITOR_GET_CLASS(mon);
> >      return cls->requires_iothread && cls->requires_iothread(mon);
> > @@ -188,8 +198,8 @@ void monitor_cancel_out_watch(Monitor *mon)
> >          GMainContext *ctx = NULL;
> >          GSource *src;
> >
> > -        if (monitor_requires_iothread(mon)) {
> > -            ctx = iothread_get_g_main_context(mon_iothread);
> > +        if (mon->iothread) {
> > +            ctx = iothread_get_g_main_context(mon->iothread);
> >          }
> >          src = g_main_context_find_source_by_id(ctx, mon->out_watch);
> >          if (!src && ctx) {
> > @@ -516,12 +526,12 @@ void monitor_suspend(Monitor *mon)
> >  {
> >      qatomic_inc(&mon->suspend_cnt);
> >
> > -    if (monitor_requires_iothread(mon)) {
> > +    if (mon->iothread) {
> >          /*
> >           * 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);
> > @@ -661,7 +671,6 @@ char *monitor_compat_id(void)
> >  static void monitor_complete(UserCreatable *uc, Error **errp)
> >  {
> >      Monitor *mon = MONITOR(uc);
> > -    AioContext *ctx;
> >
> >      if (mon->chardev_id) {
> >          Chardev *chr = qemu_chr_find(mon->chardev_id);
> > @@ -680,11 +689,18 @@ static void monitor_complete(UserCreatable *uc, Error **errp)
> >              mon_iothread = iothread_create("mon_iothread", &error_abort);
> >          }
> >
> > -        ctx = iothread_get_aio_context(mon_iothread);
> > +        mon->iothread = mon_iothread;
> > +        mon->iothread_qom_path = object_get_canonical_path(OBJECT(mon));
>
> Hmm, this is the monitor's QOM path, not the I/O threads.  Let's rename
> Monitor member iothread_qom_path to monitor_qom_path or just qom_path.

OK.

>
> > +        const IOThreadHolder io_holder = {
> > +            .type = IO_THREAD_HOLDER_KIND_QOM_OBJECT,
> > +            .u.qom_object.qom_path = mon->iothread_qom_path,
> > +        };
> > +
> > +        mon->ctx = iothread_ref_and_get_aio_context(mon->iothread, &io_holder);
> >      } else {
> > -        ctx = qemu_get_aio_context();
> > +        mon->ctx = qemu_get_aio_context();
> >      }
> > -    mon->accept_input_bh = aio_bh_new(ctx, monitor_accept_input, mon);
> > +    mon->accept_input_bh = aio_bh_new(mon->ctx, monitor_accept_input, mon);
> >  }
> >
> >  int monitor_new(MonitorOptions *opts, bool allow_hmp, Error **errp)
> > diff --git a/monitor/qmp.c b/monitor/qmp.c
> > index aec0315775..e235302fb3 100644
> > --- a/monitor/qmp.c
> > +++ b/monitor/qmp.c
> > @@ -187,7 +187,7 @@ static void monitor_qmp_caps_reset(MonitorQMP *mon)
> >      memset(mon->capab_offered, 0, sizeof(mon->capab_offered));
> >      memset(mon->capab, 0, sizeof(mon->capab));
> >      mon->capab_offered[QMP_CAPABILITY_OOB] =
> > -        monitor_requires_iothread(MONITOR(mon));
> > +        MONITOR(mon)->iothread != NULL;
> >  }
> >
> >  static void qmp_request_free(QMPRequest *req)
> > @@ -678,8 +678,8 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
> >      MonitorQMP *mon = opaque;
> >      GMainContext *context;
> >
> > -    assert(monitor_requires_iothread(MONITOR(mon)));
> > -    context = iothread_get_g_main_context(mon_iothread);
> > +    assert(MONITOR(mon)->iothread);
> > +    context = iothread_get_g_main_context(MONITOR(mon)->iothread);
> >      assert(context);
> >      qemu_chr_fe_set_handlers(&mon->parent_obj.chr, monitor_can_read,
> >                               monitor_qmp_read, monitor_qmp_event,
> > @@ -717,7 +717,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
> >
> >      qemu_chr_fe_set_echo(&mon->parent_obj.chr, true);
> >
> > -    if (monitor_requires_iothread(MONITOR(mon))) {
> > +    if (MONITOR(mon)->iothread) {
> >          /*
> >           * Make sure the old iowatch is gone.  It's possible when
> >           * e.g. the chardev is in client mode, with wait=on.
> > @@ -734,7 +734,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
> >           * thread.  Schedule a bottom half.
> >           */
> >          mon->setup_pending = true;
> > -        aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
> > +        aio_bh_schedule_oneshot(MONITOR(mon)->ctx,
> >                                  monitor_qmp_setup_handlers_bh, mon);
> >          /* The bottom half will add @mon to @mon_list */
> >      } else {
> > @@ -788,8 +788,8 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
> >      }
> >
> >      /* Synchronize with in-flight iothread callbacks. */
> > -    if (monitor_requires_iothread(mon)) {
> > -        aio_wait_bh_oneshot(iothread_get_aio_context(mon_iothread),
> > +    if (mon->iothread) {
> > +        aio_wait_bh_oneshot(mon->ctx,
> >                              monitor_qmp_iothread_quiesce, NULL);
> >      }
>
> With the rename
> Reviewed-by: Markus Armbruster <armbru@redhat.com>

Thanks
Chen

>


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

* Re: [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe
  2026-09-15 10:59   ` Markus Armbruster
@ 2026-09-16  2:24     ` Zhang Chen
  2026-09-16  6:55       ` Markus Armbruster
  0 siblings, 1 reply; 30+ messages in thread
From: Zhang Chen @ 2026-09-16  2:24 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Eric Blake, Michael S . Tsirkin, Stefan Hajnoczi,
	Paolo Bonzini, Daniel P . Berrangé, Jason Wang

On Tue, Sep 15, 2026 at 6:59 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Zhang Chen <zhangckid@gmail.com> writes:
>
> > Now that holder-aware callers have been converted, rename
> > iothread_get_aio_context() to iothread_unsafe_get_aio_context() and
> > update qmp_x_blockdev_set_iothread().
> >
> > The block graph retains only the AioContext, while detachment happens
> > outside qmp_x_blockdev_set_iothread(), so this call site cannot provide
> > a matching holder lifecycle. The new name makes its existing lifetime
> > assumption explicit.
>
> Suggest something like
>
>   The only remaining user is qmp_x_blockdev_set_iothread().  It cannot
>   use iothread_ref_and_get_aio_context() as is, because it ties the
>   AioContext to the block node, and iothread_unref_and_put_aio_context()
>   would have to be called when that tie is broken.

Looks better~
I'll use your wording in the next revision.

>
> > New code should use iothread_ref_and_get_aio_context().
> >
> > Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> > Signed-off-by: Zhang Chen <zhangckid@gmail.com>
>
> Reviewed-by: Markus Armbruster <armbru@redhat.com>
>

If you have time, could you also take a look at the remaining patches
converting IOThread users to the holder-aware API? I'd appreciate your
feedback.

Thanks
Chen


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

* Re: [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe
  2026-09-16  2:24     ` Zhang Chen
@ 2026-09-16  6:55       ` Markus Armbruster
  0 siblings, 0 replies; 30+ messages in thread
From: Markus Armbruster @ 2026-09-16  6:55 UTC (permalink / raw)
  To: Zhang Chen
  Cc: Markus Armbruster, qemu-devel, Eric Blake, Michael S . Tsirkin,
	Stefan Hajnoczi, Paolo Bonzini, Daniel P . Berrangé,
	Jason Wang

Zhang Chen <zhangckid@gmail.com> writes:

[...]

> If you have time, could you also take a look at the remaining patches
> converting IOThread users to the holder-aware API? I'd appreciate your
> feedback.

Looking at my patch review queue...  seems unlikely at this time, sorry.
I hope the maintainers of the files touched can take care of it.



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

* Re: [PATCH V17 04/13] monitor: track IOThread users with QOM paths
  2026-09-15 10:16 ` [PATCH V17 04/13] monitor: track IOThread users with QOM paths Zhang Chen
  2026-09-15 10:49   ` Markus Armbruster
@ 2026-09-16 19:33   ` Stefan Hajnoczi
  1 sibling, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:33 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 1563 bytes --]

On Tue, Sep 15, 2026 at 06:16:46PM +0800, Zhang Chen wrote:
> Monitors that require an IOThread share the internal mon_iothread.
> Account for each monitor explicitly in IOThread holder tracking, using
> the monitor's canonical QOM path to distinguish holders.
> 
> Acquire the shared monitor IOThread AioContext once and store the
> IOThread and canonical QOM path so the holder can be released after the
> monitor has been removed from the QOM tree. Use the stored IOThread for
> per-monitor checks and accesses after initialization, and reuse the
> stored context for later operations.
> 
> Release the holder from monitor_finalize(), after resources associated
> with the AioContext have been destroyed. This pairs the reference taken
> in complete() with instance finalization and keeps the IOThread alive
> throughout monitor teardown.
> 
> The internal mon_iothread remains hidden from query-iothreads, as before.
> Management applications therefore cannot use it to determine which
> monitors are using the IOThread. Nevertheless, track the monitor
> holders so that they follow the same explicit holder and
> IOThread reference lifecycle as other AioContext users.
> 
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  monitor/monitor-internal.h |  7 +++++--
>  monitor/monitor.c          | 36 ++++++++++++++++++++++++++----------
>  monitor/qmp.c              | 14 +++++++-------
>  3 files changed, 38 insertions(+), 19 deletions(-)

Aside from Markus' comment:
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 05/13] virtio-vq-mapping: track iothread-vq-mapping references using device path
  2026-09-15 10:16 ` [PATCH V17 05/13] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
@ 2026-09-16 19:36   ` Stefan Hajnoczi
  0 siblings, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:36 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 747 bytes --]

On Tue, Sep 15, 2026 at 06:16:47PM +0800, Zhang Chen wrote:
> Replace raw object_ref/unref calls with iothread_ref_and_get/
> unref_and_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(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 06/13] virtio: track IOThread references for thread pinning
  2026-09-15 10:16 ` [PATCH V17 06/13] virtio: track IOThread references for thread pinning Zhang Chen
@ 2026-09-16 19:38   ` Stefan Hajnoczi
  0 siblings, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:38 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 637 bytes --]

On Tue, Sep 15, 2026 at 06:16:48PM +0800, Zhang Chen wrote:
> Use the virtio device canonical QOM path as the holder when virtio-blk
> and virtio-scsi acquire an IOThread AioContext.
> 
> Replace the raw object references with the holder-aware
> acquire/release helpers so the users are visible through IOThread
> introspection.
> 
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  hw/block/virtio-blk.c           | 23 +++++++++++++----------
>  hw/scsi/virtio-scsi-dataplane.c | 23 +++++++++++++----------
>  2 files changed, 26 insertions(+), 20 deletions(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 07/13] net/colo: track IOThread references using path-based holder
  2026-09-15 10:16 ` [PATCH V17 07/13] net/colo: track IOThread references using path-based holder Zhang Chen
@ 2026-09-16 19:47   ` Stefan Hajnoczi
  2026-09-22  3:16     ` Zhang Chen
  0 siblings, 1 reply; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:47 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 808 bytes --]

On Tue, Sep 15, 2026 at 06:16:49PM +0800, Zhang Chen wrote:
> @@ -1404,6 +1410,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));

Did you test this? I think object_get_canonical_path() doesn't work here
because the object's ->parent link is already NULL:

  static void object_finalize_child_property(Object *obj, const char *name,
                                             void *opaque)
  {
      Object *child = opaque;
  
      if (child->class->unparent) {
          (child->class->unparent)(child);
      }
      child->parent = NULL;
      ^^^^^^^^^^^^^^^^^^^^^
      object_unref(child);
      ^^   we are here  ^^
  }

Stefan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 08/13] virtio-balloon: Update tracking iothread users with holder
  2026-09-15 10:16 ` [PATCH V17 08/13] virtio-balloon: Update tracking iothread users with holder Zhang Chen
@ 2026-09-16 19:48   ` Stefan Hajnoczi
  0 siblings, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:48 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 437 bytes --]

On Tue, Sep 15, 2026 at 06:16:50PM +0800, Zhang Chen wrote:
> Replace raw object_ref/unref calls with
> iothread_ref_and_get/unref_and_put_aio_context.
> 
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  hw/virtio/virtio-balloon.c         | 21 ++++++++++++++++-----
>  include/hw/virtio/virtio-balloon.h |  1 +
>  2 files changed, 17 insertions(+), 5 deletions(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name
  2026-09-15 10:16 ` [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
@ 2026-09-16 19:54   ` Stefan Hajnoczi
  2026-09-22  6:15     ` Zhang Chen
  0 siblings, 1 reply; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:54 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 503 bytes --]

On Tue, Sep 15, 2026 at 06:16:51PM +0800, Zhang Chen wrote:
> @@ -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));

What does this path look like when you test this? I took a quick look at
the code and it looks like this object has no parent, so path will be
NULL?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 10/13] xen-block: Update tracking iothread users with holder name
  2026-09-15 10:16 ` [PATCH V17 10/13] xen-block: " Zhang Chen
@ 2026-09-16 19:56   ` Stefan Hajnoczi
  0 siblings, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 19:56 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 384 bytes --]

On Tue, Sep 15, 2026 at 06:16:52PM +0800, Zhang Chen wrote:
> Replace raw object_ref/unref calls with
> iothread_ref_and_get/unref_and_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(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 11/13] monitor/hmp: display IOThread holders
  2026-09-15 10:16 ` [PATCH V17 11/13] monitor/hmp: display IOThread holders Zhang Chen
@ 2026-09-16 20:01   ` Stefan Hajnoczi
  0 siblings, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 20:01 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang, Zhang Chen

[-- Attachment #1: Type: text/plain, Size: 1668 bytes --]

On Tue, Sep 15, 2026 at 06:16:53PM +0800, Zhang Chen wrote:
> diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
> index 91701ddf33..d7dd24d951 100644
> --- a/monitor/hmp-cmds.c
> +++ b/monitor/hmp-cmds.c
> @@ -246,11 +246,31 @@ void hmp_info_iothreads(MonitorHMP *hmp, 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_hmp_printf(hmp, "%s:\n", value->id);
>          monitor_hmp_printf(hmp, "  thread_id=%" PRId64 "\n", value->thread_id);
> +        monitor_hmp_printf(hmp, "  holders=");
> +        for (h = value->holders; h; h = h->next) {
> +            IOThreadHolder *holder = h->value;
> +
> +            switch (holder->type) {
> +            case IO_THREAD_HOLDER_KIND_BLOCK_EXPORT:
> +                monitor_hmp_printf(hmp, "[block-export: %s]",
> +                                   holder->u.block_export.export_id);
> +                break;

Kevin: Special-casing block exports is unfortunate. Other IOThread users
tend to be full-blown Objects. Do you think BlockExport should become an
Object with a path (e.g. /block-exports/ similar to how /audiodevs and
other object types are in the QOM tree)?

What I'm thinking is that block-export-add/block-export-del remain the
commands for managing the lifetime of exports - we don't use --object
syntax. But simply making BlockExport an Object would make this and
maybe other future changes cleaner (no special cases for non-Objects).

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe
  2026-09-15 10:16 ` [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe Zhang Chen
  2026-09-15 10:59   ` Markus Armbruster
@ 2026-09-16 20:09   ` Stefan Hajnoczi
  1 sibling, 0 replies; 30+ messages in thread
From: Stefan Hajnoczi @ 2026-09-16 20:09 UTC (permalink / raw)
  To: Zhang Chen
  Cc: qemu-devel, Eric Blake, Markus Armbruster,
	'Michael S . Tsirkin', Paolo Bonzini,
	'Daniel P . Berrangé', Jason Wang

[-- Attachment #1: Type: text/plain, Size: 934 bytes --]

On Tue, Sep 15, 2026 at 06:16:54PM +0800, Zhang Chen wrote:
> Now that holder-aware callers have been converted, rename
> iothread_get_aio_context() to iothread_unsafe_get_aio_context() and
> update qmp_x_blockdev_set_iothread().
> 
> The block graph retains only the AioContext, while detachment happens
> outside qmp_x_blockdev_set_iothread(), so this call site cannot provide
> a matching holder lifecycle. The new name makes its existing lifetime
> assumption explicit.
> 
> New code should use iothread_ref_and_get_aio_context().
> 
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Signed-off-by: Zhang Chen <zhangckid@gmail.com>
> ---
>  blockdev.c                |  9 ++++++++-
>  include/system/iothread.h | 11 ++++++++++-
>  iothread.c                |  2 +-
>  3 files changed, 19 insertions(+), 3 deletions(-)

With Markus' feedback addressed:
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH V17 07/13] net/colo: track IOThread references using path-based holder
  2026-09-16 19:47   ` Stefan Hajnoczi
@ 2026-09-22  3:16     ` Zhang Chen
  0 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-22  3:16 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: qemu-devel, Eric Blake, Markus Armbruster, Michael S . Tsirkin,
	Paolo Bonzini, Daniel P . Berrangé, Jason Wang

On Mon, Sep 21, 2026 at 9:37 PM Stefan Hajnoczi <stefanha@redhat.com> wrote:
>
> On Tue, Sep 15, 2026 at 06:16:49PM +0800, Zhang Chen wrote:
> > @@ -1404,6 +1410,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));
>
> Did you test this? I think object_get_canonical_path() doesn't work here
> because the object's ->parent link is already NULL:
>
>   static void object_finalize_child_property(Object *obj, const char *name,
>                                              void *opaque)
>   {
>       Object *child = opaque;
>
>       if (child->class->unparent) {
>           (child->class->unparent)(child);
>       }
>       child->parent = NULL;
>       ^^^^^^^^^^^^^^^^^^^^^
>       object_unref(child);
>       ^^   we are here  ^^
>   }
>

Thanks for catching this. You're right — the canonical path is no longer
available in finalize().

The local fix saves the path(like the monitor parts we discussed) when
registering
the holder and reuses it in finalize(). I've verified object-del, quit, and
initialization-failure cleanup with the fix. Will update it in the next version.

Thanks
Chen

> Stefan


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

* Re: [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name
  2026-09-16 19:54   ` Stefan Hajnoczi
@ 2026-09-22  6:15     ` Zhang Chen
  0 siblings, 0 replies; 30+ messages in thread
From: Zhang Chen @ 2026-09-22  6:15 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: qemu-devel, Eric Blake, Markus Armbruster, Michael S . Tsirkin,
	Paolo Bonzini, Daniel P . Berrangé, Jason Wang

On Mon, Sep 21, 2026 at 9:37 PM Stefan Hajnoczi <stefanha@redhat.com> wrote:
>
> On Tue, Sep 15, 2026 at 06:16:51PM +0800, Zhang Chen wrote:
> > @@ -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));
>
> What does this path look like when you test this? I took a quick look at
> the code and it looks like this object has no parent, so path will be
> NULL?


Thanks for catching this. I incorrectly assumed that being a QOM object
guaranteed a canonical path. This IOChannel has no QOM parent, so the
path is actually NULL.

The local fix uses the owning PCI device's path and saves it for
disconnect(), since the device may already be unparented by then.
Initialization-failure cleanup and repeated device creation tests pass
with this change.

Thanks
Chen


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

end of thread, other threads:[~2026-09-22  6:16 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-15 10:16 [PATCH V17 00/13] iothread: Support tracking and querying IOThread holder Zhang Chen
2026-09-15 10:16 ` [PATCH V17 01/13] iothread: introduce holder tracking Zhang Chen
2026-09-15 10:16 ` [PATCH V17 02/13] iothread: add holder-aware AioContext accessors Zhang Chen
2026-09-15 10:16 ` [PATCH V17 03/13] block/export: track IOThread references Zhang Chen
2026-09-15 10:16 ` [PATCH V17 04/13] monitor: track IOThread users with QOM paths Zhang Chen
2026-09-15 10:49   ` Markus Armbruster
2026-09-16  2:13     ` Zhang Chen
2026-09-16 19:33   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 05/13] virtio-vq-mapping: track iothread-vq-mapping references using device path Zhang Chen
2026-09-16 19:36   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 06/13] virtio: track IOThread references for thread pinning Zhang Chen
2026-09-16 19:38   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 07/13] net/colo: track IOThread references using path-based holder Zhang Chen
2026-09-16 19:47   ` Stefan Hajnoczi
2026-09-22  3:16     ` Zhang Chen
2026-09-15 10:16 ` [PATCH V17 08/13] virtio-balloon: Update tracking iothread users with holder Zhang Chen
2026-09-16 19:48   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 09/13] vfio-user/proxy: Update tracking iothread users with holder name Zhang Chen
2026-09-16 19:54   ` Stefan Hajnoczi
2026-09-22  6:15     ` Zhang Chen
2026-09-15 10:16 ` [PATCH V17 10/13] xen-block: " Zhang Chen
2026-09-16 19:56   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 11/13] monitor/hmp: display IOThread holders Zhang Chen
2026-09-16 20:01   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 12/13] iothread: make legacy AioContext getter explicitly unsafe Zhang Chen
2026-09-15 10:59   ` Markus Armbruster
2026-09-16  2:24     ` Zhang Chen
2026-09-16  6:55       ` Markus Armbruster
2026-09-16 20:09   ` Stefan Hajnoczi
2026-09-15 10:16 ` [PATCH V17 13/13] tests/unit/iothread: update AioContext ref/put helpers 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.