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. > + 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? > + 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. > + 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; > +} > + > +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. > +#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. > +#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? > } > > 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. > + 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 >