parlib: Add synchronization objects
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 5 Apr 2017 15:44:53 +0000 (11:44 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 3 May 2017 16:13:02 +0000 (12:13 -0400)
The way synchronization primitives worked before is that a 2LS could
override the default functions (e.g. mutex_lock()) if they don't want the
default scheduling policy (FIFO).

After thinking about it a bit, the 2LS-specific part is the scheduling
decision: given an object (e.g. mutex), which thread should run next?  The
rest of it: uthread_yield(), callbacks, etc, is the same for all 2LSs.  We
can see this to some extent already with how recursive mutexes aren't part
of the 2LS interface.  It will become more clear as we add timeouts to
mutexes and CVs; that work just need to be done at the uthread level.  The
2LS just picks the next uthread.

This work is part of the GCC generic threading interface.  One of the
things required from that is a static initializer for mutexes.  To deal
with that, we'll need some fields in a mutex that uthread.c can access (for
a once_ctl).  That's part of the reason why mutex allocation will be a
uthread job, and the 2LS just does the sync_obj.  That sync obj can be used
for all sorts of synchronization primitives - at least mutexes and CVs for
now, maybe more in the future.

I considered allowing static allocation (but not initialization) of 2LS
sync_objs.  If we want to do that in the future, we can do so if we set an
upper bound on the size of a 2LS sync obj, and use a uint8_t
sync_store[SOME_AMT] that all 2LSs cast to their object.  It's probably not
worth it at this point.

There's also a minor issue with whether or not get_next() and friends
returns a uthread or another object.  If we wanted to use this in event.c
(which we don't, for other reasons at the moment), we might want to get the
controller back, not the uthread.  It's a layer of indirection.  That would
require storing a pointer with the per-uthread part of the sync structure
(e.g. TAILQ_ENTRY).  Right now, the implied pointer is the uthread, which
we can easily access since the per-uthread part is embedded in the uthread
(or 2LS thread).  We could just store another pointer next to it, but so
far it's not worth it / necessary.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
user/parlib/event.c
user/parlib/include/parlib/uthread.h
user/parlib/mutex.c
user/parlib/thread0_sched.c
user/parlib/uthread.c
user/pthread/pthread.c
user/vmm/sched.c

index 3a6f7a7..daff94a 100644 (file)
@@ -604,6 +604,9 @@ struct uth_sleep_ctlr {
 
 /* Attaches to an event_queue (ev_udata), tracks the uthreads for this evq */
 struct evq_wakeup_ctlr {
+       /* If we ever use a sync_obj, that would replace waiters.  But also note
+        * that we want a pointer to something other than the uthread, and currently
+        * we also wake all threads - there's no scheduling decision. */
        struct wait_link_tailq          waiters;
        struct spin_pdr_lock            lock;
 };
@@ -755,7 +758,8 @@ static bool extract_evqs_msg(struct event_queue *evqs[], size_t nr_evqs,
 static void __uth_blockon_evq_cb(struct uthread *uth, void *arg)
 {
        struct uth_sleep_ctlr *uctlr = arg;
-       uthread_has_blocked(uth, UTH_EXT_BLK_EVENTQ);
+
+       uthread_has_blocked(uth, NULL, UTH_EXT_BLK_EVENTQ);
        cmb();  /* actually block before saying 'blocked' */
        uctlr->blocked = TRUE;  /* can be woken up now */
        wrmb(); /* write 'blocked' before read 'check_evqs' */
index e12f4ea..9672a19 100644 (file)
@@ -3,6 +3,7 @@
 #include <parlib/vcore.h>
 #include <parlib/signal.h>
 #include <ros/syscall.h>
+#include <sys/queue.h>
 
 __BEGIN_DECLS
 
@@ -31,6 +32,7 @@ struct uthread {
        int state;
        struct sigstate sigstate;
        int notif_disabled_depth;
+       TAILQ_ENTRY(uthread) sync_next;
        struct syscall *sysc;   /* syscall we're blocking on, if any */
        struct syscall local_sysc;      /* for when we don't want to use the stack */
        void (*yield_func)(struct uthread*, void*);
@@ -38,8 +40,28 @@ struct uthread {
        int err_no;
        char err_str[MAX_ERRSTR_LEN];
 };
+TAILQ_HEAD(uth_tailq, uthread);
+
 extern __thread struct uthread *current_uthread;
 
+
+/* This struct is undefined.  We use it instead of void * so we can get
+ * compiler warnings if someone passes the wrong pointer type.  Internally, 2LSs
+ * and the default implementation use another object type. */
+typedef struct __uth_sync_opaque * uth_sync_t;
+
+/* 2LS-independent synchronization code (e.g. uthread mutexes) uses these
+ * helpers to access 2LS-specific functions.
+ *
+ * Note the spinlock associated with the higher-level sync primitive is held for
+ * these (where applicable). */
+uth_sync_t __uth_sync_alloc(void);
+void __uth_sync_free(uth_sync_t sync);
+struct uthread *__uth_sync_get_next(uth_sync_t sync);
+bool __uth_sync_get_uth(uth_sync_t sync, struct uthread *uth);
+/* 2LSs that use default sync objs will call this in their has_blocked op. */
+void __uth_default_sync_enqueue(struct uthread *uth, uth_sync_t sync);
+
 /* These structs are undefined.  We use them instead of void * so we can get
  * compiler warnings if someone passes the wrong pointer type.  Internally, we
  * use another struct type for mtx and cvs. */
@@ -55,7 +77,7 @@ struct schedule_ops {
        void (*thread_runnable)(struct uthread *);
        void (*thread_paused)(struct uthread *);
        void (*thread_blockon_sysc)(struct uthread *, void *);
-       void (*thread_has_blocked)(struct uthread *, int);
+       void (*thread_has_blocked)(struct uthread *, uth_sync_t, int);
        void (*thread_refl_fault)(struct uthread *, struct user_context *);
        /**** Defining these functions is optional. ****/
        /* 2LSs can leave the mutex/cv funcs empty for a default implementation */
@@ -69,6 +91,10 @@ struct schedule_ops {
        void (*cond_var_wait)(uth_cond_var_t, uth_mutex_t);
        void (*cond_var_signal)(uth_cond_var_t);
        void (*cond_var_broadcast)(uth_cond_var_t);
+       uth_sync_t (*sync_alloc)(void);
+       void (*sync_free)(uth_sync_t);
+       struct uthread *(*sync_get_next)(uth_sync_t);
+       bool (*sync_get_uth)(uth_sync_t, struct uthread *);
        /* Functions event handling wants */
        void (*preempt_pending)(void);
 };
@@ -106,7 +132,7 @@ void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
 void uthread_sleep(unsigned int seconds);
 void uthread_usleep(unsigned int usecs);
 void __attribute__((noreturn)) uthread_sleep_forever(void);
-void uthread_has_blocked(struct uthread *uthread, int flags);
+void uthread_has_blocked(struct uthread *uthread, uth_sync_t sync, int flags);
 void uthread_paused(struct uthread *uthread);
 
 /* Utility functions */
index e955390..cbc4f57 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016 Google, Inc.
+/* Copyright (c) 2016-2017 Google, Inc.
  * Barret Rhoden <brho@cs.berkeley.edu>
  * See LICENSE for details. */
 
 #include <parlib/spinlock.h>
 #include <malloc.h>
 
-/* The linkage structs are for the yield callbacks */
-struct uth_default_mtx;
-struct uth_mtx_link {
-       TAILQ_ENTRY(uth_mtx_link)       next;
-       struct uth_default_mtx          *mtx;
-       struct uthread                          *uth;
-};
-TAILQ_HEAD(mtx_link_tq, uth_mtx_link);
-
 struct uth_default_mtx {
        struct spin_pdr_lock            lock;
-       struct mtx_link_tq                      waiters;
+       uth_sync_t                                      sync_obj;
        bool                                            locked;
 };
 
-struct uth_default_cv;
-struct uth_cv_link {
-       TAILQ_ENTRY(uth_cv_link)        next;
-       struct uth_default_cv           *cv;
-       struct uth_default_mtx          *mtx;
-       struct uthread                          *uth;
-};
-TAILQ_HEAD(cv_link_tq, uth_cv_link);
-
 struct uth_default_cv {
        struct spin_pdr_lock            lock;
-       struct cv_link_tq                       waiters;
+       uth_sync_t                                      sync_obj;
 };
 
 
@@ -50,21 +32,20 @@ static struct uth_default_mtx *uth_default_mtx_alloc(void)
        mtx = malloc(sizeof(struct uth_default_mtx));
        assert(mtx);
        spin_pdr_init(&mtx->lock);
-       TAILQ_INIT(&mtx->waiters);
+       mtx->sync_obj = __uth_sync_alloc();
        mtx->locked = FALSE;
        return mtx;
 }
 
 static void uth_default_mtx_free(struct uth_default_mtx *mtx)
 {
-       assert(TAILQ_EMPTY(&mtx->waiters));
+       __uth_sync_free(mtx->sync_obj);
        free(mtx);
 }
 
 static void __mutex_cb(struct uthread *uth, void *arg)
 {
-       struct uth_mtx_link *link = (struct uth_mtx_link*)arg;
-       struct uth_default_mtx *mtx = link->mtx;
+       struct uth_default_mtx *mtx = (struct uth_default_mtx*)arg;
 
        /* We need to tell the 2LS that its thread blocked.  We need to do this
         * before unlocking the mtx, since as soon as we unlock, the mtx could be
@@ -72,27 +53,22 @@ static void __mutex_cb(struct uthread *uth, void *arg)
         *
         * Also note the lock-ordering rule.  The mtx lock is grabbed before any
         * locks the 2LS might grab. */
-       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       uthread_has_blocked(uth, mtx->sync_obj, UTH_EXT_BLK_MUTEX);
        spin_pdr_unlock(&mtx->lock);
 }
 
 static void uth_default_mtx_lock(struct uth_default_mtx *mtx)
 {
-       struct uth_mtx_link link;
-
        spin_pdr_lock(&mtx->lock);
        if (!mtx->locked) {
                mtx->locked = TRUE;
                spin_pdr_unlock(&mtx->lock);
                return;
        }
-       link.mtx = mtx;
-       link.uth = current_uthread;
-       TAILQ_INSERT_TAIL(&mtx->waiters, &link, next);
-       /* the unlock is done in the yield callback.  as always, we need to do this
-        * part in vcore context, since as soon as we unlock the uthread could
-        * restart.  (atomically yield and unlock). */
-       uthread_yield(TRUE, __mutex_cb, &link);
+       /* the unlock and sync enqueuing is done in the yield callback.  as always,
+        * we need to do this part in vcore context, since as soon as we unlock the
+        * uthread could restart.  (atomically yield and unlock). */
+       uthread_yield(TRUE, __mutex_cb, mtx);
 }
 
 static bool uth_default_mtx_trylock(struct uth_default_mtx *mtx)
@@ -110,17 +86,15 @@ static bool uth_default_mtx_trylock(struct uth_default_mtx *mtx)
 
 static void uth_default_mtx_unlock(struct uth_default_mtx *mtx)
 {
-       struct uth_mtx_link *first;
+       struct uthread *uth;
 
        spin_pdr_lock(&mtx->lock);
-       first = TAILQ_FIRST(&mtx->waiters);
-       if (first)
-               TAILQ_REMOVE(&mtx->waiters, first, next);
-       else
+       uth = __uth_sync_get_next(mtx->sync_obj);
+       if (!uth)
                mtx->locked = FALSE;
        spin_pdr_unlock(&mtx->lock);
-       if (first)
-               uthread_runnable(first->uth);
+       if (uth)
+               uthread_runnable(uth);
 }
 
 
@@ -261,16 +235,21 @@ static struct uth_default_cv *uth_default_cv_alloc(void)
        cv = malloc(sizeof(struct uth_default_cv));
        assert(cv);
        spin_pdr_init(&cv->lock);
-       TAILQ_INIT(&cv->waiters);
+       cv->sync_obj = __uth_sync_alloc();
        return cv;
 }
 
 static void uth_default_cv_free(struct uth_default_cv *cv)
 {
-       assert(TAILQ_EMPTY(&cv->waiters));
+       __uth_sync_free(cv->sync_obj);
        free(cv);
 }
 
+struct uth_cv_link {
+       struct uth_default_cv           *cv;
+       struct uth_default_mtx          *mtx;
+};
+
 static void __cv_wait_cb(struct uthread *uth, void *arg)
 {
        struct uth_cv_link *link = (struct uth_cv_link*)arg;
@@ -283,8 +262,18 @@ static void __cv_wait_cb(struct uthread *uth, void *arg)
         *
         * Also note the lock-ordering rule.  The cv lock is grabbed before any
         * locks the 2LS might grab. */
-       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       uthread_has_blocked(uth, cv->sync_obj, UTH_EXT_BLK_MUTEX);
        spin_pdr_unlock(&cv->lock);
+       /* This looks dangerous, since both the CV and MTX could use the
+        * uth->sync_next TAILQ_ENTRY (or whatever the 2LS uses), but the uthread
+        * never sleeps on both at the same time.  We *hold* the mtx - we aren't
+        * *sleeping* on it.  Sleeping uses the sync_next.  Holding it doesn't.
+        *
+        * Next, consider what happens as soon as we unlock the CV.  Our thread
+        * could get woken up, and then immediately try to grab the mtx and go to
+        * sleep! (see below).  If that happens, the uthread is no longer sleeping
+        * on the CV, and the sync_next is free.  The invariant is that a uthread
+        * can only sleep on one sync_object at a time. */
        uth_mutex_unlock((uth_mutex_t)mtx);
 }
 
@@ -348,37 +337,38 @@ static void uth_default_cv_wait(struct uth_default_cv *cv,
 
        link.cv = cv;
        link.mtx = mtx;
-       link.uth = current_uthread;
        spin_pdr_lock(&cv->lock);
-       TAILQ_INSERT_TAIL(&cv->waiters, &link, next);
        uthread_yield(TRUE, __cv_wait_cb, &link);
        uth_mutex_lock((uth_mutex_t)mtx);
 }
 
 static void uth_default_cv_signal(struct uth_default_cv *cv)
 {
-       struct uth_cv_link *first;
+       struct uthread *uth;
 
        spin_pdr_lock(&cv->lock);
-       first = TAILQ_FIRST(&cv->waiters);
-       if (first)
-               TAILQ_REMOVE(&cv->waiters, first, next);
+       uth = __uth_sync_get_next(cv->sync_obj);
        spin_pdr_unlock(&cv->lock);
-       if (first)
-               uthread_runnable(first->uth);
+       if (uth)
+               uthread_runnable(uth);
 }
 
 static void uth_default_cv_broadcast(struct uth_default_cv *cv)
 {
-       struct cv_link_tq restartees = TAILQ_HEAD_INITIALIZER(restartees);
-       struct uth_cv_link *i, *safe;
+       struct uth_tailq restartees = TAILQ_HEAD_INITIALIZER(restartees);
+       struct uthread *i, *safe;
 
        spin_pdr_lock(&cv->lock);
-       TAILQ_SWAP(&cv->waiters, &restartees, uth_cv_link, next);
+       /* If this turns out to be slow or painful for 2LSs, we can implement a
+        * get_all or something (default used to use TAILQ_SWAP). */
+       while ((i = __uth_sync_get_next(cv->sync_obj))) {
+               /* Once the uth is out of the sync obj, we can reuse sync_next. */
+               TAILQ_INSERT_TAIL(&restartees, i, sync_next);
+       }
        spin_pdr_unlock(&cv->lock);
        /* Need the SAFE, since we can't touch the linkage once the uth could run */
-       TAILQ_FOREACH_SAFE(i, &restartees, next, safe)
-               uthread_runnable(i->uth);
+       TAILQ_FOREACH_SAFE(i, &restartees, sync_next, safe)
+               uthread_runnable(i);
 }
 
 
@@ -427,3 +417,94 @@ void uth_cond_var_broadcast(uth_cond_var_t cv)
        }
        uth_default_cv_broadcast((struct uth_default_cv*)cv);
 }
+
+
+/************** Default Sync Obj Implementation **************/
+
+static uth_sync_t uth_default_sync_alloc(void)
+{
+       struct uth_tailq *tq;
+
+       tq = malloc(sizeof(struct uth_tailq));
+       assert(tq);
+       TAILQ_INIT(tq);
+       return (uth_sync_t)tq;
+}
+
+static void uth_default_sync_free(uth_sync_t sync)
+{
+       struct uth_tailq *tq = (struct uth_tailq*)sync;
+
+       assert(TAILQ_EMPTY(tq));
+       free(tq);
+}
+
+static struct uthread *uth_default_sync_get_next(uth_sync_t sync)
+{
+       struct uth_tailq *tq = (struct uth_tailq*)sync;
+       struct uthread *first;
+
+       first = TAILQ_FIRST(tq);
+       if (first)
+               TAILQ_REMOVE(tq, first, sync_next);
+       return first;
+}
+
+static bool uth_default_sync_get_uth(uth_sync_t sync, struct uthread *uth)
+{
+       struct uth_tailq *tq = (struct uth_tailq*)sync;
+       struct uthread *i;
+
+       TAILQ_FOREACH(i, tq, sync_next) {
+               if (i == uth) {
+                       TAILQ_REMOVE(tq, i, sync_next);
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+/************** External uthread sync interface **************/
+
+/* Called by the 2LS->has_blocked op, if they are using the default sync.*/
+void __uth_default_sync_enqueue(struct uthread *uth, uth_sync_t sync)
+{
+       struct uth_tailq *tq = (struct uth_tailq*)sync;
+
+       TAILQ_INSERT_TAIL(tq, uth, sync_next);
+}
+
+/* Called by 2LS-independent sync code when a sync object is created. */
+uth_sync_t __uth_sync_alloc(void)
+{
+       if (sched_ops->sync_alloc)
+               return sched_ops->sync_alloc();
+       return uth_default_sync_alloc();
+}
+
+/* Called by 2LS-independent sync code when a sync object is destroyed. */
+void __uth_sync_free(uth_sync_t sync)
+{
+       if (sched_ops->sync_free) {
+               sched_ops->sync_free(sync);
+               return;
+       }
+       uth_default_sync_free(sync);
+}
+
+/* Called by 2LS-independent sync code when a thread needs to be woken. */
+struct uthread *__uth_sync_get_next(uth_sync_t sync)
+{
+       if (sched_ops->sync_get_next)
+               return sched_ops->sync_get_next(sync);
+       return uth_default_sync_get_next(sync);
+}
+
+/* Called by 2LS-independent sync code when a specific thread needs to be woken.
+ * Returns TRUE if the uthread was blocked on the object, FALSE o/w. */
+bool __uth_sync_get_uth(uth_sync_t sync, struct uthread *uth)
+{
+       if (sched_ops->sync_get_uth)
+               return sched_ops->sync_get_uth(sync, uth);
+       return uth_default_sync_get_uth(sync, uth);
+}
index 4367d9e..f85abe0 100644 (file)
@@ -21,12 +21,17 @@ static void thread0_thread_blockon_sysc(struct uthread *uthread, void *sysc);
 static void thread0_thread_refl_fault(struct uthread *uth,
                                       struct user_context *ctx);
 static void thread0_thread_runnable(struct uthread *uth);
-static void thread0_thread_has_blocked(struct uthread *uth, int flags);
+static void thread0_thread_has_blocked(struct uthread *uth, uth_sync_t sync,
+                                       int flags);
 static uth_mutex_t thread0_mtx_alloc(void);
 static void thread0_mtx_free(uth_mutex_t m);
 static void thread0_mtx_lock(uth_mutex_t m);
 static bool thread0_mtx_trylock(uth_mutex_t m);
 static void thread0_mtx_unlock(uth_mutex_t m);
+static uth_sync_t thread0_sync_alloc(void);
+static void thread0_sync_free(uth_sync_t);
+static struct uthread *thread0_sync_get_next(uth_sync_t);
+static bool thread0_sync_get_uth(uth_sync_t s, struct uthread *uth);
 
 /* externed into uthread.c */
 struct schedule_ops thread0_2ls_ops = {
@@ -41,6 +46,10 @@ struct schedule_ops thread0_2ls_ops = {
        .mutex_lock = thread0_mtx_lock,
        .mutex_trylock = thread0_mtx_trylock,
        .mutex_unlock = thread0_mtx_unlock,
+       .sync_alloc = thread0_sync_alloc,
+       .sync_free = thread0_sync_free,
+       .sync_get_next = thread0_sync_get_next,
+       .sync_get_uth = thread0_sync_get_uth,
 };
 
 /* externed into uthread.c */
@@ -92,7 +101,7 @@ static void thread0_sched_entry(void)
 static void thread0_thread_blockon_sysc(struct uthread *uthread, void *arg)
 {
        struct syscall *sysc = (struct syscall*)arg;
-       thread0_thread_has_blocked(uthread, 0);
+       thread0_thread_has_blocked(uthread, NULL, 0);
        if (!register_evq(sysc, sysc_evq))
                thread0_thread_runnable(uthread);
 }
@@ -142,8 +151,10 @@ static void thread0_thread_runnable(struct uthread *uth)
        thread0_info.is_blocked = FALSE;
 }
 
-static void thread0_thread_has_blocked(struct uthread *uth, int flags)
+static void thread0_thread_has_blocked(struct uthread *uth, uth_sync_t sync,
+                                       int flags)
 {
+       assert(!thread0_info.is_blocked);
        thread0_info.is_blocked = TRUE;
 }
 
@@ -191,3 +202,33 @@ static void thread0_mtx_unlock(uth_mutex_t m)
        assert(*mtx == TRUE);
        *mtx = FALSE;
 }
+
+static uth_sync_t thread0_sync_alloc(void)
+{
+       return (void*)0xf00baa;
+}
+
+static void thread0_sync_free(uth_sync_t s)
+{
+}
+
+static struct uthread *thread0_sync_get_next(uth_sync_t s)
+{
+       if (thread0_info.is_blocked) {
+               /* Note we don't clear is_blocked.  Runnable does that, which should be
+                * called before the next get_next (since we have only one thread). */
+               return thread0_uth;
+       } else {
+               return NULL;
+       }
+}
+
+static bool thread0_sync_get_uth(uth_sync_t s, struct uthread *uth)
+{
+       assert(uth == thread0_uth);
+       if (thread0_info.is_blocked) {
+               /* Note we don't clear is_blocked.  Runnable does that. */
+               return TRUE;
+       }
+       return FALSE;
+}
index ff685f1..bd4e7cb 100644 (file)
@@ -364,17 +364,22 @@ void uthread_runnable(struct uthread *uthread)
  * the 2LS.  This is for informational purposes, and some semantic meaning
  * should be passed by flags (from uthread.h's UTH_EXT_BLK_xxx options).
  * Eventually, whoever calls this will call uthread_runnable(), giving the
- * thread back to the 2LS.
+ * thread back to the 2LS.  If the 2LS provide sync ops, it will have a say in
+ * which thread wakes up at a given time.
  *
  * If code outside the 2LS has blocked a thread (via uthread_yield) and ran its
  * own callback/yield_func instead of some 2LS code, that callback needs to
  * call this.
  *
+ * If sync is set, then the 2LS must save the uthread in the sync object.  Using
+ * the default implementatin is fine.  If sync is NULL, then there's nothing the
+ * 2LS should do regarding sync; it'll be told when the thread is runnable.
+ *
  * AKA: obviously_a_uthread_has_blocked_in_lincoln_park() */
-void uthread_has_blocked(struct uthread *uthread, int flags)
+void uthread_has_blocked(struct uthread *uthread, uth_sync_t sync, int flags)
 {
        assert(sched_ops->thread_has_blocked);
-       sched_ops->thread_has_blocked(uthread, flags);
+       sched_ops->thread_has_blocked(uthread, sync, flags);
 }
 
 /* Function indicating an external event has temporarily paused a uthread, but
@@ -516,7 +521,7 @@ void uthread_usleep(unsigned int usecs)
 
 static void __sleep_forever_cb(struct uthread *uth, void *arg)
 {
-       uthread_has_blocked(uth, UTH_EXT_BLK_JUSTICE);
+       uthread_has_blocked(uth, NULL, UTH_EXT_BLK_JUSTICE);
 }
 
 void __attribute__((noreturn)) uthread_sleep_forever(void)
index 932ecc0..4849051 100644 (file)
@@ -40,7 +40,8 @@ static void pth_sched_entry(void);
 static void pth_thread_runnable(struct uthread *uthread);
 static void pth_thread_paused(struct uthread *uthread);
 static void pth_thread_blockon_sysc(struct uthread *uthread, void *sysc);
-static void pth_thread_has_blocked(struct uthread *uthread, int flags);
+static void pth_thread_has_blocked(struct uthread *uthread, uth_sync_t sync_obj,
+                                   int flags);
 static void pth_thread_refl_fault(struct uthread *uth,
                                   struct user_context *ctx);
 
@@ -242,7 +243,8 @@ static void pth_thread_blockon_sysc(struct uthread *uthread, void *syscall)
        /* GIANT WARNING: do not touch the thread after this point. */
 }
 
-static void pth_thread_has_blocked(struct uthread *uthread, int flags)
+static void pth_thread_has_blocked(struct uthread *uthread, uth_sync_t sync_obj,
+                                   int flags)
 {
        struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
 
@@ -252,6 +254,8 @@ static void pth_thread_has_blocked(struct uthread *uthread, int flags)
         * mostly communicating to our future selves in pth_thread_runnable(), which
         * gets called by whoever triggered this callback */
        pthread->state = PTH_BLK_MUTEX;
+       if (sync_obj)
+               __uth_default_sync_enqueue(uthread, sync_obj);
 }
 
 static void __signal_and_restart(struct uthread *uthread,
index 5be751f..eeebc65 100644 (file)
@@ -37,7 +37,8 @@ static void vmm_sched_entry(void);
 static void vmm_thread_runnable(struct uthread *uth);
 static void vmm_thread_paused(struct uthread *uth);
 static void vmm_thread_blockon_sysc(struct uthread *uth, void *sysc);
-static void vmm_thread_has_blocked(struct uthread *uth, int flags);
+static void vmm_thread_has_blocked(struct uthread *uth, uth_sync_t sync_obj,
+                                   int flags);
 static void vmm_thread_refl_fault(struct uthread *uth,
                                   struct user_context *ctx);
 
@@ -269,13 +270,16 @@ static void vmm_thread_blockon_sysc(struct uthread *uth, void *syscall)
        /* GIANT WARNING: do not touch the thread after this point. */
 }
 
-static void vmm_thread_has_blocked(struct uthread *uth, int flags)
+static void vmm_thread_has_blocked(struct uthread *uth, uth_sync_t sync_obj,
+                                   int flags)
 {
        /* The thread blocked on something like a mutex.  It's not runnable, so we
         * don't need to put it on a list, but we do need to account for it not
         * running.  We'll find out (via thread_runnable) when it starts up again.
         */
        acct_thread_blocked((struct vmm_thread*)uth);
+       if (sync_obj)
+               __uth_default_sync_enqueue(uth, sync_obj);
 }
 
 static void refl_error(struct uthread *uth, unsigned int trap_nr,