parlib: Implement join/detach() for all uthreads
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 11 Apr 2017 19:28:50 +0000 (15:28 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 3 May 2017 16:13:02 +0000 (12:13 -0400)
Previously, it was up to specific 2LSs, such as pthreads, to implement
join() and detach() if they wanted it.  GCC needs it for the C++ threads,
and it might be helpful for all 2LSs.  Now, join/detach() are handled at
the uthread layer.

These functions only kick in if a thread exits.  That is still controlled
by the 2LS.  Previously, exits were done with a yield callback.  Instead,
2LSs will call uth_2ls_thread_exit(), and the callback work is done in the
2LS->thread_exited() op.  If a thread never exits, such as certain types of
VM threads, then that op will not be called.  And, as one would expect, if
you join() a thread that never exits, you'll block forever.

The implementation of join/detach/exit differs from the original pthreads
implementation.  The pthread implementation had a race on the detach state,
where a bad combination of detacher/joiner/exiter could be corrupted.
CAS-ing on the state solved a lot of those problems.

The other major improvement was adding a parallel join.  Since this join
will work for all 2LSs, I'd like it to work for some of the more exotic
schedulers.  Parallel join is a common operation in some threading
libraries and frameworks (openmp, lithe, etc).  My implementation uses
krefs so that the joiner is only woken up once for all N threads, instead
of up to N times.  The kref also helps with races between concurrent joins
and exits.

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

index 9731f35..4eab654 100644 (file)
@@ -91,6 +91,12 @@ int main(int argc, char** argv)
                                    __procinfo.vcoremap[i].pcoreid);
                }
        }
+       struct uth_join_request *join_reqs;
+
+       join_reqs = malloc(nr_yield_threads * sizeof(struct uth_join_request));
+       for (int i = 0; i < nr_yield_threads; i++)
+               join_reqs[i].retval_loc = &my_retvals[i];
+       assert(join_reqs);
 #endif /* __ros__ */
 
        pthread_barrier_init(&barrier, NULL, nr_yield_threads);
@@ -102,12 +108,19 @@ int main(int argc, char** argv)
        }
        if (gettimeofday(&start_tv, 0))
                perror("Start time error...");
+       /* Akaros supports parallel join */
+#ifdef __ros__
+       for (int i = 0; i < nr_yield_threads; i++)
+               join_reqs[i].uth = (struct uthread*)my_threads[i];
+       uthread_join_arr(join_reqs, nr_yield_threads);
+#else
        for (int i = 0; i < nr_yield_threads; i++) {
                printf_safe("[A] About to join on thread %d(%p)\n", i, my_threads[i]);
                pthread_join(my_threads[i], &my_retvals[i]);
                printf_safe("[A] Successfully joined on thread %d (retval: %p)\n", i,
                            my_retvals[i]);
        }
+#endif
        if (gettimeofday(&end_tv, 0))
                perror("End time error...");
        nr_ctx_switches = nr_yield_threads * nr_yield_loops;
index 03ec741..5e65643 100644 (file)
@@ -4,6 +4,7 @@
 #include <parlib/signal.h>
 #include <parlib/spinlock.h>
 #include <parlib/parlib.h>
+#include <parlib/kref.h>
 #include <ros/syscall.h>
 #include <sys/queue.h>
 #include <time.h>
@@ -24,6 +25,26 @@ __BEGIN_DECLS
 #define UTH_EXT_BLK_EVENTQ                     2
 #define UTH_EXT_BLK_JUSTICE                    3       /* whatever.  might need more options */
 
+/* One per joiner, usually kept on the stack. */
+struct uth_join_kicker {
+       struct kref                                     kref;
+       struct uthread                          *joiner;
+};
+
+/* Join states, stored in the join_ctl */
+#define UTH_JOIN_DETACHED              1
+#define UTH_JOIN_JOINABLE              2
+#define UTH_JOIN_HAS_JOINER            3
+#define UTH_JOIN_EXITED                        4
+
+/* One per uthread, to encapsulate all the join fields. */
+struct uth_join_ctl {
+       atomic_t                                        state;
+       void                                            *retval;
+       void                                            **retval_loc;
+       struct uth_join_kicker          *kicker;
+};
+
 /* Bare necessities of a user thread.  2LSs should allocate a bigger struct and
  * cast their threads to uthreads when talking with vcore code.  Vcore/default
  * 2LS code won't touch udata or beyond. */
@@ -33,6 +54,7 @@ struct uthread {
        void *tls_desc;
        int flags;
        int state;
+       struct uth_join_ctl join_ctl;
        struct sigstate sigstate;
        int notif_disabled_depth;
        TAILQ_ENTRY(uthread) sync_next;
@@ -75,6 +97,7 @@ struct schedule_ops {
        void (*thread_blockon_sysc)(struct uthread *, void *);
        void (*thread_has_blocked)(struct uthread *, uth_sync_t, int);
        void (*thread_refl_fault)(struct uthread *, struct user_context *);
+       void (*thread_exited)(struct uthread *);
        /**** Defining these functions is optional. ****/
        uth_sync_t (*sync_alloc)(void);
        void (*sync_free)(uth_sync_t);
@@ -102,13 +125,24 @@ void uthread_mcp_init(void);
  * pthread_create(), which can wrap these with their own stuff (like attrs,
  * retvals, etc). */
 
-/* uthread_init() does the uthread initialization of a uthread that the caller
- * created.  Call this whenever you are "starting over" with a thread.  Pass in
- * attr, if you want to override any defaults. */
 struct uth_thread_attr {
        bool want_tls;          /* default, no */
+       bool detached;          /* default, no */
 };
+
+struct uth_join_request {
+       struct uthread                          *uth;
+       void                                            **retval_loc;
+};
+
+/* uthread_init() does the uthread initialization of a uthread that the caller
+ * created.  Call this whenever you are "starting over" with a thread.  Pass in
+ * attr, if you want to override any defaults. */
 void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr);
+void uthread_detach(struct uthread *uth);
+void uthread_join(struct uthread *uth, void **retval_loc);
+void uthread_join_arr(struct uth_join_request reqs[], size_t nr_req);
+
 /* Call this when you are done with a uthread, forever, but before you free it */
 void uthread_cleanup(struct uthread *uthread);
 void uthread_runnable(struct uthread *uthread);
@@ -131,6 +165,7 @@ void highjack_current_uthread(struct uthread *uthread);
 struct uthread *stop_current_uthread(void);
 void __attribute__((noreturn)) run_current_uthread(void);
 void __attribute__((noreturn)) run_uthread(struct uthread *uthread);
+void __attribute__((noreturn)) uth_2ls_thread_exit(void *retval);
 
 /* Asking for trouble with this API, when we just want stacktop (or whatever
  * the SP will be). */
index 39b4a2a..1def58c 100644 (file)
@@ -23,6 +23,7 @@ static void thread0_thread_refl_fault(struct uthread *uth,
 static void thread0_thread_runnable(struct uthread *uth);
 static void thread0_thread_has_blocked(struct uthread *uth, uth_sync_t sync,
                                        int flags);
+static void thread0_thread_exited(struct uthread *uth);
 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);
@@ -36,6 +37,7 @@ struct schedule_ops thread0_2ls_ops = {
        .thread_runnable = thread0_thread_runnable,
        .thread_paused = thread0_thread_runnable,
        .thread_has_blocked = thread0_thread_has_blocked,
+       .thread_exited = thread0_thread_exited,
        .sync_alloc = thread0_sync_alloc,
        .sync_free = thread0_sync_free,
        .sync_get_next = thread0_sync_get_next,
@@ -148,6 +150,13 @@ static void thread0_thread_has_blocked(struct uthread *uth, uth_sync_t sync,
        thread0_info.is_blocked = TRUE;
 }
 
+/* Actually, a 2LS only needs to implement this if it calls
+ * uth_2ls_thread_exit().  Keep it here to catch bugs. */
+static void thread0_thread_exited(struct uthread *uth)
+{
+       assert(0);
+}
+
 static uth_sync_t thread0_sync_alloc(void)
 {
        return (void*)0xf00baa;
index bd4e7cb..faa425c 100644 (file)
@@ -46,6 +46,8 @@ static void uthread_init_thread0(struct uthread *uthread)
        current_uthread = uthread;
        /* Thread is currently running (it is 'us') */
        uthread->state = UT_RUNNING;
+       /* Thread is detached */
+       atomic_set(&uthread->join_ctl.state, UTH_JOIN_DETACHED);
        /* Reset the signal state */
        uthread->sigstate.mask = 0;
        __sigemptyset(&uthread->sigstate.pending);
@@ -330,6 +332,8 @@ void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
         * were interrupted off a core. */
        new_thread->flags |= UTHREAD_SAVED;
        new_thread->notif_disabled_depth = 0;
+       /* TODO: on a reinit, if they changed whether or not they want TLS, we'll
+        * have issues (checking tls_desc, assert in allocate_tls, maybe more). */
        if (attr && attr->want_tls) {
                /* Get a TLS.  If we already have one, reallocate/refresh it */
                if (new_thread->tls_desc)
@@ -348,6 +352,10 @@ void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
        } else {
                new_thread->tls_desc = UTH_TLSDESC_NOTLS;
        }
+       if (attr && attr->detached)
+               atomic_set(&new_thread->join_ctl.state, UTH_JOIN_DETACHED);
+       else
+               atomic_set(&new_thread->join_ctl.state, UTH_JOIN_JOINABLE);
 }
 
 /* This is a wrapper for the sched_ops thread_runnable, for use by functions
@@ -1205,3 +1213,164 @@ bool uth_2ls_is_multithreaded(void)
         * will be multithreaded. */
        return sched_ops != &thread0_2ls_ops;
 }
+
+/* Who does the thread_exited callback (2LS-specific cleanup)?  It depends.  If
+ * the thread exits first, then join/detach does it.  o/w, the exit path does.
+ *
+ * What are the valid state changes?
+ *
+ *             JOINABLE   -> DETACHED (only by detach())
+ *             JOINABLE   -> HAS_JOINER (only by join())
+ *             JOINABLE   -> EXITED (only by uth_2ls_thread_exit())
+ *
+ * That's it.  The initial state is either JOINABLE or DETACHED. */
+void uthread_detach(struct uthread *uth)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+       long old_state;
+
+       do {
+               old_state = atomic_read(&jc->state);
+               switch (old_state) {
+               case UTH_JOIN_EXITED:
+                       sched_ops->thread_exited(uth);
+                       return;
+               case UTH_JOIN_DETACHED:
+                       panic("Uth %p has already been detached!", uth);
+               case UTH_JOIN_HAS_JOINER:
+                       panic("Uth %p has a pending joiner, can't detach!", uth);
+               };
+               assert(old_state == UTH_JOIN_JOINABLE);
+       } while (!atomic_cas(&jc->state, old_state, UTH_JOIN_DETACHED));
+}
+
+/* Helper.  We have a joiner.  So we'll write the retval to the final location
+ * (the one passed to join() and decref to wake the joiner.  This may seem a
+ * little odd for a normal join, but it works identically a parallel join - and
+ * there's only one wakeup (hence the kref). */
+static void uth_post_and_kick_joiner(struct uthread *uth, void *retval)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+
+       if (jc->retval_loc)
+               *jc->retval_loc = retval;
+       /* Note the JC has a pointer to the kicker.  There's one kicker for the
+        * joiner, but there could be many joinees. */
+       kref_put(&jc->kicker->kref);
+}
+
+/* Callback after the exiting uthread has yielded and is in vcore context.  Note
+ * that the thread_exited callback can be called concurrently (e.g., a racing
+ * call to detach()), so it's important to not be in the uthread's context. */
+static void __uth_2ls_thread_exit_cb(struct uthread *uth, void *retval)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+       long old_state;
+
+       do {
+               old_state = atomic_read(&jc->state);
+               switch (old_state) {
+               case UTH_JOIN_DETACHED:
+                       sched_ops->thread_exited(uth);
+                       return;
+               case UTH_JOIN_HAS_JOINER:
+                       uth_post_and_kick_joiner(uth, retval);
+                       sched_ops->thread_exited(uth);
+                       return;
+               case UTH_JOIN_JOINABLE:
+                       /* This write is harmless and idempotent; we can lose the race and
+                        * still be safe.  Assuming we don't, the joiner will look here for
+                        * the retval.  It's temporary storage since we don't know the final
+                        * retval location (since join hasn't happened yet). */
+                       jc->retval = retval;
+                       break;
+               };
+               assert(old_state == UTH_JOIN_JOINABLE);
+       } while (!atomic_cas(&jc->state, old_state, UTH_JOIN_EXITED));
+       /* We were joinable, now we have exited.  A detacher or joiner will trigger
+        * thread_exited. */
+}
+
+/* 2LSs call this when their threads are exiting.  The 2LS will regain control
+ * of the thread in sched_ops->thread_exited.  This will be after the
+ * join/detach/exit has completed, and might be in vcore context. */
+void __attribute__((noreturn)) uth_2ls_thread_exit(void *retval)
+{
+       uthread_yield(FALSE, __uth_2ls_thread_exit_cb, retval);
+       assert(0);
+}
+
+/* Helper: Attaches the caller (specifically the jk) to the target uthread.
+ * When the thread has been joined (either due to the UTH_EXITED case or due to
+ * __uth_2ls_thread_exit_cb), the join kicker will be decreffed. */
+static void join_one(struct uthread *uth, struct uth_join_kicker *jk,
+                     void **retval_loc)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+       long old_state;
+
+       /* We can safely write to the join_ctl, even if we don't end up setting
+        * HAS_JOINER.  There's only supposed to be one joiner, and if not, we'll
+        * catch the bad state. */
+       jc->retval_loc = retval_loc;
+       jc->kicker = jk;
+       do {
+               old_state = atomic_read(&jc->state);
+               switch (old_state) {
+               case UTH_JOIN_EXITED:
+                       if (retval_loc)
+                               *retval_loc = jc->retval;
+                       sched_ops->thread_exited(uth);
+                       kref_put(&jk->kref);
+                       return;
+               case UTH_JOIN_DETACHED:
+                       panic("Uth %p has been detached, can't join!", uth);
+               case UTH_JOIN_HAS_JOINER:
+                       panic("Uth %p has another pending joiner!", uth);
+               };
+               assert(old_state == UTH_JOIN_JOINABLE);
+       } while (!atomic_cas(&jc->state, old_state, UTH_JOIN_HAS_JOINER));
+}
+
+/* Bottom half of the join, in vcore context */
+static void __uth_join_cb(struct uthread *uth, void *arg)
+{
+       struct uth_join_kicker *jk = (struct uth_join_kicker*)arg;
+
+       uthread_has_blocked(uth, NULL, UTH_EXT_BLK_MUTEX);
+       /* After this, and after all threads join, we could be woken up. */
+       kref_put(&jk->kref);
+}
+
+static void kicker_release(struct kref *k)
+{
+       struct uth_join_kicker *jk = container_of(k, struct uth_join_kicker, kref);
+
+       uthread_runnable(jk->joiner);
+}
+
+void uthread_join_arr(struct uth_join_request reqs[], size_t nr_req)
+{
+       struct uth_join_kicker jk[1];
+
+       jk->joiner = current_uthread;
+       /* One ref for each target, another for *us*, which we drop in the yield
+        * callback.  As as soon as it is fully decreffed, our thread will be
+        * restarted.  We must block before that (in the yield callback). */
+       kref_init(&jk->kref, kicker_release, nr_req + 1);
+       for (int i = 0; i < nr_req; i++)
+               join_one(reqs[i].uth, jk, reqs[i].retval_loc);
+       uthread_yield(TRUE, __uth_join_cb, jk);
+}
+
+/* Unlike POSIX, we don't bother with returning error codes.  Anything that can
+ * go wrong is so horrendous that you should crash (the specs say the behavior
+ * is undefined). */
+void uthread_join(struct uthread *uth, void **retval_loc)
+{
+       struct uth_join_request req[1];
+
+       req->uth = uth;
+       req->retval_loc = retval_loc;
+       uthread_join_arr(req, 1);
+}
index 4849051..d09f901 100644 (file)
@@ -44,6 +44,7 @@ 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);
+static void pth_thread_exited(struct uthread *uth);
 
 /* Event Handlers */
 static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type,
@@ -56,6 +57,7 @@ struct schedule_ops pthread_sched_ops = {
        .thread_blockon_sysc = pth_thread_blockon_sysc,
        .thread_has_blocked = pth_thread_has_blocked,
        .thread_refl_fault = pth_thread_refl_fault,
+       .thread_exited = pth_thread_exited,
 };
 
 /* Static helpers */
@@ -138,7 +140,6 @@ static void pth_thread_runnable(struct uthread *uthread)
        switch (pthread->state) {
                case (PTH_CREATED):
                case (PTH_BLK_YIELDING):
-               case (PTH_BLK_JOINING):
                case (PTH_BLK_SYSC):
                case (PTH_BLK_PAUSED):
                case (PTH_BLK_MUTEX):
@@ -333,6 +334,24 @@ static void pth_thread_refl_fault(struct uthread *uth,
        }
 }
 
+static void pth_thread_exited(struct uthread *uth)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uth;
+
+       __pthread_generic_yield(pthread);
+       /* Catch some bugs */
+       pthread->state = PTH_EXITING;
+       /* Destroy the pthread */
+       uthread_cleanup(uth);
+       /* Cleanup, mirroring pthread_create() */
+       __pthread_free_stack(pthread);
+       /* If we were the last pthread, we exit for the whole process.  Keep in mind
+        * that thread0 is counted in this, so this will only happen if that thread
+        * calls pthread_exit(). */
+       if ((atomic_fetch_and_add(&threads_total, -1) == 1))
+               exit(0);
+}
+
 /* Akaros pthread extensions / hacks */
 
 void pthread_need_tls(bool need)
@@ -422,9 +441,11 @@ int pthread_attr_getstack(const pthread_attr_t *__restrict __attr,
 
 int pthread_getattr_np(pthread_t __th, pthread_attr_t *__attr)
 {
+       struct uthread *uth = (struct uthread*)__th;
+
        __attr->stackaddr = __th->stacktop - __th->stacksize;
        __attr->stacksize = __th->stacksize;
-       if (__th->detached)
+       if (atomic_read(&uth->join_ctl.state) == UTH_JOIN_DETACHED)
                __attr->detachstate = PTHREAD_CREATE_DETACHED;
        else
                __attr->detachstate = PTHREAD_CREATE_JOINABLE;
@@ -452,9 +473,7 @@ void __attribute__((constructor)) pthread_lib_init(void)
        t->id = get_next_pid();
        t->stacksize = USTACK_NUM_PAGES * PGSIZE;
        t->stacktop = (void*)USTACKTOP;
-       t->detached = TRUE;
        t->state = PTH_RUNNING;
-       t->joiner = 0;
        /* implies that sigmasks are longs, which they are. */
        assert(t->id == 0);
        t->sched_policy = SCHED_FIFO;
@@ -554,8 +573,6 @@ int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        pthread->stacksize = PTHREAD_STACK_SIZE;        /* default */
        pthread->state = PTH_CREATED;
        pthread->id = get_next_pid();
-       pthread->detached = FALSE;                              /* default */
-       pthread->joiner = 0;
        /* Might override these later, based on attr && EXPLICIT_SCHED */
        pthread->sched_policy = parent->sched_policy;
        pthread->sched_priority = parent->sched_priority;
@@ -565,7 +582,7 @@ int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                if (attr->stacksize)                                    /* don't set a 0 stacksize */
                        pthread->stacksize = attr->stacksize;
                if (attr->detachstate == PTHREAD_CREATE_DETACHED)
-                       pthread->detached = TRUE;
+                       uth_attr.detached = TRUE;
                if (attr->sched_inherit == PTHREAD_EXPLICIT_SCHED) {
                        pthread->sched_policy = attr->sched_policy;
                        pthread->sched_priority = attr->sched_priority;
@@ -609,101 +626,20 @@ void __pthread_generic_yield(struct pthread_tcb *pthread)
        mcs_pdr_unlock(&queue_lock);
 }
 
-/* Callback/bottom half of join, called from __uthread_yield (vcore context).
- * join_target is who we are trying to join on (and who is calling exit). */
-static void __pth_join_cb(struct uthread *uthread, void *arg)
-{
-       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
-       struct pthread_tcb *join_target = (struct pthread_tcb*)arg;
-       struct pthread_tcb *temp_pth = 0;
-       __pthread_generic_yield(pthread);
-       /* We're trying to join, yield til we get woken up */
-       pthread->state = PTH_BLK_JOINING;       /* could do this front-side */
-       /* Put ourselves in the join target's joiner slot.  If we get anything back,
-        * we lost the race and need to wake ourselves.  Syncs with __pth_exit_cb.*/
-       temp_pth = atomic_swap_ptr((void**)&join_target->joiner, pthread);
-       /* After that atomic swap, the pthread might be woken up (if it succeeded),
-        * so don't touch pthread again after that (this following if () is okay).*/
-       if (temp_pth) {         /* temp_pth != 0 means they exited first */
-               assert(temp_pth == join_target);        /* Sanity */
-               /* wake ourselves, not the exited one! */
-               printd("[pth] %08p already exit, rewaking ourselves, joiner %08p\n",
-                      temp_pth, pthread);
-               pth_thread_runnable(uthread);   /* wake ourselves */
-       }
-}
-
 int pthread_join(struct pthread_tcb *join_target, void **retval)
 {
-       /* Not sure if this is the right semantics.  There is a race if we deref
-        * join_target and he is already freed (which would have happened if he was
-        * detached. */
-       if (join_target->detached) {
-               printf("[pthread] trying to join on a detached pthread");
-               return -1;
-       }
-       /* See if it is already done, to avoid the pain of a uthread_yield() (the
-        * early check is an optimization, pth_thread_yield() handles the race). */
-       if (!join_target->joiner) {
-               uthread_yield(TRUE, __pth_join_cb, join_target);
-               /* When we return/restart, the thread will be done */
-       } else {
-               assert(join_target->joiner == join_target);     /* sanity check */
-       }
-       if (retval)
-               *retval = join_target->retval;
-       free(join_target);
+       uthread_join((struct uthread*)join_target, retval);
        return 0;
 }
 
-/* Callback/bottom half of exit.  Syncs with __pth_join_cb.  Here's how it
- * works: the slot for joiner is initially 0.  Joiners try to swap themselves
- * into that spot.  Exiters try to put 'themselves' into it.  Whoever gets 0
- * back won the race.  If the exiter lost the race, it must wake up the joiner
- * (which was the value from temp_pth).  If the joiner lost the race, it must
- * wake itself up, and for sanity reasons can ensure the value from temp_pth is
- * the join target). */
-static void __pth_exit_cb(struct uthread *uthread, void *junk)
-{
-       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
-       struct pthread_tcb *temp_pth = 0;
-       __pthread_generic_yield(pthread);
-       /* Catch some bugs */
-       pthread->state = PTH_EXITING;
-       /* Destroy the pthread */
-       uthread_cleanup(uthread);
-       /* Cleanup, mirroring pthread_create() */
-       __pthread_free_stack(pthread);
-       /* TODO: race on detach state (see join) */
-       if (pthread->detached) {
-               free(pthread);
-       } else {
-               /* See if someone is joining on us.  If not, we're done (and the
-                * joiner will wake itself when it saw us there instead of 0). */
-               temp_pth = atomic_swap_ptr((void**)&pthread->joiner, pthread);
-               if (temp_pth) {
-                       /* they joined before we exited, we need to wake them */
-                       printd("[pth] %08p exiting, waking joiner %08p\n",
-                              pthread, temp_pth);
-                       pth_thread_runnable((struct uthread*)temp_pth);
-               }
-       }
-       /* If we were the last pthread, we exit for the whole process.  Keep in mind
-        * that thread0 is counted in this, so this will only happen if that thread
-        * calls pthread_exit(). */
-       if ((atomic_fetch_and_add(&threads_total, -1) == 1))
-               exit(0);
-}
-
 static inline void pthread_exit_no_cleanup(void *ret)
 {
        struct pthread_tcb *pthread = pthread_self();
 
-       pthread->retval = ret;
        while (SLIST_FIRST(&pthread->cr_stack))
                pthread_cleanup_pop(FALSE);
        destroy_dtls();
-       uthread_yield(FALSE, __pth_exit_cb, 0);
+       uth_2ls_thread_exit(ret);
 }
 
 void pthread_exit(void *ret)
@@ -1145,8 +1081,7 @@ int pthread_barrier_destroy(pthread_barrier_t *b)
 
 int pthread_detach(pthread_t thread)
 {
-       /* TODO: race on this state.  Someone could be trying to join now */
-       thread->detached = TRUE;
+       uthread_detach((struct uthread*)thread);
        return 0;
 }
 
index 5c245cf..ce161bd 100644 (file)
@@ -20,10 +20,9 @@ __BEGIN_DECLS
 #define PTH_RUNNING                    3
 #define PTH_EXITING                    4
 #define PTH_BLK_YIELDING       5       /* brief state btw pth_yield and pth_runnable */
-#define PTH_BLK_JOINING                6       /* joining on a child */
-#define PTH_BLK_SYSC           7       /* blocked on a syscall */
-#define PTH_BLK_MUTEX          8       /* blocked externally, possibly on a mutex */
-#define PTH_BLK_PAUSED         9       /* handed back to us from uthread code */
+#define PTH_BLK_SYSC           6       /* blocked on a syscall */
+#define PTH_BLK_MUTEX          7       /* blocked externally, possibly on a mutex */
+#define PTH_BLK_PAUSED         8       /* handed back to us from uthread code */
 
 /* Entry for a pthread_cleanup_routine on the stack of cleanup handlers. */
 struct pthread_cleanup_routine {
@@ -44,14 +43,11 @@ struct pthread_tcb {
                SLIST_ENTRY(pthread_tcb) sl_next;
        };
        int state;
-       bool detached;
-       struct pthread_tcb *joiner;                     /* raced on by exit and join */
        uint32_t id;
        uint32_t stacksize;
        void *stacktop;
        void *(*start_routine)(void*);
        void *arg;
-       void *retval;
        int sched_policy;
        int sched_priority;             /* careful, GNU #defines this to __sched_priority */
        struct pthread_cleanup_stack cr_stack;
index 95bed94..5258b89 100644 (file)
@@ -41,6 +41,7 @@ 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);
+static void vmm_thread_exited(struct uthread *uth);
 
 struct schedule_ops vmm_sched_ops = {
        .sched_entry = vmm_sched_entry,
@@ -49,6 +50,7 @@ struct schedule_ops vmm_sched_ops = {
        .thread_blockon_sysc = vmm_thread_blockon_sysc,
        .thread_has_blocked = vmm_thread_has_blocked,
        .thread_refl_fault = vmm_thread_refl_fault,
+       .thread_exited = vmm_thread_exited,
 };
 
 /* Helpers */
@@ -393,6 +395,20 @@ static void vmm_thread_refl_fault(struct uthread *uth,
        }
 }
 
+static void vmm_thread_exited(struct uthread *uth)
+{
+       struct vmm_thread *vth = (struct vmm_thread*)uth;
+       struct task_thread *tth = (struct task_thread*)uth;
+
+       /* Catch bugs.  Right now, only tasks threads can exit. */
+       assert(vth->type == VMM_THREAD_TASK);
+
+       acct_thread_blocked((struct vmm_thread*)tth);
+       uthread_cleanup(uth);
+       __free_stack(tth->stacktop, tth->stacksize);
+       free(tth);
+}
+
 static void destroy_guest_thread(struct guest_thread *gth)
 {
        struct ctlr_thread *cth = gth->buddy;
@@ -476,29 +492,19 @@ void start_guest_thread(struct guest_thread *gth)
        enqueue_vmm_thread((struct vmm_thread*)gth);
 }
 
-static void __tth_exit_cb(struct uthread *uthread, void *junk)
-{
-       struct task_thread *tth = (struct task_thread*)uthread;
-
-       acct_thread_blocked((struct vmm_thread*)tth);
-       uthread_cleanup(uthread);
-       __free_stack(tth->stacktop, tth->stacksize);
-       free(tth);
-}
-
 static void __task_thread_run(void)
 {
        struct task_thread *tth = (struct task_thread*)current_uthread;
 
        tth->func(tth->arg);
-       uthread_yield(FALSE, __tth_exit_cb, 0);
+       uth_2ls_thread_exit(0);
 }
 
 struct task_thread *vmm_run_task(struct virtual_machine *vm,
                                  void (*func)(void *), void *arg)
 {
        struct task_thread *tth;
-       struct uth_thread_attr tth_attr = {.want_tls = TRUE};
+       struct uth_thread_attr tth_attr = {.want_tls = TRUE, .detached = TRUE};
 
        tth = (struct task_thread*)alloc_vmm_thread(vm, VMM_THREAD_TASK);
        if (!tth)