@samitouri / QOSamiQemu / commits / 73ae0be3f1

ui/spice: add cleanup on shutdown

SPICE resources were never freed on shutdown. Add per-subsystem cleanup (display, input, core) and call it from qemu_cleanup(). Move spice-module.c into libui so the qemu_spice ops table links with the rest of the UI code. Add an LSan suppression for a known spice-server leak. Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-ID: <20260623-b4-ui-v4-14-4656aec3398d@redhat.com>

Marc-André Lureau committed Jun 23, 2026 at 11:44 UTC 73ae0be3f14b1df2ffea26387586e727e6d4434c
9 files changed +129 -22
include/ui/qemu-spice-module.h
+1
@@ -26,6 +26,7 @@ typedef struct SpiceInfo SpiceInfo;
26
27 struct QemuSpiceOps {
28 void (*init)(void);
29 + void (*cleanup)(void);
30 void (*display_init)(void);
31 int (*migrate_info)(const char *h, int p, int t, const char *s);
32 int (*set_passwd)(const char *passwd,
include/ui/qemu-spice.h
+2
@@ -27,7 +27,9 @@
27 #include "qemu/config-file.h"
28
29 void qemu_spice_input_init(void);
30 +void qemu_spice_input_cleanup(void);
31 void qemu_spice_display_init(void);
32 +void qemu_spice_display_cleanup(void);
33 void qemu_spice_display_init_done(void);
34 bool qemu_spice_have_display_interface(QemuConsole *con);
35 int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con);
scripts/lsan_suppressions.txt
+5
@@ -20,3 +20,8 @@ leak:libfontconfig.so
20 # https://github.com/GNOME/glib/blob/main/tools/glib.supp
21 # This avoids false positive leak reports for the qga-ssh-test.
22 leak:g_set_user_dirs
23 +
24 +# spice_server_add_interface allocates internal channel data that
25 +# spice_server_destroy does not free
26 +# https://gitlab.freedesktop.org/spice/spice/-/merge_requests/246
27 +leak:spice_server_add_interface
system/runstate.c
+4
@@ -62,6 +62,7 @@
62 #include "system/system.h"
63 #include "system/tpm.h"
64 #include "ui/console.h"
65 +#include "ui/qemu-spice-module.h"
66
67 #include "trace.h"
68
@@ -1048,6 +1049,9 @@ void qemu_cleanup(int status)
1049 user_creatable_cleanup();
1050 #ifdef CONFIG_VNC
1051 vnc_cleanup();
1052 +#endif
1053 +#ifdef CONFIG_SPICE
1054 + qemu_spice.cleanup();
1055 #endif
1056 /* TODO: unref root container, check all devices are ok */
1057 }
ui/meson.build
+2 -2
@@ -42,13 +42,14 @@ libui_sources = files(
42 'kbd-state.c',
43 'keymaps.c',
44 'qemu-pixman.c',
45 + 'spice-module.c',
46 'vgafont.c',
47 )
48 if pixman.found()
49 libui_sources += files('cp437.c', 'vt100.c')
50 endif
51 libui = static_library('qemuui', libui_sources + genh,
51 - dependencies: [pixman],
52 + dependencies: [pixman, spice_headers],
53 build_by_default: false)
54 ui = declare_dependency(objects: libui.extract_all_objects(recursive: false), dependencies: [pixman])
55 system_ss.add(png)
@@ -64,7 +65,6 @@ system_ss.add(when: pixman, if_true: files('console-vc.c'), if_false: files('con
65 if dbus_display
66 system_ss.add(files('dbus-module.c'))
67 endif
67 -system_ss.add([spice_headers, files('spice-module.c')])
68 system_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
69
70 if host_os == 'linux'
ui/spice-core.c
+23 -2
@@ -651,12 +651,15 @@ static void vm_change_state_handler(void *opaque, bool running,
651 }
652 }
653
654 +static VMChangeStateEntry *vm_change_entry;
655 +
656 void qemu_spice_display_init_done(void)
657 {
658 if (runstate_is_running()) {
659 qemu_spice_display_start();
660 }
659 - qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
661 + vm_change_entry =
662 + qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
663 }
664
665 static void qemu_spice_init(void)
@@ -894,7 +897,8 @@ static int qemu_spice_add_interface(SpiceBaseInstance *sin)
897 spice_server = spice_server_new();
898 spice_server_set_sasl_appname(spice_server, "qemu");
899 spice_server_init(spice_server, &core_interface);
897 - qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
900 + vm_change_entry =
901 + qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
902 }
903
904 return spice_server_add_interface(spice_server, sin);
@@ -1005,8 +1009,25 @@ int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd)
1009 return spice_display_is_running;
1010 }
1011
1012 +static void qemu_spice_cleanup(void)
1013 +{
1014 + if (!spice_server) {
1015 + return;
1016 + }
1017 +
1018 + qemu_spice_display_cleanup();
1019 + qemu_spice_input_cleanup();
1020 + migration_remove_notifier(&migration_state);
1021 + g_clear_pointer(&spice_consoles, g_slist_free);
1022 + g_clear_pointer(&auth_passwd, g_free);
1023 + g_clear_pointer(&spice_server, spice_server_destroy);
1024 + g_clear_pointer(&vm_change_entry, qemu_del_vm_change_state_handler);
1025 + using_spice = 0;
1026 +}
1027 +
1028 static struct QemuSpiceOps real_spice_ops = {
1029 .init = qemu_spice_init,
1030 + .cleanup = qemu_spice_cleanup,
1031 .display_init = qemu_spice_display_init,
1032 .migrate_info = qemu_spice_migrate_info,
1033 .set_passwd = qemu_spice_set_passwd,
ui/spice-display.c
+52
@@ -34,6 +34,8 @@ bool spice_opengl;
34 bool spice_remote_client;
35 int spice_max_refresh_rate;
36
37 +static GPtrArray *spice_displays;
38 +
39 int qemu_spice_rect_is_empty(const QXLRect* r)
40 {
41 return r->top == r->bottom || r->left == r->right;
@@ -1421,6 +1423,54 @@ static void qemu_spice_display_init_one(QemuConsole *con)
1423 qemu_console_set_display_gl_ctx(con, &ssd->dgc);
1424 }
1425 qemu_console_register_listener(con, &ssd->dcl, ops);
1426 + g_ptr_array_add(spice_displays, ssd);
1427 +}
1428 +
1429 +void qemu_spice_display_cleanup(void)
1430 +{
1431 + if (!spice_displays) {
1432 + return;
1433 + }
1434 +
1435 + for (guint i = 0; i < spice_displays->len; i++) {
1436 + SimpleSpiceDisplay *ssd = g_ptr_array_index(spice_displays, i);
1437 + SimpleSpiceUpdate *update;
1438 +
1439 + qemu_console_unregister_listener(&ssd->dcl);
1440 +#ifdef HAVE_SPICE_GL
1441 + if (spice_opengl) {
1442 + qemu_console_set_display_gl_ctx(ssd->dcl.con, NULL);
1443 + }
1444 +#endif
1445 +
1446 + if (ssd->ds) {
1447 + qemu_spice_destroy_host_primary(ssd);
1448 + }
1449 + qemu_spice_del_memslot(ssd, MEMSLOT_GROUP_HOST, 0);
1450 + spice_server_remove_interface(&ssd->qxl.base);
1451 +
1452 + while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) {
1453 + QTAILQ_REMOVE(&ssd->updates, update, next);
1454 + qemu_spice_destroy_update(ssd, update);
1455 + }
1456 + g_clear_pointer(&ssd->ptr_define, g_free);
1457 + g_clear_pointer(&ssd->ptr_move, g_free);
1458 + g_clear_pointer(&ssd->cursor, cursor_unref);
1459 + g_clear_pointer(&ssd->surface, pixman_image_unref);
1460 + g_clear_pointer(&ssd->mirror, pixman_image_unref);
1461 + g_clear_pointer(&ssd->buf, g_free);
1462 +#ifdef HAVE_SPICE_GL
1463 + g_clear_pointer(&ssd->gl_unblock_bh, qemu_bh_delete);
1464 + g_clear_pointer(&ssd->gl_unblock_timer, timer_free);
1465 + g_clear_pointer(&ssd->gls, qemu_gl_fini_shader);
1466 + egl_fb_destroy(&ssd->guest_fb);
1467 + egl_fb_destroy(&ssd->blit_fb);
1468 + egl_fb_destroy(&ssd->cursor_fb);
1469 +#endif
1470 + qemu_mutex_destroy(&ssd->lock);
1471 + g_free(ssd);
1472 + }
1473 + g_clear_pointer(&spice_displays, g_ptr_array_unref);
1474 }
1475
1476 void qemu_spice_display_init(void)
@@ -1431,6 +1481,8 @@ void qemu_spice_display_init(void)
1481 const char *str;
1482 int i;
1483
1484 + spice_displays = g_ptr_array_new();
1485 +
1486 str = qemu_opt_get(opts, "display");
1487 if (str) {
1488 int head = qemu_opt_get_number(opts, "head", 0);
ui/spice-input.c
+35 -18
@@ -242,24 +242,41 @@ static void mouse_mode_notifier(Notifier *notifier, void *data)
242 pointer->absolute = is_absolute;
243 }
244
245 +static QemuSpiceKbd *spice_kbd;
246 +static QemuSpicePointer *spice_pointer;
247 +
248 void qemu_spice_input_init(void)
249 {
247 - QemuSpiceKbd *kbd;
248 - QemuSpicePointer *pointer;
249 -
250 - kbd = g_malloc0(sizeof(*kbd));
251 - kbd->sin.base.sif = &kbd_interface.base;
252 - qemu_spice.add_interface(&kbd->sin.base);
253 - kbd->led_notifier.notify = kbd_leds;
254 - qemu_input_led_notifier_add(&kbd->led_notifier);
255 -
256 - pointer = g_malloc0(sizeof(*pointer));
257 - pointer->mouse.base.sif = &mouse_interface.base;
258 - pointer->tablet.base.sif = &tablet_interface.base;
259 - qemu_spice.add_interface(&pointer->mouse.base);
260 -
261 - pointer->absolute = false;
262 - pointer->mouse_mode.notify = mouse_mode_notifier;
263 - qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode);
264 - mouse_mode_notifier(&pointer->mouse_mode, NULL);
250 + spice_kbd = g_new0(QemuSpiceKbd, 1);
251 + spice_kbd->sin.base.sif = &kbd_interface.base;
252 + qemu_spice.add_interface(&spice_kbd->sin.base);
253 + spice_kbd->led_notifier.notify = kbd_leds;
254 + qemu_input_led_notifier_add(&spice_kbd->led_notifier);
255 +
256 + spice_pointer = g_new0(QemuSpicePointer, 1);
257 + spice_pointer->mouse.base.sif = &mouse_interface.base;
258 + spice_pointer->tablet.base.sif = &tablet_interface.base;
259 + qemu_spice.add_interface(&spice_pointer->mouse.base);
260 +
261 + spice_pointer->absolute = false;
262 + spice_pointer->mouse_mode.notify = mouse_mode_notifier;
263 + qemu_add_mouse_mode_change_notifier(&spice_pointer->mouse_mode);
264 + mouse_mode_notifier(&spice_pointer->mouse_mode, NULL);
265 +}
266 +
267 +void qemu_spice_input_cleanup(void)
268 +{
269 + if (spice_pointer) {
270 + qemu_remove_mouse_mode_change_notifier(&spice_pointer->mouse_mode);
271 + if (spice_pointer->absolute) {
272 + spice_server_remove_interface(&spice_pointer->tablet.base);
273 + }
274 + spice_server_remove_interface(&spice_pointer->mouse.base);
275 + g_clear_pointer(&spice_pointer, g_free);
276 + }
277 + if (spice_kbd) {
278 + qemu_input_led_notifier_remove(&spice_kbd->led_notifier);
279 + spice_server_remove_interface(&spice_kbd->sin.base);
280 + g_clear_pointer(&spice_kbd, g_free);
281 + }
282 }
ui/spice-module.c
+5
@@ -62,6 +62,10 @@ static int qemu_spice_display_add_client_stub(int csock, int skipauth,
62 return -1;
63 }
64
65 +static void qemu_spice_cleanup_stub(void)
66 +{
67 +}
68 +
69 struct QemuSpiceOps qemu_spice = {
70 .init = qemu_spice_init_stub,
71 .display_init = qemu_spice_display_init_stub,
@@ -69,6 +73,7 @@ struct QemuSpiceOps qemu_spice = {
73 .set_passwd = qemu_spice_set_passwd_stub,
74 .set_pw_expire = qemu_spice_set_pw_expire_stub,
75 .display_add_client = qemu_spice_display_add_client_stub,
76 + .cleanup = qemu_spice_cleanup_stub,
77 };
78
79 #ifdef CONFIG_SPICE