On Mon, Aug 24, 2026 at 3:24 PM Markus Armbruster <[email protected]> wrote:
>
> Zhang Chen <[email protected]> writes:
>
> > On Thu, Aug 20, 2026 at 4:17 PM Markus Armbruster <[email protected]> wrote:
> >>
> >> Zhang Chen <[email protected]> writes:
> >>
> >> > Display holder information in info iothreads to aid manual debugging.
> >> > Omit the holders line when an IOThread has no holders.
> >> >
> >> > Signed-off-by: Zhang Chen <[email protected]>
> >> > ---
> >> > monitor/hmp-cmds.c | 21 +++++++++++++++++++++
> >> > 1 file changed, 21 insertions(+)
> >> >
> >> > diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
> >> > index e9fb8d827a..41a813e79f 100644
> >> > --- a/monitor/hmp-cmds.c
> >> > +++ b/monitor/hmp-cmds.c
> >> > @@ -240,11 +240,32 @@ void hmp_info_iothreads(Monitor *mon, const QDict
> >> > *qdict)
> >> > IOThreadInfoList *info_list = qmp_query_iothreads(NULL);
> >> > IOThreadInfoList *info;
> >> > IOThreadInfo *value;
> >> > + IOThreadHolderList *h;
> >> >
> >> > for (info = info_list; info; info = info->next) {
> >> > value = info->value;
> >> > monitor_printf(mon, "%s:\n", value->id);
> >> > monitor_printf(mon, " thread_id=%" PRId64 "\n",
> >> > value->thread_id);
> >> > + monitor_printf(mon, " holders=");
> >> > + for (h = value->holders; h; h = h->next) {
> >> > + IOThreadHolder *holder = h->value;
> >> > +
> >> > + switch (holder->type) {
> >> > + case IO_THREAD_HOLDER_KIND_BLOCK_EXPORT:
> >> > + monitor_printf(mon, "[block-export: %s]",
> >> > + holder->u.block_export.export_id);
> >> > + break;
> >> > + case IO_THREAD_HOLDER_KIND_QOM_OBJECT:
> >> > + monitor_printf(mon, "[qom-path: %s]",
> >> > + holder->u.qom_object.qom_path);
> >> > + break;
> >> > + default:
> >> > + monitor_printf(mon, "[unknown]");
> >> > + break;
> >>
> >> This is unreachable. Please use
> >>
> >> default:
> >> g_assert_not_reached();
> >>
> >> to make it obvious.
> >>
> >> > + }
> >> > + }
> >> > + monitor_printf(mon, "\n");
> >> > +
> >> > monitor_printf(mon, " poll-max-ns=%" PRId64 "\n",
> >> > value->poll_max_ns);
> >> > monitor_printf(mon, " poll-grow=%" PRId64 "\n",
> >> > value->poll_grow);
> >> > monitor_printf(mon, " poll-shrink=%" PRId64 "\n",
> >> > value->poll_shrink);
> >>
> >> Quick test:
> >>
> >> $ qemu-system-x86_64 -S -nodefaults -display none -chardev
> >> socket,id=chr-qmp,path=test-qmp,server=on,wait=off -object
> >> monitor-qmp,id=qmp,chardev=chr-qmp -monitor stdio
> >> QEMU 11.1.50 monitor - type 'help' for more information
> >> (qemu) info iothreads
> >> (qemu) q
> >>
> >> Hmm, no output? The QMP monitor should be holding the monitor I/O
> >> thread. To confirm that, I made monitor_complete() print things (patch
> >> appended), and got
> >>
> >> ### monitor qmp holding iothread mon_iothread
> >> ### monitor compat_monitor0 without iothread
> >>
> >> What's going on here?
> >>
> >
> > This is expected with the existing query-iothreads behavior.
> > mon_iothread is created under the internal object root, while
> > qmp_query_iothreads() only walks /objects, so internal IOThreads are
> > intentionally not exposed through QMP/HMP.
>
> TIL about the internal object root. It's the root of a *second* QOM
> composition tree. I have been operating under the assumnption there is
> just one.
>
> Your series adds holder tracking for any I/O thread, whether it's in the
> externally visible QOM composition tree (the one returned by
> object_get_root()) or the internal one (the one returned by
> object_get_internal_root()). Makes sense, because at the place where we
> track we don't want to know / make assumptions about which tree it is.
>
> query-iothreads shows only the I/O threads in the externally visible QOM
> composition tree. Makes sense.
>
> Fortunately, you were able to eliminate the special case for monitors.
> If it was still there, the query-iothreads code to show it would be
> dead.
>
> Back to what got us there. My attempt to test query-iothreads showing
> holders failed, because monitors are not usable for this test. Please
> show me how to test this. Ideally, a simple command line and a few
> simple monitor commands ending with a query-iothreads that shows
> holders.
>
Thanks. Here is a reproducer that also exercises holder removal and
registration by another device type:
$ qemu-system-x86_64 \
-S -nodefaults -display none \
-object iothread,id=iothread0 \
-blockdev driver=null-co,node-name=null0 \
-qmp stdio
First, hotplug a virtio-scsi device and query the IOThread:
{"execute":"qmp_capabilities"}
{"execute":"query-iothreads"}
{"return": [{"poll-shrink": 2, "thread-id": 4002255, "poll-weight": 3,
"aio-max-batch": 0, "poll-grow": 2, "holders": [], "poll-max-ns":
32768, "id": "iothread0"}]}
{"execute":"device_add","arguments":{"driver":"virtio-scsi-pci","id":"scsi0","iothread":"iothread0"}}
{"execute":"query-iothreads"}
{"return": [{"poll-shrink": 2, "thread-id": 4002255, "poll-weight": 3,
"aio-max-batch": 0, "poll-grow": 2, "holders": [{"qom-path":
"/machine/peripheral/scsi0/virtio-backend", "type": "qom-object"}],
"poll-max-ns": 32768, "id": "iothread0"}]}
The result contains:
> "holders": [
Remove the device:
{"execute":"device_del","arguments":{"id":"scsi0"}}
{"return": {}}
{"execute":"query-iothreads"}
{"return": [{"poll-shrink": 2, "thread-id": 4002255, "poll-weight": 3,
"aio-max-batch": 0, "poll-grow": 2, "holders": [], "poll-max-ns":
32768, "id": "iothread0"}]}
And use the HMP is the same logic, just need change the "-qmp" to "-monitor":
$ qemu-system-x86_64 \
-S -nodefaults -display none \
-object iothread,id=iothread0 \
-blockdev driver=null-co,node-name=null0 \
-monitor stdio
(qemu) info iothreads
iothread0:
thread_id=4005900
holders=
poll-max-ns=32768
poll-grow=2
poll-shrink=2
poll-weight=3
aio-max-batch=0
.......
Thanks
Chen
> > I prefer to preserve that behavior in this series, since exposing internal
> > IOThreads would require additional API decisions, including handling ID
> > collisions with user-created IOThreads.
>
> Okay!
>
> > I’ll also change the unreachable default case to
> > g_assert_not_reached().
> >
> > Thanks
> > Chen
>
> [...]
>