@samitouri / QOSamiQemu / commits / 3ca9eff4af

ui/vnc: make the worker thread per-VncDisplay

The VNC encoding worker thread was using a single global queue shared across all VNC displays, with no way to stop it. This made it impossible to properly clean up resources when a VncDisplay is freed. Move the VncJobQueue from a file-scoped global to a per-VncDisplay member, so each display owns its worker thread and queue. Add vnc_stop_worker_thread() to perform an orderly shutdown: signal the thread to exit, join it, and destroy the queue. The thread is now created as QEMU_THREAD_JOINABLE instead of QEMU_THREAD_DETACHED. Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Marc-André Lureau committed Apr 7, 2026 at 16:03 UTC 3ca9eff4aff07489d885691f6fa5db199c2674a9
4 files changed +46 -24
ui/vnc-jobs.c
+40 -22
@@ -29,8 +29,6 @@
29 #include "qemu/osdep.h"
30 #include "vnc.h"
31 #include "vnc-jobs.h"
32 -#include "qemu/sockets.h"
33 -#include "qemu/main-loop.h"
32 #include "trace.h"
33
34 /*
@@ -56,17 +54,10 @@ struct VncJobQueue {
54 QemuCond cond;
55 QemuMutex mutex;
56 QemuThread thread;
57 + bool exit;
58 QTAILQ_HEAD(, VncJob) jobs;
59 };
60
62 -typedef struct VncJobQueue VncJobQueue;
63 -
64 -/*
65 - * We use a single global queue, but most of the functions are
66 - * already reentrant, so we can easily add more than one encoding thread
67 - */
68 -static VncJobQueue *queue;
69 -
61 static void vnc_lock_queue(VncJobQueue *queue)
62 {
63 qemu_mutex_lock(&queue->mutex);
@@ -125,12 +116,15 @@ static void vnc_job_free(VncJob *job)
116 */
117 void vnc_job_push(VncJob *job)
118 {
119 + VncJobQueue *queue = job->vs->vd->queue;
120 +
121 assert(!QTAILQ_IN_USE(job, next));
122
123 if (QLIST_EMPTY(&job->rectangles)) {
124 vnc_job_free(job);
125 } else {
126 vnc_lock_queue(queue);
127 + assert(!queue->exit);
128 QTAILQ_INSERT_TAIL(&queue->jobs, job, next);
129 qemu_cond_broadcast(&queue->cond);
130 vnc_unlock_queue(queue);
@@ -139,6 +133,7 @@ void vnc_job_push(VncJob *job)
133
134 static bool vnc_has_job_locked(VncState *vs)
135 {
136 + VncJobQueue *queue = vs->vd->queue;
137 VncJob *job;
138
139 QTAILQ_FOREACH(job, &queue->jobs, next) {
@@ -151,6 +146,8 @@ static bool vnc_has_job_locked(VncState *vs)
146
147 void vnc_jobs_join(VncState *vs)
148 {
149 + VncJobQueue *queue = vs->vd->queue;
150 +
151 vnc_lock_queue(queue);
152 while (vnc_has_job_locked(vs)) {
153 qemu_cond_wait(&queue->cond, &queue->mutex);
@@ -252,9 +249,13 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
249 int saved_offset;
250
251 vnc_lock_queue(queue);
255 - while (QTAILQ_EMPTY(&queue->jobs)) {
252 + while (QTAILQ_EMPTY(&queue->jobs) && !queue->exit) {
253 qemu_cond_wait(&queue->cond, &queue->mutex);
254 }
255 + if (queue->exit) {
256 + vnc_unlock_queue(queue);
257 + return 1;
258 + }
259 job = QTAILQ_FIRST(&queue->jobs);
260 vnc_unlock_queue(queue);
261
@@ -340,7 +341,7 @@ disconnected:
341 return 0;
342 }
343
343 -static VncJobQueue *vnc_queue_init(void)
344 +static VncJobQueue *vnc_queue_new(void)
345 {
346 VncJobQueue *queue = g_new0(VncJobQueue, 1);
347
@@ -350,29 +351,46 @@ static VncJobQueue *vnc_queue_init(void)
351 return queue;
352 }
353
354 +static void vnc_queue_free(VncJobQueue *queue)
355 +{
356 + qemu_cond_destroy(&queue->cond);
357 + qemu_mutex_destroy(&queue->mutex);
358 + g_free(queue);
359 +}
360 +
361 static void *vnc_worker_thread(void *arg)
362 {
363 VncJobQueue *queue = arg;
364
365 while (!vnc_worker_thread_loop(queue)) ;
358 - g_assert_not_reached();
366 +
367 return NULL;
368 }
369
362 -static bool vnc_worker_thread_running(void)
370 +void vnc_start_worker_thread(VncDisplay *vd)
371 {
364 - return queue; /* Check global queue */
372 + assert(vd->queue == NULL);
373 +
374 + vd->queue = vnc_queue_new();
375 + qemu_thread_create(&vd->queue->thread, "vnc_worker", vnc_worker_thread, vd->queue,
376 + QEMU_THREAD_JOINABLE);
377 }
378
367 -void vnc_start_worker_thread(void)
379 +void vnc_stop_worker_thread(VncDisplay *vd)
380 {
369 - VncJobQueue *q;
381 + VncJobQueue *queue = vd->queue;
382
371 - if (vnc_worker_thread_running())
383 + if (!queue) {
384 return;
385 + }
386 +
387 + /* all VNC clients must have finished before we can stop the worker thread */
388 + vnc_lock_queue(queue);
389 + assert(QTAILQ_EMPTY(&queue->jobs));
390 + queue->exit = true;
391 + qemu_cond_broadcast(&queue->cond);
392 + vnc_unlock_queue(queue);
393
374 - q = vnc_queue_init();
375 - qemu_thread_create(&q->thread, "vnc_worker", vnc_worker_thread, q,
376 - QEMU_THREAD_DETACHED);
377 - queue = q; /* Set global queue */
394 + qemu_thread_join(&queue->thread);
395 + g_clear_pointer(&vd->queue, vnc_queue_free);
396 }
ui/vnc-jobs.h
+2 -1
@@ -37,7 +37,8 @@ void vnc_job_push(VncJob *job);
37 void vnc_jobs_join(VncState *vs);
38
39 void vnc_jobs_consume_buffer(VncState *vs);
40 -void vnc_start_worker_thread(void);
40 +void vnc_start_worker_thread(VncDisplay *vd);
41 +void vnc_stop_worker_thread(VncDisplay *vd);
42
43 /* Locks */
44 static inline int vnc_trylock_display(VncDisplay *vd)
ui/vnc.c
+2 -1
@@ -3457,7 +3457,7 @@ void vnc_display_init(const char *id, Error **errp)
3457 vd->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
3458 vd->connections_limit = 32;
3459
3460 - vnc_start_worker_thread();
3460 + vnc_start_worker_thread(vd);
3461
3462 register_displaychangelistener(&vd->dcl);
3463 vd->kbd = qkbd_state_init(vd->dcl.con);
@@ -3513,6 +3513,7 @@ static void vnc_display_free(VncDisplay *vd)
3513
3514 assert(QTAILQ_EMPTY(&vd->clients));
3515
3516 + vnc_stop_worker_thread(vd);
3517 vnc_display_close(vd);
3518 unregister_displaychangelistener(&vd->dcl);
3519 qkbd_state_free(vd->kbd);
ui/vnc.h
+2
@@ -62,6 +62,7 @@
62
63 typedef struct VncState VncState;
64 typedef struct VncJob VncJob;
65 +typedef struct VncJobQueue VncJobQueue;
66 typedef struct VncRect VncRect;
67 typedef struct VncRectEntry VncRectEntry;
68
@@ -158,6 +159,7 @@ struct VncDisplay
159 int ledstate;
160 QKbdState *kbd;
161 QemuMutex mutex;
162 + VncJobQueue *queue;
163
164 int cursor_msize;
165 uint8_t *cursor_mask;