On Fri, Jun 4, 2010 at 3:44 PM, Alexander Graf <ag...@suse.de> wrote: > > On 04.06.2010, at 15:20, Corentin Chary wrote: > >> Implement a threaded VNC server using the producer-consumer model. >> The main thread will push encoding jobs (a list a rectangles to update) >> in a queue, and the VNC worker thread will consume that queue and send >> framebuffer updates to the output buffer. >> >> The threaded VNC server can be enabled with ./configure --enable-vnc-thread. >> >> If you don't want it, just use ./configure --disable-vnc-thread and a >> syncrhonous >> queue of job will be used (which as exactly the same behavior as the old >> queue). >> If you disable the VNC thread, all thread related code will not be built and >> there will >> be no overhead. >> >> Signed-off-by: Corentin Chary <corenti...@iksaif.net> >> --- >> Makefile.objs | 7 +- >> configure | 13 ++ >> ui/vnc-jobs-sync.c | 65 ++++++++++ >> ui/vnc-jobs.c | 351 >> ++++++++++++++++++++++++++++++++++++++++++++++++++++ >> ui/vnc.c | 169 ++++++++++++++++++++++---- >> ui/vnc.h | 75 +++++++++++ >> 6 files changed, 657 insertions(+), 23 deletions(-) >> create mode 100644 ui/vnc-jobs-sync.c >> create mode 100644 ui/vnc-jobs.c >> >> diff --git a/Makefile.objs b/Makefile.objs >> index 22622a9..0c6334b 100644 >> --- a/Makefile.objs >> +++ b/Makefile.objs >> @@ -109,10 +109,15 @@ ui-obj-y += vnc-enc-tight.o >> ui-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o >> ui-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o >> ui-obj-$(CONFIG_COCOA) += cocoa.o >> +ifdef CONFIG_VNC_THREAD >> +ui-obj-y += vnc-jobs.o >> +else >> +ui-obj-y += vnc-jobs-sync.o >> +endif >> common-obj-y += $(addprefix ui/, $(ui-obj-y)) >> >> common-obj-y += iov.o acl.o >> -common-obj-$(CONFIG_IOTHREAD) += qemu-thread.o >> +common-obj-$(CONFIG_THREAD) += qemu-thread.o >> common-obj-y += notify.o event_notifier.o >> common-obj-y += qemu-timer.o >> >> diff --git a/configure b/configure >> index 679f2fc..6f2e3a7 100755 >> --- a/configure >> +++ b/configure >> @@ -264,6 +264,7 @@ vde="" >> vnc_tls="" >> vnc_sasl="" >> vnc_jpeg="" >> +vnc_thread="" >> xen="" >> linux_aio="" >> vhost_net="" >> @@ -552,6 +553,10 @@ for opt do >> ;; >> --enable-vnc-jpeg) vnc_jpeg="yes" >> ;; >> + --disable-vnc-thread) vnc_thread="no" >> + ;; >> + --enable-vnc-thread) vnc_thread="yes" >> + ;; >> --disable-slirp) slirp="no" >> ;; >> --disable-uuid) uuid="no" >> @@ -786,6 +791,8 @@ echo " --disable-vnc-sasl disable SASL encryption >> for VNC server" >> echo " --enable-vnc-sasl enable SASL encryption for VNC server" >> echo " --disable-vnc-jpeg disable JPEG lossy compression for VNC >> server" >> echo " --enable-vnc-jpeg enable JPEG lossy compression for VNC >> server" >> +echo " --disable-vnc-thread disable threaded VNC server" >> +echo " --enable-vnc-thread enable threaded VNC server" >> echo " --disable-curses disable curses output" >> echo " --enable-curses enable curses output" >> echo " --disable-curl disable curl connectivity" >> @@ -2048,6 +2055,7 @@ echo "Mixer emulation $mixemu" >> echo "VNC TLS support $vnc_tls" >> echo "VNC SASL support $vnc_sasl" >> echo "VNC JPEG support $vnc_jpeg" >> +echo "VNC thread $vnc_thread" >> if test -n "$sparc_cpu"; then >> echo "Target Sparc Arch $sparc_cpu" >> fi >> @@ -2191,6 +2199,10 @@ if test "$vnc_jpeg" = "yes" ; then >> echo "CONFIG_VNC_JPEG=y" >> $config_host_mak >> echo "VNC_JPEG_CFLAGS=$vnc_jpeg_cflags" >> $config_host_mak >> fi >> +if test "$vnc_thread" = "yes" ; then > > So it's disabled by default? Sounds like a pretty cool and useful feature to > me that should be enabled by default.
Because it's does not work on windows (qemu-thread.c only uses pthread) and because I don't want to break everything :) >> + echo "CONFIG_VNC_THREAD=y" >> $config_host_mak >> + echo "CONFIG_THREAD=y" >> $config_host_mak >> +fi >> if test "$fnmatch" = "yes" ; then >> echo "CONFIG_FNMATCH=y" >> $config_host_mak >> fi >> @@ -2267,6 +2279,7 @@ if test "$xen" = "yes" ; then >> fi >> if test "$io_thread" = "yes" ; then >> echo "CONFIG_IOTHREAD=y" >> $config_host_mak >> + echo "CONFIG_THREAD=y" >> $config_host_mak >> fi >> if test "$linux_aio" = "yes" ; then >> echo "CONFIG_LINUX_AIO=y" >> $config_host_mak >> diff --git a/ui/vnc-jobs-sync.c b/ui/vnc-jobs-sync.c >> new file mode 100644 >> index 0000000..9f138f5 >> --- /dev/null >> +++ b/ui/vnc-jobs-sync.c >> @@ -0,0 +1,65 @@ >> +/* >> + * QEMU VNC display driver >> + * >> + * Copyright (C) 2006 Anthony Liguori <anth...@codemonkey.ws> >> + * Copyright (C) 2006 Fabrice Bellard >> + * Copyright (C) 2009 Red Hat, Inc >> + * Copyright (C) 2010 Corentin Chary <corentin.ch...@gmail.com> >> + * >> + * Permission is hereby granted, free of charge, to any person obtaining a >> copy >> + * of this software and associated documentation files (the "Software"), to >> deal >> + * in the Software without restriction, including without limitation the >> rights >> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell >> + * copies of the Software, and to permit persons to whom the Software is >> + * furnished to do so, subject to the following conditions: >> + * >> + * The above copyright notice and this permission notice shall be included >> in >> + * all copies or substantial portions of the Software. >> + * >> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS >> OR >> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, >> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL >> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR >> OTHER >> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING >> FROM, >> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN >> + * THE SOFTWARE. >> + */ >> + >> + >> +#include "vnc.h" >> + >> +VncJob *vnc_job_new(VncState *vs) >> +{ >> + vs->job.vs = vs; >> + vs->job.rectangles = 0; >> + >> + vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> + vnc_write_u8(vs, 0); > > Creating a job writes out a framebuffer update message? Why? This is vnc-job-sync.c, since it's synchroneous, we have to start the update here. >> + vs->job.saved_offset = vs->output.offset; >> + vnc_write_u16(vs, 0); >> + return &vs->job; >> +} >> + >> +void vnc_job_push(VncJob *job) >> +{ >> + VncState *vs = job->vs; >> + >> + vs->output.buffer[job->saved_offset] = (job->rectangles >> 8) & 0xFF; >> + vs->output.buffer[job->saved_offset + 1] = job->rectangles & 0xFF; > > There's a 16 bit little endian pointer helper somewhere. Probably better to > use that - makes the code more readable. Hum right, >> + vnc_flush(job->vs); >> +} >> + >> +int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h) >> +{ >> + int n; >> + >> + n = vnc_send_framebuffer_update(job->vs, x, y, w, h); >> + if (n >= 0) >> + job->rectangles += n; > > Coding style. > >> + return n; >> +} >> + >> +bool vnc_has_job(VncState *vs) >> +{ >> + return false; >> +} >> diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c >> new file mode 100644 >> index 0000000..65ce5f8 >> --- /dev/null >> +++ b/ui/vnc-jobs.c >> @@ -0,0 +1,351 @@ >> +/* >> + * QEMU VNC display driver >> + * >> + * Copyright (C) 2006 Anthony Liguori <anth...@codemonkey.ws> >> + * Copyright (C) 2006 Fabrice Bellard >> + * Copyright (C) 2009 Red Hat, Inc >> + * Copyright (C) 2010 Corentin Chary <corentin.ch...@gmail.com> >> + * >> + * Permission is hereby granted, free of charge, to any person obtaining a >> copy >> + * of this software and associated documentation files (the "Software"), to >> deal >> + * in the Software without restriction, including without limitation the >> rights >> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell >> + * copies of the Software, and to permit persons to whom the Software is >> + * furnished to do so, subject to the following conditions: >> + * >> + * The above copyright notice and this permission notice shall be included >> in >> + * all copies or substantial portions of the Software. >> + * >> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS >> OR >> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, >> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL >> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR >> OTHER >> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING >> FROM, >> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN >> + * THE SOFTWARE. >> + */ >> + >> + >> +#include "vnc.h" >> + >> +/* >> + * Locking: >> + * >> + * There is three levels of locking: >> + * - jobs queue lock: for each operation on the queue (push, pop, isEmpty?) >> + * - VncDisplay global lock: mainly used for framebuffer updates to avoid >> + * screen corruption if the framebuffer is updated >> + * while the worker is doing something. >> + * - VncState::output lock: used to make sure the output buffer is not >> corrupted >> + * if two threads try to write on it at the same time >> + * >> + * While the VNC worker thread is working, the VncDisplay global lock is >> hold >> + * to avoid screen corruptions (this does not block vnc_refresh() because it >> + * uses trylock()) but the output lock is not hold because the thread work >> on >> + * its own output buffer. >> + * When the encoding job is done, the worker thread will hold the output >> lock >> + * and copy its output buffer in vs->output. >> +*/ >> + >> +struct VncJobQueue { >> + QemuCond cond; >> + QemuMutex mutex; >> + QemuThread thread; >> + Buffer buffer; >> + bool exit; >> + QTAILQ_HEAD(, VncJob) jobs; >> +}; >> + >> +typedef struct VncJobQueue VncJobQueue; >> + >> +/* >> + * We use a single global queue, but most of the functions are >> + * already reetrant, so we can easilly add more than one encoding thread >> + */ >> +static VncJobQueue *queue; >> + >> +static void vnc_lock_queue(VncJobQueue *queue) >> +{ >> + qemu_mutex_lock(&queue->mutex); >> +} >> + >> +static void vnc_unlock_queue(VncJobQueue *queue) >> +{ >> + qemu_mutex_unlock(&queue->mutex); >> +} >> + >> +VncJob *vnc_job_new(VncState *vs) >> +{ >> + VncJob *job = qemu_mallocz(sizeof(VncJob)); >> + >> + job->vs = vs; >> + vnc_lock_queue(queue); >> + QLIST_INIT(&job->rectangles); >> + vnc_unlock_queue(queue); >> + return job; >> +} >> + >> +int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h) >> +{ >> + VncRectEntry *entry = qemu_mallocz(sizeof(VncRectEntry)); >> + >> + entry->rect.x = x; >> + entry->rect.y = y; >> + entry->rect.w = w; >> + entry->rect.h = h; >> + >> + vnc_lock_queue(queue); >> + QLIST_INSERT_HEAD(&job->rectangles, entry, next); >> + vnc_unlock_queue(queue); >> + return 1; >> +} >> + >> +void vnc_job_push(VncJob *job) >> +{ >> + vnc_lock_queue(queue); >> + if (QLIST_EMPTY(&job->rectangles)) { >> + qemu_free(job); >> + } else { >> + QTAILQ_INSERT_TAIL(&queue->jobs, job, next); >> + qemu_cond_broadcast(&queue->cond); >> + } >> + vnc_unlock_queue(queue); >> +} >> + >> +static bool vnc_has_job_locked(VncState *vs) >> +{ >> + VncJob *job; >> + >> + QTAILQ_FOREACH(job, &queue->jobs, next) { >> + if (job->vs == vs || !vs) { >> + return true; >> + } >> + } >> + return false; >> +} >> + >> +bool vnc_has_job(VncState *vs) >> +{ >> + bool ret; >> + >> + vnc_lock_queue(queue); >> + ret = vnc_has_job_locked(vs); >> + vnc_unlock_queue(queue); >> + return ret; >> +} >> + >> +void vnc_jobs_clear(VncState *vs) >> +{ >> + VncJob *job, *tmp; >> + >> + vnc_lock_queue(queue); >> + QTAILQ_FOREACH_SAFE(job, &queue->jobs, next, tmp) { >> + if (job->vs == vs || !vs) >> + QTAILQ_REMOVE(&queue->jobs, job, next); > > Coding style... > >> + } >> + vnc_unlock_queue(queue); >> +} >> + >> +void vnc_jobs_join(VncState *vs) >> +{ >> + vnc_lock_queue(queue); >> + while (vnc_has_job_locked(vs)) { >> + qemu_cond_wait(&queue->cond, &queue->mutex); >> + } >> + vnc_unlock_queue(queue); >> +} >> + >> +/* >> + * Copy data for local use >> + * FIXME: isolate what we use in a specific structure >> + * to avoid invalid usage in vnc-encoding-*.c and avoid copying >> + * because what we want is only want is only swapping VncState::output >> + * with the queue buffer >> + */ >> +static void vnc_async_encoding_start(VncState *orig, VncState *local) >> +{ >> + local->vnc_encoding = orig->vnc_encoding; >> + local->features = orig->features; >> + local->ds = orig->ds; >> + local->vd = orig->vd; >> + local->write_pixels = orig->write_pixels; >> + local->clientds = orig->clientds; >> + local->tight_quality = orig->tight_quality; >> + local->tight_compression = orig->tight_compression; >> + local->tight_pixel24 = 0; >> + local->tight = orig->tight; >> + local->tight_tmp = orig->tight_tmp; >> + local->tight_zlib = orig->tight_zlib; >> + local->tight_gradient = orig->tight_gradient; >> + local->tight_jpeg = orig->tight_jpeg; >> + memcpy(local->tight_levels, orig->tight_levels, >> sizeof(orig->tight_levels)); >> + memcpy(local->tight_stream, orig->tight_stream, >> sizeof(orig->tight_stream)); >> + local->send_hextile_tile = orig->send_hextile_tile; >> + local->zlib = orig->zlib; >> + local->zlib_tmp = orig->zlib_tmp; >> + local->zlib_stream = orig->zlib_stream; >> + local->zlib_level = orig->zlib_level; >> + local->output = queue->buffer; >> + local->csock = -1; /* Don't do any network work on this thread */ > > Wow, this looks like a lot of copies. How about a *local = *orig? Then just > clean up the 3 variables you have to. > >> + >> + buffer_reset(&local->output); >> +} >> + >> +static void vnc_async_encoding_end(VncState *orig, VncState *local) >> +{ >> + orig->tight_quality = local->tight_quality; >> + orig->tight_compression = local->tight_compression; >> + orig->tight = local->tight; >> + orig->tight_tmp = local->tight_tmp; >> + orig->tight_zlib = local->tight_zlib; >> + orig->tight_gradient = local->tight_gradient; >> + orig->tight_jpeg = local->tight_jpeg; >> + memcpy(orig->tight_levels, local->tight_levels, >> sizeof(local->tight_levels)); >> + memcpy(orig->tight_stream, local->tight_stream, >> sizeof(local->tight_stream)); >> + orig->zlib = local->zlib; >> + orig->zlib_tmp = local->zlib_tmp; >> + orig->zlib_stream = local->zlib_stream; >> + orig->zlib_level = local->zlib_level; > > This is probably a bit more complicated to get clean. How about putting the > tight and the zlib information in structs, so you can just move those around? > orig->tight = local->tight; Yep, I should use specific structures, but this will come in a later patch. >> +} >> + >> +static int vnc_worker_thread_loop(VncJobQueue *queue) >> +{ >> + VncJob *job; >> + VncRectEntry *entry, *tmp; >> + VncState vs; >> + int n_rectangles; >> + int saved_offset; >> + bool flush; >> + >> + vnc_lock_queue(queue); >> + if (QTAILQ_EMPTY(&queue->jobs)) { >> + qemu_cond_wait(&queue->cond, &queue->mutex); >> + } >> + >> + /* If the queue is empty, it's an exit order */ >> + if (QTAILQ_EMPTY(&queue->jobs)) { >> + vnc_unlock_queue(queue); >> + return -1; >> + } >> + >> + job = QTAILQ_FIRST(&queue->jobs); >> + vnc_unlock_queue(queue); >> + >> + qemu_mutex_lock(&job->vs->output_mutex); >> + if (job->vs->csock == -1 || job->vs->abording == true) { >> + goto disconnected; >> + } >> + qemu_mutex_unlock(&job->vs->output_mutex); >> + >> + /* Make a local copy of vs and switch output buffers */ >> + vnc_async_encoding_start(job->vs, &vs); >> + >> + /* Start sending rectangles */ >> + n_rectangles = 0; >> + vnc_write_u8(&vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> + vnc_write_u8(&vs, 0); >> + saved_offset = vs.output.offset; >> + vnc_write_u16(&vs, 0); > > This too strikes me as odd. > >> + >> + qemu_mutex_lock(&job->vs->vd->mutex); >> + QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) { >> + int n; >> + >> + if (job->vs->csock == -1) { >> + qemu_mutex_unlock(&job->vs->vd->mutex); >> + goto disconnected; >> + } >> + >> + n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y, >> + entry->rect.w, entry->rect.h); > > Wow, wait. You're coming from a rect, put everything in individual parameters > and form them into a rect again afterwards? Just keep the rect :). > >> + >> + if (n >= 0) >> + n_rectangles += n; >> + qemu_free(entry); >> + } >> + qemu_mutex_unlock(&job->vs->vd->mutex); >> + >> + /* Put n_rectangles at the beginning of the message */ >> + vs.output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; >> + vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF; > > Deja vu? > >> + >> + /* Switch back buffers */ >> + qemu_mutex_lock(&job->vs->output_mutex); >> + if (job->vs->csock == -1) { >> + goto disconnected; >> + } >> + >> + vnc_write(job->vs, vs.output.buffer, vs.output.offset); >> + >> +disconnected: >> + /* Copy persistent encoding data */ >> + vnc_async_encoding_end(job->vs, &vs); >> + flush = (job->vs->csock != -1 && job->vs->abording != true); >> + qemu_mutex_unlock(&job->vs->output_mutex); >> + >> + if (flush) { >> + vnc_flush(job->vs); >> + } >> + >> + qemu_mutex_lock(&queue->mutex); >> + QTAILQ_REMOVE(&queue->jobs, job, next); >> + qemu_mutex_unlock(&queue->mutex); >> + qemu_cond_broadcast(&queue->cond); >> + qemu_free(job); >> + return 0; >> +} >> + >> +static VncJobQueue *vnc_queue_init(void) >> +{ >> + VncJobQueue *queue = qemu_mallocz(sizeof(VncJobQueue)); >> + >> + qemu_cond_init(&queue->cond); >> + qemu_mutex_init(&queue->mutex); >> + QTAILQ_INIT(&queue->jobs); >> + return queue; >> +} >> + >> +static void vnc_queue_clear(VncJobQueue *q) >> +{ >> + qemu_cond_destroy(&queue->cond); >> + qemu_mutex_destroy(&queue->mutex); >> + buffer_free(&queue->buffer); >> + qemu_free(q); >> + queue = NULL; /* Unset global queue */ >> +} >> + >> +static void *vnc_worker_thread(void *arg) >> +{ >> + VncJobQueue *queue = arg; >> + >> + while (!vnc_worker_thread_loop(queue)) ; >> + vnc_queue_clear(queue); >> + return NULL; >> +} >> + >> +void vnc_start_worker_thread(void) >> +{ >> + VncJobQueue *q; >> + >> + if (vnc_worker_thread_running()) >> + return ; >> + >> + q = vnc_queue_init(); >> + qemu_thread_create(&q->thread, vnc_worker_thread, q); >> + queue = q; /* Set global queue */ >> +} >> + >> +bool vnc_worker_thread_running(void) >> +{ >> + return queue; /* Check global queue */ >> +} >> + >> +void vnc_stop_worker_thread(void) >> +{ >> + if (!vnc_worker_thread_running()) >> + return ; >> + >> + /* Remove all jobs and wake up the thread */ >> + vnc_jobs_clear(NULL); >> + qemu_cond_broadcast(&queue->cond); >> +} >> diff --git a/ui/vnc.c b/ui/vnc.c >> index e3ef315..f6475b3 100644 >> --- a/ui/vnc.c >> +++ b/ui/vnc.c >> @@ -45,6 +45,36 @@ >> } \ >> } >> >> +#ifdef CONFIG_VNC_THREAD >> +static int vnc_trylock_display(VncDisplay *vd) >> +{ >> + return qemu_mutex_trylock(&vd->mutex); >> +} >> + >> +static void vnc_unlock_display(VncDisplay *vd) >> +{ >> + qemu_mutex_unlock(&vd->mutex); >> +} >> + >> +static void vnc_lock_output(VncState *vs) >> +{ >> + qemu_mutex_lock(&vs->output_mutex); >> +} >> + >> +static void vnc_unlock_output(VncState *vs) >> +{ >> + qemu_mutex_unlock(&vs->output_mutex); >> +} > > Not static, but static inline. I guess. But then again that doesn't really > hurt - we're not in a header file. I think it's better to let gcc decide if this should be inlined or not. (Here it will probably be inline with -O2). >> +#else >> +static int vnc_trylock_display(VncDisplay *vd) >> +{ >> + return 0; >> +} >> + >> +#define vnc_unlock_display(vs) (void) (vs); >> +#define vnc_lock_output(vs) (void) (vs); >> +#define vnc_unlock_output(vs) (void) (vs); > > Please make those static functions too. Ok, >> +#endif >> >> static VncDisplay *vnc_display; /* needed for info vnc */ >> static DisplayChangeListener *dcl; >> @@ -363,6 +393,7 @@ static inline uint32_t vnc_has_feature(VncState *vs, int >> feature) { >> */ >> >> static int vnc_update_client(VncState *vs, int has_dirty); >> +static int vnc_update_client_sync(VncState *vs, int has_dirty); >> static void vnc_disconnect_start(VncState *vs); >> static void vnc_disconnect_finish(VncState *vs); >> static void vnc_init_timer(VncDisplay *vd); >> @@ -493,6 +524,7 @@ void buffer_append(Buffer *buffer, const void *data, >> size_t len) >> buffer->offset += len; >> } >> >> + > > Huh? > >> static void vnc_desktop_resize(VncState *vs) >> { >> DisplayState *ds = vs->ds; >> @@ -506,19 +538,46 @@ static void vnc_desktop_resize(VncState *vs) >> } >> vs->client_width = ds_get_width(ds); >> vs->client_height = ds_get_height(ds); >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> vnc_write_u8(vs, 0); >> vnc_write_u16(vs, 1); /* number of rects */ >> vnc_framebuffer_update(vs, 0, 0, vs->client_width, vs->client_height, >> VNC_ENCODING_DESKTOPRESIZE); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } >> >> +#ifdef CONFIG_VNC_THREAD >> +static void vnc_abort_display_jobs(VncDisplay *vd) >> +{ >> + VncState *vs; >> + >> + QTAILQ_FOREACH(vs, &vd->clients, next) { >> + vnc_lock_output(vs); >> + vs->abording = true; >> + vnc_unlock_output(vs); >> + } >> + QTAILQ_FOREACH(vs, &vd->clients, next) { >> + vnc_jobs_join(vs); >> + } >> + QTAILQ_FOREACH(vs, &vd->clients, next) { >> + vnc_lock_output(vs); >> + vs->abording = false; >> + vnc_unlock_output(vs); >> + } >> +} >> +#else >> +#define vnc_abort_display_jobs(vd) > > see above > >> +#endif >> + >> static void vnc_dpy_resize(DisplayState *ds) >> { >> VncDisplay *vd = ds->opaque; >> VncState *vs; >> >> + vnc_abort_display_jobs(vd); >> + >> /* server surface */ >> if (!vd->server) >> vd->server = qemu_mallocz(sizeof(*vd->server)); >> @@ -646,7 +705,7 @@ int vnc_raw_send_framebuffer_update(VncState *vs, int x, >> int y, int w, int h) >> return 1; >> } >> >> -static int send_framebuffer_update(VncState *vs, int x, int y, int w, int h) >> +int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) >> { >> int n = 0; >> >> @@ -672,12 +731,14 @@ static int send_framebuffer_update(VncState *vs, int >> x, int y, int w, int h) >> static void vnc_copy(VncState *vs, int src_x, int src_y, int dst_x, int >> dst_y, int w, int h) >> { >> /* send bitblit op to the vnc client */ >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> vnc_write_u8(vs, 0); >> vnc_write_u16(vs, 1); /* number of rects */ >> vnc_framebuffer_update(vs, dst_x, dst_y, w, h, VNC_ENCODING_COPYRECT); >> vnc_write_u16(vs, src_x); >> vnc_write_u16(vs, src_y); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } >> >> @@ -694,7 +755,7 @@ static void vnc_dpy_copy(DisplayState *ds, int src_x, >> int src_y, int dst_x, int >> QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { >> if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) { >> vs->force_update = 1; >> - vnc_update_client(vs, 1); >> + vnc_update_client_sync(vs, 1); >> /* vs might be free()ed here */ >> } >> } >> @@ -814,15 +875,29 @@ static int find_and_clear_dirty_height(struct VncState >> *vs, >> return h; >> } >> >> +#ifdef CONFIG_VNC_THREAD >> +static int vnc_update_client_sync(VncState *vs, int has_dirty) >> +{ >> + int ret = vnc_update_client(vs, has_dirty); >> + vnc_jobs_join(vs); >> + return ret; >> +} >> +#else >> +static int vnc_update_client_sync(VncState *vs, int has_dirty) >> +{ >> + return vnc_update_client(vs, has_dirty); >> +} >> +#endif >> + >> static int vnc_update_client(VncState *vs, int has_dirty) >> { >> if (vs->need_update && vs->csock != -1) { >> VncDisplay *vd = vs->vd; >> + VncJob *job; >> int y; >> - int n_rectangles; >> - int saved_offset; >> int width, height; >> - int n; >> + int n = 0; >> + >> >> if (vs->output.offset && !vs->audio_cap && !vs->force_update) >> /* kernel send buffers are full -> drop frames to throttle */ >> @@ -837,11 +912,7 @@ static int vnc_update_client(VncState *vs, int >> has_dirty) >> * happening in parallel don't disturb us, the next pass will >> * send them to the client. >> */ >> - n_rectangles = 0; >> - vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> - vnc_write_u8(vs, 0); >> - saved_offset = vs->output.offset; >> - vnc_write_u16(vs, 0); >> + job = vnc_job_new(vs); >> >> width = MIN(vd->server->width, vs->client_width); >> height = MIN(vd->server->height, vs->client_height); >> @@ -858,25 +929,23 @@ static int vnc_update_client(VncState *vs, int >> has_dirty) >> } else { >> if (last_x != -1) { >> int h = find_and_clear_dirty_height(vs, y, last_x, >> x); >> - n = send_framebuffer_update(vs, last_x * 16, y, >> - (x - last_x) * 16, h); >> - n_rectangles += n; >> + >> + n += vnc_job_add_rect(job, last_x * 16, y, >> + (x - last_x) * 16, h); >> } >> last_x = -1; >> } >> } >> if (last_x != -1) { >> int h = find_and_clear_dirty_height(vs, y, last_x, x); >> - n = send_framebuffer_update(vs, last_x * 16, y, >> - (x - last_x) * 16, h); >> - n_rectangles += n; >> + n += vnc_job_add_rect(job, last_x * 16, y, >> + (x - last_x) * 16, h); > > Oh, so that's why. Well, better keep the individual parameters then. > >> } >> } >> - vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; >> - vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF; >> - vnc_flush(vs); >> + >> + vnc_job_push(job); >> vs->force_update = 0; >> - return n_rectangles; >> + return n; > > So by now the rest of the code thought you successfully processed that rect, > right? Yes. >> } >> >> if (vs->csock == -1) >> @@ -892,16 +961,20 @@ static void audio_capture_notify(void *opaque, >> audcnotification_e cmd) >> >> switch (cmd) { >> case AUD_CNOTIFY_DISABLE: >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); >> vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); >> vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_END); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> break; >> >> case AUD_CNOTIFY_ENABLE: >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); >> vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); >> vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_BEGIN); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> break; >> } >> @@ -915,11 +988,13 @@ static void audio_capture(void *opaque, void *buf, int >> size) >> { >> VncState *vs = opaque; >> >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); >> vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); >> vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA); >> vnc_write_u32(vs, size); >> vnc_write(vs, buf, size); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } >> >> @@ -961,6 +1036,9 @@ static void vnc_disconnect_start(VncState *vs) >> >> static void vnc_disconnect_finish(VncState *vs) >> { >> + vnc_jobs_join(vs); /* Wait encoding jobs */ >> + >> + vnc_lock_output(vs); >> vnc_qmp_event(vs, QEVENT_VNC_DISCONNECTED); >> >> buffer_free(&vs->input); >> @@ -989,6 +1067,11 @@ static void vnc_disconnect_finish(VncState *vs) >> vnc_remove_timer(vs->vd); >> if (vs->vd->lock_key_sync) >> qemu_remove_led_event_handler(vs->led); >> + vnc_unlock_output(vs); >> + >> +#ifdef CONFIG_VNC_THREAD >> + qemu_mutex_destroy(&vs->output_mutex); >> +#endif >> qemu_free(vs); >> } >> >> @@ -1108,7 +1191,7 @@ static long vnc_client_write_plain(VncState *vs) >> * the client socket. Will delegate actual work according to whether >> * SASL SSF layers are enabled (thus requiring encryption calls) >> */ >> -void vnc_client_write(void *opaque) >> +static void vnc_client_write_locked(void *opaque) >> { >> VncState *vs = opaque; >> >> @@ -1122,6 +1205,19 @@ void vnc_client_write(void *opaque) >> vnc_client_write_plain(vs); >> } >> >> +void vnc_client_write(void *opaque) >> +{ >> + VncState *vs = opaque; >> + >> + vnc_lock_output(vs); >> + if (vs->output.offset) { >> + vnc_client_write_locked(opaque); >> + } else { >> + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); >> + } >> + vnc_unlock_output(vs); >> +} >> + >> void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) >> { >> vs->read_handler = func; >> @@ -1232,6 +1328,7 @@ void vnc_write(VncState *vs, const void *data, size_t >> len) >> { >> buffer_reserve(&vs->output, len); >> >> + > > Eeh. > >> if (vs->csock != -1 && buffer_empty(&vs->output)) { >> qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, >> vnc_client_write, vs); >> } >> @@ -1273,8 +1370,10 @@ void vnc_write_u8(VncState *vs, uint8_t value) >> >> void vnc_flush(VncState *vs) >> { >> + vnc_lock_output(vs); >> if (vs->csock != -1 && vs->output.offset) >> - vnc_client_write(vs); >> + vnc_client_write_locked(vs); > > Please change the brackets while you're at it. Some genius thought it'd be a > good idea to define a "new" coding style that's different from all the > current qemu code. But consistency is better than none, so we need to stick > with it now. > >> + vnc_unlock_output(vs); >> } >> >> uint8_t read_u8(uint8_t *data, size_t offset) >> @@ -1309,12 +1408,14 @@ static void check_pointer_type_change(Notifier >> *notifier) >> int absolute = kbd_mouse_is_absolute(); >> >> if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute >> != absolute) { >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> vnc_write_u8(vs, 0); >> vnc_write_u16(vs, 1); >> vnc_framebuffer_update(vs, absolute, 0, >> ds_get_width(vs->ds), ds_get_height(vs->ds), >> VNC_ENCODING_POINTER_TYPE_CHANGE); > > Man I really think those framebuffer update message headers should be folded > into the respective update function. Yep >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } >> vs->absolute = absolute; >> @@ -1618,21 +1719,25 @@ static void framebuffer_update_request(VncState *vs, >> int incremental, >> >> static void send_ext_key_event_ack(VncState *vs) >> { >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> vnc_write_u8(vs, 0); >> vnc_write_u16(vs, 1); >> vnc_framebuffer_update(vs, 0, 0, ds_get_width(vs->ds), >> ds_get_height(vs->ds), >> VNC_ENCODING_EXT_KEY_EVENT); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } >> >> static void send_ext_audio_ack(VncState *vs) >> { >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> vnc_write_u8(vs, 0); >> vnc_write_u16(vs, 1); >> vnc_framebuffer_update(vs, 0, 0, ds_get_width(vs->ds), >> ds_get_height(vs->ds), >> VNC_ENCODING_AUDIO); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } >> >> @@ -1791,12 +1896,14 @@ static void vnc_colordepth(VncState *vs) >> { >> if (vnc_has_feature(vs, VNC_FEATURE_WMVI)) { >> /* Sending a WMVi message to notify the client*/ >> + vnc_lock_output(vs); >> vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); >> vnc_write_u8(vs, 0); >> vnc_write_u16(vs, 1); /* number of rects */ >> vnc_framebuffer_update(vs, 0, 0, ds_get_width(vs->ds), >> ds_get_height(vs->ds), VNC_ENCODING_WMVi); >> pixel_format_message(vs); >> + vnc_unlock_output(vs); >> vnc_flush(vs); >> } else { >> set_pixel_conversion(vs); >> @@ -2224,12 +2331,21 @@ static void vnc_refresh(void *opaque) >> >> vga_hw_update(); >> >> + if (vnc_trylock_display(vd)) { >> + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; >> + qemu_mod_timer(vd->timer, qemu_get_clock(rt_clock) + >> + vd->timer_interval); >> + return; >> + } >> + >> has_dirty = vnc_refresh_server_surface(vd); >> + vnc_unlock_display(vd); >> >> QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { >> rects += vnc_update_client(vs, has_dirty); >> /* vs might be free()ed here */ >> } >> + > > ... > >> /* vd->timer could be NULL now if the last client disconnected, >> * in this case don't update the timer */ >> if (vd->timer == NULL) >> @@ -2288,6 +2404,10 @@ static void vnc_connect(VncDisplay *vd, int csock) >> vs->as.fmt = AUD_FMT_S16; >> vs->as.endianness = 0; >> >> +#ifdef CONFIG_VNC_THREAD >> + qemu_mutex_init(&vs->output_mutex); >> +#endif >> + >> QTAILQ_INSERT_HEAD(&vd->clients, vs, next); >> >> vga_hw_update(); >> @@ -2345,6 +2465,11 @@ void vnc_display_init(DisplayState *ds) >> if (!vs->kbd_layout) >> exit(1); >> >> +#ifdef CONFIG_VNC_THREAD >> + qemu_mutex_init(&vs->mutex); >> + vnc_start_worker_thread(); >> +#endif >> + >> dcl->dpy_copy = vnc_dpy_copy; >> dcl->dpy_update = vnc_dpy_update; >> dcl->dpy_resize = vnc_dpy_resize; >> diff --git a/ui/vnc.h b/ui/vnc.h >> index cca1946..9405e61 100644 >> --- a/ui/vnc.h >> +++ b/ui/vnc.h >> @@ -29,6 +29,9 @@ >> >> #include "qemu-common.h" >> #include "qemu-queue.h" >> +#ifdef CONFIG_VNC_THREAD >> +#include "qemu-thread.h" >> +#endif >> #include "console.h" >> #include "monitor.h" >> #include "audio/audio.h" >> @@ -59,6 +62,9 @@ typedef struct Buffer >> } Buffer; >> >> typedef struct VncState VncState; >> +typedef struct VncJob VncJob; >> +typedef struct VncRect VncRect; >> +typedef struct VncRectEntry VncRectEntry; >> >> typedef int VncReadEvent(VncState *vs, uint8_t *data, size_t len); >> >> @@ -101,6 +107,9 @@ struct VncDisplay >> DisplayState *ds; >> kbd_layout_t *kbd_layout; >> int lock_key_sync; >> +#ifdef CONFIG_VNC_THREAD >> + QemuMutex mutex; >> +#endif >> >> QEMUCursor *cursor; >> int cursor_msize; >> @@ -122,6 +131,38 @@ struct VncDisplay >> #endif >> }; >> >> + >> +#ifdef CONFIG_VNC_THREAD >> +struct VncRect >> +{ >> + int x; >> + int y; >> + int w; >> + int h; >> +}; >> + >> +struct VncRectEntry >> +{ >> + struct VncRect rect; >> + QLIST_ENTRY(VncRectEntry) next; >> +}; >> + >> +struct VncJob >> +{ >> + VncState *vs; >> + >> + QLIST_HEAD(, VncRectEntry) rectangles; >> + QTAILQ_ENTRY(VncJob) next; >> +}; >> +#else >> +struct VncJob >> +{ >> + VncState *vs; >> + int rectangles; >> + size_t saved_offset; >> +}; >> +#endif >> + >> struct VncState >> { >> int csock; >> @@ -170,6 +211,12 @@ struct VncState >> QEMUPutLEDEntry *led; >> >> /* Encoding specific */ >> + bool abording; >> +#ifndef CONFIG_VNC_THREAD > > Please stay on your positive attitude :) > >> + VncJob job; >> +#else >> + QemuMutex output_mutex; >> +#endif >> >> /* Tight */ >> uint8_t tight_quality; >> @@ -412,6 +459,8 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, >> int w, int h, >> void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v); >> >> /* Encodings */ >> +int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); >> + >> int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int >> h); >> >> int vnc_hextile_send_framebuffer_update(VncState *vs, int x, >> @@ -427,4 +476,30 @@ void vnc_zlib_clear(VncState *vs); >> int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, int w, int >> h); >> void vnc_tight_clear(VncState *vs); >> >> +/* Jobs */ >> +#ifdef CONFIG_VNC_THREAD >> + >> +VncJob *vnc_job_new(VncState *vs); >> +int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h); >> +void vnc_job_push(VncJob *job); >> +bool vnc_has_job(VncState *vs); >> +void vnc_jobs_clear(VncState *vs); >> +void vnc_jobs_join(VncState *vs); >> +void vnc_start_worker_thread(void); >> +bool vnc_worker_thread_running(void); >> +void vnc_stop_worker_thread(void); >> + >> +#else >> + >> +#define vnc_jobs_clear(vs) (void) (vs); >> +#define vnc_jobs_join(vs) (void) (vs); > > static inline functions please. > >> + >> +VncJob *vnc_job_new(VncState *vs); >> +bool vnc_has_job(VncState *vs); >> +int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h); >> +bool vnc_worker_thread_running(void); >> +void vnc_job_push(VncJob *job); >> + >> +#endif /* CONFIG_VNC_THREAD */ >> + >> #endif /* __QEMU_VNC_H */ >> -- >> 1.7.1 >> > > -- Corentin Chary http://xf.iksaif.net