[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] Re: [PATCH v2 2/2] vnc: threaded VNC server
From: |
Corentin Chary |
Subject: |
[Qemu-devel] Re: [PATCH v2 2/2] vnc: threaded VNC server |
Date: |
Sat, 5 Jun 2010 10:03:14 +0200 |
On Fri, Jun 4, 2010 at 3:44 PM, Alexander Graf <address@hidden> 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 <address@hidden>
>> ---
>> 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 <address@hidden>
>> + * Copyright (C) 2006 Fabrice Bellard
>> + * Copyright (C) 2009 Red Hat, Inc
>> + * Copyright (C) 2010 Corentin Chary <address@hidden>
>> + *
>> + * 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 <address@hidden>
>> + * Copyright (C) 2006 Fabrice Bellard
>> + * Copyright (C) 2009 Red Hat, Inc
>> + * Copyright (C) 2010 Corentin Chary <address@hidden>
>> + *
>> + * 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
Re: [Qemu-devel] [PATCH v2 2/2] vnc: threaded VNC server, Avi Kivity, 2010/06/06