Re: [gtk-vnc-devel] [RFC] GThread based coroutines



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]