Re: [gtk-vnc-devel] [RFC] GThread based coroutines
- From: "Daniel P. Berrange" <berrange redhat com>
- To: Anthony Liguori <anthony codemonkey ws>
- Cc: gtk-vnc-devel List <gtk-vnc-devel lists sourceforge net>
- Subject: Re: [gtk-vnc-devel] [RFC] GThread based coroutines
- Date: Thu, 20 Dec 2007 20:17:54 +0000
On Tue, Dec 18, 2007 at 10:51:05PM -0500, Anthony Liguori wrote:
> Hi,
>
> Attached a thread-based coroutine implementation. This should make the
> Windows port much easier. I've tested it with gvncviewer and vinagre
> (with multiple tabs) so I think it works pretty well. The basic idea is
> to use a GCond and GMutex to run all threads in lock-step. It was
> tricky getting the ucontext coroutines working properly so
> testing/review is greatly appreciated!
>
> Also, I'm hoping someone who has a bit more autoconf foo can help me
> with the proper autoconf/automake integration. We need to detect
> ucontext in configure and if it's available, compile in
> ucontext_coroutine.[ch], continuation.[ch]. Otherwise, we need to add a
> dependency to gthread-2.0 and compile in gthread_coroutine.[ch].
Here's an updated patch with autoconf magic. There wre a couple of things
I needed to deal with:
When we do 'make install' we also install the coroutine headers. This
doesn't work so well because your patch has #ifdef in the coroutine.h
to then #include either the gthread or ucontext header, and this #ifdef
depends on a variable set from configure.ac
I observed that the structs required for ucontext vs gthread were more or
less the same except for the GThread * vs struct continuation. So I unified
them into a generic 'void *context'. In the implementation this context
points to a GThread * or a 'struct continuation *' as needed. The latter
means the 'container_of' trick doesn't work, so I simply added 'void *owner'
to the 'struct continuation'.
The GThread patch doesn't go correct (well any) GThread cleanup when the
coroutine exits - we need to join & also free the GThread * object. I
didn't try to address this.
configure now has '--with-coroutine=ucontext|gthread', defaulting to
'ucontext' and automatically switching to 'gthread' if it can't find
the getcontext/makecontext/swapcontext function calls.
a/src/coroutine.c | 121 ----------------------------------------
b/src/coroutine_gthread.c | 129 +++++++++++++++++++++++++++++++++++++++++++
b/src/coroutine_ucontext.c | 133 +++++++++++++++++++++++++++++++++++++++++++++
configure.ac | 35 +++++++++++
src/Makefile.am | 20 +++++-
src/continuation.h | 1
src/coroutine.h | 8 +-
7 files changed, 317 insertions(+), 130 deletions(-)
Regards,
Dan.
--
|=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=|
|=- Perl modules: http://search.cpan.org/~danberr/ -=|
|=- Projects: http://freshmeat.net/~danielpb/ -=|
|=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|
diff -r 71c9bd03a6c7 configure.ac
--- a/configure.ac Tue Dec 18 21:34:09 2007 -0500
+++ b/configure.ac Thu Dec 20 15:10:27 2007 -0500
@@ -9,6 +9,7 @@ AC_SUBST(GNUTLS_REQUIRED)
AC_SUBST(GNUTLS_REQUIRED)
PYGTK_REQUIRED=2.0.0
+GTHREAD_REQUIRED=2.0.0
PYTHON_REQUIRED=2.4
AC_CONFIG_HEADERS([config.h:config.hin])
@@ -19,6 +20,7 @@ AM_INIT_AUTOMAKE(gtk-vnc, 0.3.1)
AM_INIT_AUTOMAKE(gtk-vnc, 0.3.1)
AC_PROG_CC_STDC
+AM_PROG_CC_C_O
AC_PROG_LIBTOOL
AC_ARG_WITH(python,
@@ -85,6 +87,39 @@ AC_SUBST(GNUTLS_CFLAGS)
AC_SUBST(GNUTLS_CFLAGS)
AC_SUBST(GNUTLS_LIBS)
+GTHREAD_CFLAGS=
+GTHREAD_LIBS=
+
+WITH_UCONTEXT=1
+
+AC_ARG_WITH(coroutine,
+[ --with-coroutine=ucontext/gthread use ucontext or GThread for coroutines],
+[],[with_coroutine=ucontext])
+
+case $with_coroutine in
+ ucontext)
+ ;;
+ gthread)
+ ;;
+ *)
+ AC_ERROR([Unsupported coroutine type])
+esac
+
+if test "$with_coroutine" = "ucontext"; then
+ AC_CHECK_FUNC(makecontext, [],[with_coroutine=gthread])
+ AC_CHECK_FUNC(swapcontext, [],[with_coroutine=gthread])
+ AC_CHECK_FUNC(getcontext, [],[with_coroutine=gthread])
+fi
+
+if test "$with_coroutine" = "gthread"; then
+ PKG_CHECK_MODULES(GTHREAD, gthread-2.0 > $GTHREAD_REQUIRED)
+ WITH_UCONTEXT=0
+fi
+AC_SUBST(GTHREAD_CFLAGS)
+AC_SUBST(GTHREAD_LIBS)
+AC_DEFINE_UNQUOTED(WITH_UCONTEXT,[$WITH_UCONTEXT], [Whether to use ucontext coroutine impl])
+AM_CONDITIONAL(WITH_UCONTEXT, [test "$WITH_UCONTEXT" != "0"])
+
if test "$WITH_PYTHON" = "yes"; then
PKG_CHECK_MODULES(PYGTK, pygtk-2.0 >= $PYGTK_REQUIRED)
AC_SUBST(PYGTK_CFLAGS)
diff -r 71c9bd03a6c7 src/Makefile.am
--- a/src/Makefile.am Tue Dec 18 21:34:09 2007 -0500
+++ b/src/Makefile.am Thu Dec 20 15:10:27 2007 -0500
@@ -3,24 +3,34 @@ EXTRA_DIST = libgtk-vnc_sym.version vncm
lib_LTLIBRARIES = libgtk-vnc-1.0.la
-libgtk_vnc_1_0_la_LIBADD = @GTK_LIBS@ @GNUTLS_LIBS@
-libgtk_vnc_1_0_la_CFLAGS = @GTK_CFLAGS@ @GNUTLS_CFLAGS@ @WARNING_CFLAGS@ \
+libgtk_vnc_1_0_la_LIBADD = @GTK_LIBS@ @GNUTLS_LIBS@ @GTHREAD_LIBS@
+libgtk_vnc_1_0_la_CFLAGS = @GTK_CFLAGS@ @GNUTLS_CFLAGS@ @GTHREAD_CFLAGS@ \
+ @WARNING_CFLAGS@ \
-DSYSCONFDIR=\""$(sysconfdir)"\" \
@DEBUG_CFLAGS@
libgtk_vnc_1_0_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libgtk-vnc_sym.version \
-version-info 0:1:0
gtk_vnc_includedir = $(includedir)/gtk-vnc-1.0/
-gtk_vnc_include_HEADERS = vncdisplay.h gvnc.h coroutine.h continuation.h
+gtk_vnc_include_HEADERS = vncdisplay.h gvnc.h coroutine.h
libgtk_vnc_1_0_la_SOURCES = blt.h blt1.h \
- continuation.h continuation.c \
- coroutine.h coroutine.c \
+ coroutine.h \
d3des.h d3des.c \
gvnc.h gvnc.c \
vncdisplay.h vncdisplay.c \
vncmarshal.h vncmarshal.c \
utils.h
+
+if WITH_UCONTEXT
+libgtk_vnc_1_0_la_SOURCES += continuation.h continuation.c \
+ coroutine_ucontext.c
+EXTRA_DIST += coroutine_gthread.c
+else
+libgtk_vnc_1_0_la_SOURCES += coroutine_gthread.c
+EXTRA_DIST += continuation.h continuation.c \
+ coroutine_ucontext.c
+endif
vncmarshal.c: vncmarshal.txt
glib-genmarshal --body $< > $@ || (rm -f $@ && exit 1)
diff -r 71c9bd03a6c7 src/continuation.h
--- a/src/continuation.h Tue Dec 18 21:34:09 2007 -0500
+++ b/src/continuation.h Thu Dec 20 15:10:27 2007 -0500
@@ -19,6 +19,7 @@ struct continuation
size_t stack_size;
void (*entry)(struct continuation *cc);
int (*release)(struct continuation *cc);
+ void *owner;
/* private */
ucontext_t uc;
diff -r 71c9bd03a6c7 src/coroutine.c
--- a/src/coroutine.c Tue Dec 18 21:34:09 2007 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2006 Anthony Liguori <anthony codemonkey ws>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * GTK VNC Widget
- */
-
-#include <sys/types.h>
-#include <sys/mman.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include "coroutine.h"
-
-int coroutine_release(struct coroutine *co)
-{
- return cc_release(&co->cc);
-}
-
-static int _coroutine_release(struct continuation *cc)
-{
- struct coroutine *co = container_of(cc, struct coroutine, cc);
-
- if (co->release) {
- int ret = co->release(co);
- if (ret < 0)
- return ret;
- }
-
- co->caller = NULL;
-
- return munmap(cc->stack, cc->stack_size);
-}
-
-static void coroutine_trampoline(struct continuation *cc)
-{
- struct coroutine *co = container_of(cc, struct coroutine, cc);
- co->data = co->entry(co->data);
-}
-
-int coroutine_init(struct coroutine *co)
-{
- if (co->stack_size == 0)
- co->stack_size = 16 << 20;
-
- co->cc.stack_size = co->stack_size;
- co->cc.stack = mmap(0, co->stack_size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- -1, 0);
- if (co->cc.stack == MAP_FAILED)
- return -1;
- co->cc.entry = coroutine_trampoline;
- co->cc.release = _coroutine_release;
- co->exited = 0;
-
- return cc_init(&co->cc);
-}
-
-#if 0
-static __thread struct coroutine leader;
-static __thread struct coroutine *current;
-#else
-static struct coroutine leader;
-static struct coroutine *current;
-#endif
-
-struct coroutine *coroutine_self(void)
-{
- if (current == NULL)
- current = &leader;
- return current;
-}
-
-void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
-{
- int ret;
- to->data = arg;
- current = to;
- ret = cc_swap(&from->cc, &to->cc);
- if (ret == 0)
- return from->data;
- else if (ret == 1) {
- coroutine_release(to);
- current = &leader;
- to->exited = 1;
- return to->data;
- }
-
- return NULL;
-}
-
-void *coroutine_yieldto(struct coroutine *to, void *arg)
-{
- if (to->caller) {
- fprintf(stderr, "Co-routine is re-entering itself\n");
- abort();
- }
- to->caller = coroutine_self();
- return coroutine_swap(coroutine_self(), to, arg);
-}
-
-void *coroutine_yield(void *arg)
-{
- struct coroutine *to = coroutine_self()->caller;
- if (!to) {
- fprintf(stderr, "Co-routine is yielding to no one\n");
- abort();
- }
- coroutine_self()->caller = NULL;
- return coroutine_swap(coroutine_self(), to, arg);
-}
-/*
- * Local variables:
- * c-indent-level: 8
- * c-basic-offset: 8
- * tab-width: 8
- * End:
- */
diff -r 71c9bd03a6c7 src/coroutine.h
--- a/src/coroutine.h Tue Dec 18 21:34:09 2007 -0500
+++ b/src/coroutine.h Thu Dec 20 15:10:27 2007 -0500
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 Anthony Liguori <anthony codemonkey ws>
+ * Copyright (C) 2007 Anthony Liguori <anthony codemonkey ws>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2 as
@@ -11,7 +11,7 @@
#ifndef _COROUTINE_H_
#define _COROUTINE_H_
-#include "continuation.h"
+#include <glib.h>
struct coroutine
{
@@ -23,10 +23,10 @@ struct coroutine
int exited;
/* private */
+ void *context;
+ gboolean runnable;
struct coroutine *caller;
void *data;
-
- struct continuation cc;
};
int coroutine_init(struct coroutine *co);
diff -r 71c9bd03a6c7 src/coroutine_gthread.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/coroutine_gthread.c Thu Dec 20 15:10:27 2007 -0500
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2007 Anthony Liguori <anthony codemonkey ws>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * GTK VNC Widget
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "coroutine.h"
+
+static GCond *run_cond;
+static GMutex *run_lock;
+static struct coroutine *current;
+static struct coroutine leader;
+
+static void coroutine_system_init(void)
+{
+ if (!g_thread_supported())
+ g_thread_init(NULL);
+
+ run_cond = g_cond_new();
+ run_lock = g_mutex_new();
+
+ /* The thread that creates the first coroutine is the system coroutine
+ * so let's fill out a structure for it */
+ leader.entry = NULL;
+ leader.release = NULL;
+ leader.stack_size = 0;
+ leader.exited = 0;
+ leader.context = g_thread_self();
+ leader.runnable = TRUE; /* we're the one running right now */
+ leader.caller = NULL;
+ leader.data = NULL;
+
+ current = &leader;
+}
+
+static gpointer coroutine_thread(gpointer opaque)
+{
+ struct coroutine *co = opaque;
+
+ g_mutex_lock(run_lock);
+ while (!co->runnable)
+ g_cond_wait(run_cond, run_lock);
+
+ current = co;
+ co->data = co->entry(co->data);
+ co->exited = 1;
+
+ co->caller->runnable = TRUE;
+ g_cond_broadcast(run_cond);
+ g_mutex_unlock(run_lock);
+
+ return NULL;
+}
+
+int coroutine_init(struct coroutine *co)
+{
+ if (run_cond == NULL)
+ coroutine_system_init();
+
+ co->context = g_thread_create_full(coroutine_thread, co, co->stack_size,
+ FALSE, TRUE,
+ G_THREAD_PRIORITY_NORMAL,
+ NULL);
+ if (co->context == NULL)
+ return -1;
+
+ co->exited = 0;
+ co->runnable = FALSE;
+ co->caller = NULL;
+
+ return 0;
+}
+
+int coroutine_release(struct coroutine *co G_GNUC_UNUSED)
+{
+ /* should we join the thread? */
+ return 0;
+}
+
+void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
+{
+ from->runnable = FALSE;
+ to->runnable = TRUE;
+ to->data = arg;
+ to->caller = from;
+ g_cond_broadcast(run_cond);
+ g_mutex_unlock(run_lock);
+
+ g_mutex_lock(run_lock);
+ while (!from->runnable)
+ g_cond_wait(run_cond, run_lock);
+
+ current = from;
+
+ return from->data;
+}
+
+struct coroutine *coroutine_self(void)
+{
+ return current;
+}
+
+void *coroutine_yieldto(struct coroutine *to, void *arg)
+{
+ if (to->caller) {
+ fprintf(stderr, "Co-routine is re-entering itself\n");
+ abort();
+ }
+ to->caller = coroutine_self();
+ return coroutine_swap(coroutine_self(), to, arg);
+}
+
+void *coroutine_yield(void *arg)
+{
+ struct coroutine *to = coroutine_self()->caller;
+ if (!to) {
+ fprintf(stderr, "Co-routine is yielding to no one\n");
+ abort();
+ }
+ coroutine_self()->caller = NULL;
+ return coroutine_swap(coroutine_self(), to, arg);
+}
+
diff -r 71c9bd03a6c7 src/coroutine_ucontext.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/coroutine_ucontext.c Thu Dec 20 15:10:27 2007 -0500
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2006 Anthony Liguori <anthony codemonkey ws>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * GTK VNC Widget
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include "coroutine.h"
+#include "continuation.h"
+
+int coroutine_release(struct coroutine *co)
+{
+ return cc_release(co->context);
+}
+
+static int _coroutine_release(struct continuation *cc)
+{
+ struct coroutine *co = cc->owner;
+
+ if (co->release) {
+ int ret = co->release(co);
+ if (ret < 0)
+ return ret;
+ }
+
+ co->caller = NULL;
+
+ return munmap(cc->stack, cc->stack_size);
+}
+
+static void coroutine_trampoline(struct continuation *cc)
+{
+ struct coroutine *co = cc->owner;
+ co->data = co->entry(co->data);
+}
+
+int coroutine_init(struct coroutine *co)
+{
+ struct continuation *cc = calloc(1, sizeof(struct continuation));
+ if (!cc)
+ return -1;
+
+ if (co->stack_size == 0)
+ co->stack_size = 16 << 20;
+
+ cc->stack_size = co->stack_size;
+ cc->stack = mmap(0, co->stack_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1, 0);
+ if (cc->stack == MAP_FAILED) {
+ free(cc);
+ return -1;
+ }
+
+ cc->entry = coroutine_trampoline;
+ cc->release = _coroutine_release;
+ cc->owner = co;
+
+ co->context = cc;
+ co->exited = 0;
+
+ return cc_init(cc);
+}
+
+#if 0
+static __thread struct coroutine leader;
+static __thread struct coroutine *current;
+#else
+static struct continuation leadercc;
+static struct coroutine leader = { .context = &leadercc };
+static struct coroutine *current;
+#endif
+
+struct coroutine *coroutine_self(void)
+{
+ if (current == NULL)
+ current = &leader;
+ return current;
+}
+
+void *coroutine_swap(struct coroutine *from, struct coroutine *to, void *arg)
+{
+ int ret;
+ to->data = arg;
+ current = to;
+ ret = cc_swap(from->context, to->context);
+ if (ret == 0)
+ return from->data;
+ else if (ret == 1) {
+ coroutine_release(to);
+ current = &leader;
+ to->exited = 1;
+ return to->data;
+ }
+
+ return NULL;
+}
+
+void *coroutine_yieldto(struct coroutine *to, void *arg)
+{
+ if (to->caller) {
+ fprintf(stderr, "Co-routine is re-entering itself\n");
+ abort();
+ }
+ to->caller = coroutine_self();
+ return coroutine_swap(coroutine_self(), to, arg);
+}
+
+void *coroutine_yield(void *arg)
+{
+ struct coroutine *to = coroutine_self()->caller;
+ if (!to) {
+ fprintf(stderr, "Co-routine is yielding to no one\n");
+ abort();
+ }
+ coroutine_self()->caller = NULL;
+ return coroutine_swap(coroutine_self(), to, arg);
+}
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * tab-width: 8
+ * End:
+ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]