Patchwork [v7,2/4] coroutine: implement coroutines using gthread

login
register
mail settings
Submitter Stefan Hajnoczi
Date July 25, 2011, 8:04 p.m.
Message ID <1311624250-1670-3-git-send-email-stefanha@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/106745/
State New
Headers show

Comments

Stefan Hajnoczi - July 25, 2011, 8:04 p.m.
From: "Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>

On platforms that don't support makecontext(3) use gthread based
coroutine implementation.

Darwin has makecontext(3) but getcontext(3) is stubbed out to return
ENOTSUP.  Andreas Färber <andreas.faerber@web.de> debugged this and
contributed the ./configure test which solves the issue for Darwin/ppc64
(and ppc) v10.5.

[Original patch by Aneesh, made consistent with coroutine-ucontext.c and
switched to GStaticPrivate by Stefan.  Tested on Linux and OpenBSD.]

Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
---
 Makefile.objs       |    4 ++
 configure           |   18 +++++++
 coroutine-gthread.c |  131 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 153 insertions(+), 0 deletions(-)
 create mode 100644 coroutine-gthread.c
Anthony Liguori - July 25, 2011, 8:38 p.m.
On 07/25/2011 03:04 PM, Stefan Hajnoczi wrote:
> From: "Aneesh Kumar K.V"<aneesh.kumar@linux.vnet.ibm.com>
>
> On platforms that don't support makecontext(3) use gthread based
> coroutine implementation.
>
> Darwin has makecontext(3) but getcontext(3) is stubbed out to return
> ENOTSUP.  Andreas Färber<andreas.faerber@web.de>  debugged this and
> contributed the ./configure test which solves the issue for Darwin/ppc64
> (and ppc) v10.5.
>
> [Original patch by Aneesh, made consistent with coroutine-ucontext.c and
> switched to GStaticPrivate by Stefan.  Tested on Linux and OpenBSD.]
>
> Signed-off-by: Aneesh Kumar K.V<aneesh.kumar@linux.vnet.ibm.com>
> Signed-off-by: Stefan Hajnoczi<stefanha@linux.vnet.ibm.com>
> ---
>   Makefile.objs       |    4 ++
>   configure           |   18 +++++++
>   coroutine-gthread.c |  131 +++++++++++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 153 insertions(+), 0 deletions(-)
>   create mode 100644 coroutine-gthread.c
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 28e1762..5679e1f 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -13,7 +13,11 @@ oslib-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-thread-posix.o
>   #######################################################################
>   # coroutines
>   coroutine-obj-y = qemu-coroutine.o
> +ifeq ($(CONFIG_UCONTEXT_COROUTINE),y)
>   coroutine-obj-$(CONFIG_POSIX) += coroutine-ucontext.o
> +else
> +coroutine-obj-$(CONFIG_POSIX) += coroutine-gthread.o
> +endif
>   coroutine-obj-$(CONFIG_WIN32) += coroutine-win32.o
>
>   #######################################################################
> diff --git a/configure b/configure
> index 600da9b..1b6ad87 100755
> --- a/configure
> +++ b/configure
> @@ -2499,6 +2499,20 @@ if test "$trace_backend" = "dtrace"; then
>   fi
>
>   ##########################################
> +# check if we have makecontext
> +
> +ucontext_coroutine=no
> +if test "$darwin" != "yes"; then
> +  cat>  $TMPC<<  EOF
> +#include<ucontext.h>
> +int main(void) { makecontext(0, 0, 0); }
> +EOF
> +  if compile_prog "" "" ; then
> +      ucontext_coroutine=yes
> +  fi
> +fi
> +

Doesn't this make the build non-bisectable on platforms that don't have 
makecontext?  I think the gthread patch needs to come first and then the 
ucontext version can be used on platforms that we detect it's there.

Regards,

Anthony Liguori

> +##########################################
>   # End of CC checks
>   # After here, no more $cc or $ld runs
>
> @@ -2970,6 +2984,10 @@ if test "$rbd" = "yes" ; then
>     echo "CONFIG_RBD=y">>  $config_host_mak
>   fi
>
> +if test "$ucontext_coroutine" = "yes" ; then
> +  echo "CONFIG_UCONTEXT_COROUTINE=y">>  $config_host_mak
> +fi
> +
>   # USB host support
>   case "$usb" in
>   linux)
> diff --git a/coroutine-gthread.c b/coroutine-gthread.c
> new file mode 100644
> index 0000000..f09877e
> --- /dev/null
> +++ b/coroutine-gthread.c
> @@ -0,0 +1,131 @@
> +/*
> + * GThread coroutine initialization code
> + *
> + * Copyright (C) 2006  Anthony Liguori<anthony@codemonkey.ws>
> + * Copyright (C) 2011  Aneesh Kumar K.V<aneesh.kumar@linux.vnet.ibm.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.0 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see<http://www.gnu.org/licenses/>.
> + */
> +
> +#include<glib.h>
> +#include "qemu-common.h"
> +#include "qemu-coroutine-int.h"
> +
> +typedef struct {
> +    Coroutine base;
> +    GThread *thread;
> +    bool runnable;
> +    CoroutineAction action;
> +} CoroutineGThread;
> +
> +static GCond *coroutine_cond;
> +static GStaticMutex coroutine_lock = G_STATIC_MUTEX_INIT;
> +static GStaticPrivate coroutine_key = G_STATIC_PRIVATE_INIT;
> +
> +static void __attribute__((constructor)) coroutine_init(void)
> +{
> +    if (!g_thread_supported()) {
> +        g_thread_init(NULL);
> +    }
> +
> +    coroutine_cond = g_cond_new();
> +}
> +
> +static void coroutine_wait_runnable_locked(CoroutineGThread *co)
> +{
> +    while (!co->runnable) {
> +        g_cond_wait(coroutine_cond, g_static_mutex_get_mutex(&coroutine_lock));
> +    }
> +}
> +
> +static void coroutine_wait_runnable(CoroutineGThread *co)
> +{
> +    g_static_mutex_lock(&coroutine_lock);
> +    coroutine_wait_runnable_locked(co);
> +    g_static_mutex_unlock(&coroutine_lock);
> +}
> +
> +static gpointer coroutine_thread(gpointer opaque)
> +{
> +    CoroutineGThread *co = opaque;
> +
> +    g_static_private_set(&coroutine_key, co, NULL);
> +    coroutine_wait_runnable(co);
> +    co->base.entry(co->base.entry_arg);
> +    qemu_coroutine_switch(&co->base, co->base.caller, COROUTINE_TERMINATE);
> +    return NULL;
> +}
> +
> +Coroutine *qemu_coroutine_new(void)
> +{
> +    CoroutineGThread *co;
> +
> +    co = qemu_mallocz(sizeof(*co));
> +    co->thread = g_thread_create_full(coroutine_thread, co, 0, TRUE, TRUE,
> +                                      G_THREAD_PRIORITY_NORMAL, NULL);
> +    if (!co->thread) {
> +        qemu_free(co);
> +        return NULL;
> +    }
> +    return&co->base;
> +}
> +
> +void qemu_coroutine_delete(Coroutine *co_)
> +{
> +    CoroutineGThread *co = DO_UPCAST(CoroutineGThread, base, co_);
> +
> +    g_thread_join(co->thread);
> +    qemu_free(co);
> +}
> +
> +CoroutineAction qemu_coroutine_switch(Coroutine *from_,
> +                                      Coroutine *to_,
> +                                      CoroutineAction action)
> +{
> +    CoroutineGThread *from = DO_UPCAST(CoroutineGThread, base, from_);
> +    CoroutineGThread *to = DO_UPCAST(CoroutineGThread, base, to_);
> +
> +    g_static_mutex_lock(&coroutine_lock);
> +    from->runnable = false;
> +    from->action = action;
> +    to->runnable = true;
> +    to->action = action;
> +    g_cond_broadcast(coroutine_cond);
> +
> +    if (action != COROUTINE_TERMINATE) {
> +        coroutine_wait_runnable_locked(from);
> +    }
> +    g_static_mutex_unlock(&coroutine_lock);
> +    return from->action;
> +}
> +
> +Coroutine *qemu_coroutine_self(void)
> +{
> +    CoroutineGThread *co = g_static_private_get(&coroutine_key);
> +
> +    if (!co) {
> +        co = qemu_mallocz(sizeof(*co));
> +        co->runnable = true;
> +        g_static_private_set(&coroutine_key, co, (GDestroyNotify)qemu_free);
> +    }
> +
> +    return&co->base;
> +}
> +
> +bool qemu_in_coroutine(void)
> +{
> +    CoroutineGThread *co = g_static_private_get(&coroutine_key);
> +
> +    return co&&  co->base.caller;
> +}
Stefan Hajnoczi - July 26, 2011, 9:22 a.m.
On Mon, Jul 25, 2011 at 9:38 PM, Anthony Liguori <anthony@codemonkey.ws> wrote:
> On 07/25/2011 03:04 PM, Stefan Hajnoczi wrote:
>>
>> From: "Aneesh Kumar K.V"<aneesh.kumar@linux.vnet.ibm.com>
>>
>> On platforms that don't support makecontext(3) use gthread based
>> coroutine implementation.
>>
>> Darwin has makecontext(3) but getcontext(3) is stubbed out to return
>> ENOTSUP.  Andreas Färber<andreas.faerber@web.de>  debugged this and
>> contributed the ./configure test which solves the issue for Darwin/ppc64
>> (and ppc) v10.5.
>>
>> [Original patch by Aneesh, made consistent with coroutine-ucontext.c and
>> switched to GStaticPrivate by Stefan.  Tested on Linux and OpenBSD.]
>>
>> Signed-off-by: Aneesh Kumar K.V<aneesh.kumar@linux.vnet.ibm.com>
>> Signed-off-by: Stefan Hajnoczi<stefanha@linux.vnet.ibm.com>
>> ---
>>  Makefile.objs       |    4 ++
>>  configure           |   18 +++++++
>>  coroutine-gthread.c |  131
>> +++++++++++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 153 insertions(+), 0 deletions(-)
>>  create mode 100644 coroutine-gthread.c
>>
>> diff --git a/Makefile.objs b/Makefile.objs
>> index 28e1762..5679e1f 100644
>> --- a/Makefile.objs
>> +++ b/Makefile.objs
>> @@ -13,7 +13,11 @@ oslib-obj-$(CONFIG_POSIX) += oslib-posix.o
>> qemu-thread-posix.o
>>  #######################################################################
>>  # coroutines
>>  coroutine-obj-y = qemu-coroutine.o
>> +ifeq ($(CONFIG_UCONTEXT_COROUTINE),y)
>>  coroutine-obj-$(CONFIG_POSIX) += coroutine-ucontext.o
>> +else
>> +coroutine-obj-$(CONFIG_POSIX) += coroutine-gthread.o
>> +endif
>>  coroutine-obj-$(CONFIG_WIN32) += coroutine-win32.o
>>
>>  #######################################################################
>> diff --git a/configure b/configure
>> index 600da9b..1b6ad87 100755
>> --- a/configure
>> +++ b/configure
>> @@ -2499,6 +2499,20 @@ if test "$trace_backend" = "dtrace"; then
>>  fi
>>
>>  ##########################################
>> +# check if we have makecontext
>> +
>> +ucontext_coroutine=no
>> +if test "$darwin" != "yes"; then
>> +  cat>  $TMPC<<  EOF
>> +#include<ucontext.h>
>> +int main(void) { makecontext(0, 0, 0); }
>> +EOF
>> +  if compile_prog "" "" ; then
>> +      ucontext_coroutine=yes
>> +  fi
>> +fi
>> +
>
> Doesn't this make the build non-bisectable on platforms that don't have
> makecontext?  I think the gthread patch needs to come first and then the
> ucontext version can be used on platforms that we detect it's there.

You are correct.  I have sent a new version that first introduces
gthread, and then ucontext and fibers.

Stefan

Patch

diff --git a/Makefile.objs b/Makefile.objs
index 28e1762..5679e1f 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -13,7 +13,11 @@  oslib-obj-$(CONFIG_POSIX) += oslib-posix.o qemu-thread-posix.o
 #######################################################################
 # coroutines
 coroutine-obj-y = qemu-coroutine.o
+ifeq ($(CONFIG_UCONTEXT_COROUTINE),y)
 coroutine-obj-$(CONFIG_POSIX) += coroutine-ucontext.o
+else
+coroutine-obj-$(CONFIG_POSIX) += coroutine-gthread.o
+endif
 coroutine-obj-$(CONFIG_WIN32) += coroutine-win32.o
 
 #######################################################################
diff --git a/configure b/configure
index 600da9b..1b6ad87 100755
--- a/configure
+++ b/configure
@@ -2499,6 +2499,20 @@  if test "$trace_backend" = "dtrace"; then
 fi
 
 ##########################################
+# check if we have makecontext
+
+ucontext_coroutine=no
+if test "$darwin" != "yes"; then
+  cat > $TMPC << EOF
+#include <ucontext.h>
+int main(void) { makecontext(0, 0, 0); }
+EOF
+  if compile_prog "" "" ; then
+      ucontext_coroutine=yes
+  fi
+fi
+
+##########################################
 # End of CC checks
 # After here, no more $cc or $ld runs
 
@@ -2970,6 +2984,10 @@  if test "$rbd" = "yes" ; then
   echo "CONFIG_RBD=y" >> $config_host_mak
 fi
 
+if test "$ucontext_coroutine" = "yes" ; then
+  echo "CONFIG_UCONTEXT_COROUTINE=y" >> $config_host_mak
+fi
+
 # USB host support
 case "$usb" in
 linux)
diff --git a/coroutine-gthread.c b/coroutine-gthread.c
new file mode 100644
index 0000000..f09877e
--- /dev/null
+++ b/coroutine-gthread.c
@@ -0,0 +1,131 @@ 
+/*
+ * GThread coroutine initialization code
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2011  Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include "qemu-common.h"
+#include "qemu-coroutine-int.h"
+
+typedef struct {
+    Coroutine base;
+    GThread *thread;
+    bool runnable;
+    CoroutineAction action;
+} CoroutineGThread;
+
+static GCond *coroutine_cond;
+static GStaticMutex coroutine_lock = G_STATIC_MUTEX_INIT;
+static GStaticPrivate coroutine_key = G_STATIC_PRIVATE_INIT;
+
+static void __attribute__((constructor)) coroutine_init(void)
+{
+    if (!g_thread_supported()) {
+        g_thread_init(NULL);
+    }
+
+    coroutine_cond = g_cond_new();
+}
+
+static void coroutine_wait_runnable_locked(CoroutineGThread *co)
+{
+    while (!co->runnable) {
+        g_cond_wait(coroutine_cond, g_static_mutex_get_mutex(&coroutine_lock));
+    }
+}
+
+static void coroutine_wait_runnable(CoroutineGThread *co)
+{
+    g_static_mutex_lock(&coroutine_lock);
+    coroutine_wait_runnable_locked(co);
+    g_static_mutex_unlock(&coroutine_lock);
+}
+
+static gpointer coroutine_thread(gpointer opaque)
+{
+    CoroutineGThread *co = opaque;
+
+    g_static_private_set(&coroutine_key, co, NULL);
+    coroutine_wait_runnable(co);
+    co->base.entry(co->base.entry_arg);
+    qemu_coroutine_switch(&co->base, co->base.caller, COROUTINE_TERMINATE);
+    return NULL;
+}
+
+Coroutine *qemu_coroutine_new(void)
+{
+    CoroutineGThread *co;
+
+    co = qemu_mallocz(sizeof(*co));
+    co->thread = g_thread_create_full(coroutine_thread, co, 0, TRUE, TRUE,
+                                      G_THREAD_PRIORITY_NORMAL, NULL);
+    if (!co->thread) {
+        qemu_free(co);
+        return NULL;
+    }
+    return &co->base;
+}
+
+void qemu_coroutine_delete(Coroutine *co_)
+{
+    CoroutineGThread *co = DO_UPCAST(CoroutineGThread, base, co_);
+
+    g_thread_join(co->thread);
+    qemu_free(co);
+}
+
+CoroutineAction qemu_coroutine_switch(Coroutine *from_,
+                                      Coroutine *to_,
+                                      CoroutineAction action)
+{
+    CoroutineGThread *from = DO_UPCAST(CoroutineGThread, base, from_);
+    CoroutineGThread *to = DO_UPCAST(CoroutineGThread, base, to_);
+
+    g_static_mutex_lock(&coroutine_lock);
+    from->runnable = false;
+    from->action = action;
+    to->runnable = true;
+    to->action = action;
+    g_cond_broadcast(coroutine_cond);
+
+    if (action != COROUTINE_TERMINATE) {
+        coroutine_wait_runnable_locked(from);
+    }
+    g_static_mutex_unlock(&coroutine_lock);
+    return from->action;
+}
+
+Coroutine *qemu_coroutine_self(void)
+{
+    CoroutineGThread *co = g_static_private_get(&coroutine_key);
+
+    if (!co) {
+        co = qemu_mallocz(sizeof(*co));
+        co->runnable = true;
+        g_static_private_set(&coroutine_key, co, (GDestroyNotify)qemu_free);
+    }
+
+    return &co->base;
+}
+
+bool qemu_in_coroutine(void)
+{
+    CoroutineGThread *co = g_static_private_get(&coroutine_key);
+
+    return co && co->base.caller;
+}