@samitouri / QOSamiQemu / commits / a5df506e12

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>

Christian Brauner committed Jul 6, 2026 at 14:58 UTC a5df506e1293e6901a3ff38927a4d562fa042806
3 files changed +76 -2
monitor/monitor-internal.h
+2
@@ -178,6 +178,7 @@ struct MonitorQMP {
178 Monitor parent_obj;
179 JSONMessageParser parser;
180 bool pretty;
181 + bool setup_pending; /* iothread BH has not yet set up chardev handlers */
182 /*
183 * When a client connects, we're in capabilities negotiation mode.
184 * @commands is &qmp_cap_negotiation_commands then. When command
@@ -206,6 +207,7 @@ extern MonitorList mon_list;
207
208 bool monitor_requires_iothread(const Monitor *mon);
209 int monitor_can_read(void *opaque);
210 +void monitor_cancel_out_watch(Monitor *mon);
211 void monitor_list_append(Monitor *mon);
212 void monitor_fdsets_cleanup(void);
213 int monitor_set_cpu(Monitor *mon, int cpu_index);
monitor/monitor.c
+22
@@ -179,6 +179,28 @@ static gboolean monitor_unblocked(void *do_not_use, GIOCondition cond,
179 return G_SOURCE_REMOVE;
180 }
181
182 +/* Cancel a pending out_watch GSource. Caller must hold mon_lock. */
183 +void monitor_cancel_out_watch(Monitor *mon)
184 +{
185 + if (mon->out_watch) {
186 + GMainContext *ctx = NULL;
187 + GSource *src;
188 +
189 + if (monitor_requires_iothread(mon)) {
190 + ctx = iothread_get_g_main_context(mon_iothread);
191 + }
192 + src = g_main_context_find_source_by_id(ctx, mon->out_watch);
193 + if (!src && ctx) {
194 + /* Handler disconnect may have reset gcontext to NULL. */
195 + src = g_main_context_find_source_by_id(NULL, mon->out_watch);
196 + }
197 + if (src) {
198 + g_source_destroy(src);
199 + }
200 + mon->out_watch = 0;
201 + }
202 +}
203 +
204 /* Caller must hold mon->mon_lock */
205 void monitor_flush_locked(Monitor *mon)
206 {
monitor/qmp.c
+52 -2
@@ -184,6 +184,12 @@ static void monitor_qmp_cleanup_req_queue_locked(MonitorQMP *mon)
184 }
185 }
186
187 +static void monitor_qmp_drain_queue(MonitorQMP *mon)
188 +{
189 + QEMU_LOCK_GUARD(&mon->qmp_queue_lock);
190 + monitor_qmp_cleanup_req_queue_locked(mon);
191 +}
192 +
193 static void monitor_qmp_cleanup_queue_and_resume(MonitorQMP *mon)
194 {
195 QEMU_LOCK_GUARD(&mon->qmp_queue_lock);
@@ -597,6 +603,7 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
603 monitor_qmp_read, monitor_qmp_event,
604 NULL, &mon->parent_obj, context, true);
605 monitor_list_append(&mon->parent_obj);
606 + qatomic_set(&mon->setup_pending, false);
607 }
608
609 void monitor_new_qmp(const char *id, const char *chardev_id,
@@ -644,6 +651,7 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
651 * since chardev might be running in the monitor I/O
652 * thread. Schedule a bottom half.
653 */
654 + mon->setup_pending = true;
655 aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
656 monitor_qmp_setup_handlers_bh, mon);
657 /* The bottom half will add @mon to @mon_list */
@@ -655,8 +663,14 @@ static void monitor_qmp_complete(UserCreatable *uc, Error **errp)
663 }
664 }
665
666 +static void monitor_qmp_iothread_quiesce(void *opaque)
667 +{
668 + /* No-op: synchronization point only */
669 +}
670 +
671 static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
672 {
673 + Monitor *mon = MONITOR(uc);
674 MonitorQMP *qmp = MONITOR_QMP(uc);
675
676 if (monitor_qmp_dispatcher_is_servicing(qmp)) {
@@ -664,8 +678,44 @@ static bool monitor_qmp_prepare_delete(UserCreatable *uc, Error **errp)
678 return false;
679 }
680
667 - error_setg(errp, "Deleting QMP monitors is not supported");
668 - return false;
681 + if (qatomic_read(&qmp->setup_pending)) {
682 + error_setg(errp, "monitor is still initializing");
683 + return false;
684 + }
685 +
686 + /* Remove from mon_list before chardev disconnect. */
687 + WITH_QEMU_LOCK_GUARD(&monitor_lock) {
688 + QTAILQ_REMOVE(&mon_list, mon, entry);
689 + }
690 +
691 + /* Cancel out_watch while gcontext still points to the right ctx. */
692 + WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
693 + monitor_cancel_out_watch(mon);
694 + }
695 +
696 + qemu_chr_fe_set_handlers(&mon->chr, NULL, NULL, NULL, NULL,
697 + NULL, NULL, true);
698 +
699 + /* Drain requests from any in-flight monitor_qmp_read(). */
700 + monitor_qmp_drain_queue(qmp);
701 +
702 + WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
703 + /* Disable flushes before cancel -- gcontext is already wrong. */
704 + qemu_chr_fe_set_open(&mon->chr, false);
705 + monitor_cancel_out_watch(mon);
706 + }
707 +
708 + /* Synchronize with in-flight iothread callbacks. */
709 + if (monitor_requires_iothread(mon)) {
710 + aio_wait_bh_oneshot(iothread_get_aio_context(mon_iothread),
711 + monitor_qmp_iothread_quiesce, NULL);
712 + }
713 +
714 + /* Catch requests from a racing monitor_qmp_read(). */
715 + monitor_qmp_drain_queue(qmp);
716 + monitor_fdsets_cleanup();
717 +
718 + return true;
719 }
720
721 static void monitor_qmp_accept_input(Monitor *mon)