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;