monitor: implement support for deleting QMP objects

The removal sequence is:

 1. Remove from mon_list under monitor_lock.  This must happen
    before disconnecting chardev handlers to prevent event
    broadcast from calling monitor_flush_locked() after the
    gcontext reset, which would create an out_watch on the wrong
    GMainContext (see monitor_cancel_out_watch()).
 2. Cancel any pending out_watch while gcontext still points to the
    correct context.
 3. Disconnect chardev handlers, passing context=NULL and close
    the connection.
 4. Drain pending requests from any in-flight monitor_qmp_read().
 5. Destroy the monitor object

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[DB: extracted from a larger commit and refactored to apply
     to the new monitor class structure. Remove 'self delete'
     feature which requires complex special-case code]
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Message-ID: <20260706135824.2623960-28-berrange@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
Christian Brauner
2026-07-06 14:58:15 +01:00
committed by Markus Armbruster
parent a0248548ca
commit a5df506e12
3 changed files with 76 additions and 2 deletions

View File

@@ -178,6 +178,7 @@ struct MonitorQMP {
Monitor parent_obj; Monitor parent_obj;
JSONMessageParser parser; JSONMessageParser parser;
bool pretty; bool pretty;
bool setup_pending; /* iothread BH has not yet set up chardev handlers */
/* /*
* When a client connects, we're in capabilities negotiation mode. * When a client connects, we're in capabilities negotiation mode.
* @commands is &qmp_cap_negotiation_commands then. When command * @commands is &qmp_cap_negotiation_commands then. When command
@@ -206,6 +207,7 @@ extern MonitorList mon_list;
bool monitor_requires_iothread(const Monitor *mon); bool monitor_requires_iothread(const Monitor *mon);
int monitor_can_read(void *opaque); int monitor_can_read(void *opaque);
void monitor_cancel_out_watch(Monitor *mon);
void monitor_list_append(Monitor *mon); void monitor_list_append(Monitor *mon);
void monitor_fdsets_cleanup(void); void monitor_fdsets_cleanup(void);
int monitor_set_cpu(Monitor *mon, int cpu_index); int monitor_set_cpu(Monitor *mon, int cpu_index);

View File

@@ -179,6 +179,28 @@ static gboolean monitor_unblocked(void *do_not_use, GIOCondition cond,
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }
/* Cancel a pending out_watch GSource. Caller must hold mon_lock. */
void monitor_cancel_out_watch(Monitor *mon)
{
if (mon->out_watch) {
GMainContext *ctx = NULL;
GSource *src;
if (monitor_requires_iothread(mon)) {
ctx = iothread_get_g_main_context(mon_iothread);
}
src = g_main_context_find_source_by_id(ctx, mon->out_watch);
if (!src && ctx) {
/* Handler disconnect may have reset gcontext to NULL. */
src = g_main_context_find_source_by_id(NULL, mon->out_watch);
}
if (src) {
g_source_destroy(src);
}
mon->out_watch = 0;
}
}
/* Caller must hold mon->mon_lock */ /* Caller must hold mon->mon_lock */
void monitor_flush_locked(Monitor *mon) void monitor_flush_locked(Monitor *mon)
{ {

View File

@@ -184,6 +184,12 @@ static void monitor_qmp_cleanup_req_queue_locked(MonitorQMP *mon)
} }
} }
static void monitor_qmp_drain_queue(MonitorQMP *mon)
{
QEMU_LOCK_GUARD(&mon->qmp_queue_lock);
monitor_qmp_cleanup_req_queue_locked(mon);
}
static void monitor_qmp_cleanup_queue_and_resume(MonitorQMP *mon) static void monitor_qmp_cleanup_queue_and_resume(MonitorQMP *mon)
{ {
QEMU_LOCK_GUARD(&mon->qmp_queue_lock); QEMU_LOCK_GUARD(&mon->qmp_queue_lock);
@@ -597,6 +603,7 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
monitor_qmp_read, monitor_qmp_event, monitor_qmp_read, monitor_qmp_event,
NULL, &mon->parent_obj, context, true); NULL, &mon->parent_obj, context, true);
monitor_list_append(&mon->parent_obj); monitor_list_append(&mon->parent_obj);
qatomic_set(&mon->setup_pending, false);
} }
void monitor_new_qmp(const char *id, const char *chardev_id, void monitor_new_qmp(const char *id, const char *chardev_id,
@@ -644,6 +651,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
* since chardev might be running in the monitor I/O * since chardev might be running in the monitor I/O
* thread. Schedule a bottom half. * thread. Schedule a bottom half.
*/ */
mon->setup_pending = true;
aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread), aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
monitor_qmp_setup_handlers_bh, mon); monitor_qmp_setup_handlers_bh, mon);
/* The bottom half will add @mon to @mon_list */ /* The bottom half will add @mon to @mon_list */
@@ -655,8 +663,14 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
} }
} }
static void monitor_qmp_iothread_quiesce(void *opaque)
{
/* No-op: synchronization point only */
}
static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp) static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
{ {
Monitor *mon = MONITOR(uc);
MonitorQMP *qmp = MONITOR_QMP(uc); MonitorQMP *qmp = MONITOR_QMP(uc);
if (monitor_qmp_dispatcher_is_servicing(qmp)) { if (monitor_qmp_dispatcher_is_servicing(qmp)) {
@@ -664,8 +678,44 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
return false; return false;
} }
error_setg(errp, "Deleting QMP monitors is not supported"); if (qatomic_read(&qmp->setup_pending)) {
return false; error_setg(errp, "monitor is still initializing");
return false;
}
/* Remove from mon_list before chardev disconnect. */
WITH_QEMU_LOCK_GUARD(&monitor_lock) {
QTAILQ_REMOVE(&mon_list, mon, entry);
}
/* Cancel out_watch while gcontext still points to the right ctx. */
WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
monitor_cancel_out_watch(mon);
}
qemu_chr_fe_set_handlers(&mon->chr, NULL, NULL, NULL, NULL,
NULL, NULL, true);
/* Drain requests from any in-flight monitor_qmp_read(). */
monitor_qmp_drain_queue(qmp);
WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
/* Disable flushes before cancel -- gcontext is already wrong. */
qemu_chr_fe_set_open(&mon->chr, false);
monitor_cancel_out_watch(mon);
}
/* Synchronize with in-flight iothread callbacks. */
if (monitor_requires_iothread(mon)) {
aio_wait_bh_oneshot(iothread_get_aio_context(mon_iothread),
monitor_qmp_iothread_quiesce, NULL);
}
/* Catch requests from a racing monitor_qmp_read(). */
monitor_qmp_drain_queue(qmp);
monitor_fdsets_cleanup();
return true;
} }
static void monitor_qmp_accept_input(Monitor *mon) static void monitor_qmp_accept_input(Monitor *mon)