Add support for pthread_cleanup() routines
authorKevin Klues <klueska@cs.berkeley.edu>
Fri, 4 Sep 2015 01:21:50 +0000 (18:21 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 6 Oct 2015 20:31:50 +0000 (16:31 -0400)
We still don't support pthread_cancel(), but at least now any cleanup
routines will be executed properly upon pthread_exit().
Also, we could consider turning these into macros and removing the
malloc/free calls because these functions are guaranteed by the POSIX
standard to be called in the same lexical scope:

http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cleanup_pop.html

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tests/pthread_cleanup_test.c [new file with mode: 0644]
user/pthread/pthread.c
user/pthread/pthread.h

diff --git a/tests/pthread_cleanup_test.c b/tests/pthread_cleanup_test.c
new file mode 100644 (file)
index 0000000..afe997d
--- /dev/null
@@ -0,0 +1,64 @@
+#include <pthread.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define handle_error_en(en, msg) \
+                 do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
+
+static volatile int done = 0;
+static volatile int cleanup_pop_arg = 0;
+static volatile int cnt = 0;
+
+static void
+cleanup_handler(void *arg)
+{
+       printf("Running Cleanup Handler\n");
+}
+
+static void *
+thread_start(void *arg)
+{
+       time_t start, curr;
+       printf("Pushing Cleanup Handler\n");
+       pthread_cleanup_push(cleanup_handler, NULL);
+       while (!done)
+               cnt++;
+       printf("Popping Cleanup Handler: %d\n", cleanup_pop_arg);
+       pthread_cleanup_pop(cleanup_pop_arg);
+       return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+       pthread_t thr;
+       int s;
+       void *res;
+
+       if (argc == 2) {
+               cleanup_pop_arg = atoi(argv[1]);
+       } else {
+               printf("You must supply either 0 or 1 as an argument to "
+                      "run the pop handler or not.\n");
+               exit(EXIT_FAILURE);
+       }
+
+       /* Start a new thread. */
+       s = pthread_create(&thr, NULL, thread_start, NULL);
+       if (s != 0)
+               handle_error_en(s, "pthread_create");
+
+       /* Allow new thread to run a while, then signal done. */
+       uthread_sleep(2);
+       done = 1;
+
+       s = pthread_join(thr, &res);
+       if (s != 0)
+               handle_error_en(s, "pthread_join");
+
+       printf("Thread terminated normally; cnt = %d\n", cnt);
+       exit(EXIT_SUCCESS);
+}
index ba38dd8..9b8af19 100644 (file)
@@ -35,6 +35,7 @@ struct sysc_mgmt *sysc_mgmt = 0;
 /* Helper / local functions */
 static int get_next_pid(void);
 static inline void spin_to_sleep(unsigned int spins, unsigned int *spun);
+static inline void pthread_exit_no_cleanup(void *ret);
 
 /* Pthread 2LS operations */
 static void pth_sched_entry(void);
@@ -255,7 +256,7 @@ static void __attribute__((noreturn)) pth_sched_entry(void)
 static void __pthread_run(void)
 {
        struct pthread_tcb *me = pthread_self();
-       pthread_exit(me->start_routine(me->arg));
+       pthread_exit_no_cleanup(me->start_routine(me->arg));
 }
 
 /* GIANT WARNING: if you make any changes to this, also change the broadcast
@@ -608,6 +609,7 @@ void __attribute__((constructor)) pthread_lib_init(void)
        assert(t->id == 0);
        t->sched_policy = SCHED_FIFO;
        t->sched_priority = 0;
+       SLIST_INIT(&t->cr_stack);
        /* Put the new pthread (thread0) on the active queue */
        mcs_pdr_lock(&queue_lock);
        threads_active++;
@@ -720,6 +722,7 @@ int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        /* Might override these later, based on attr && EXPLICIT_SCHED */
        pthread->sched_policy = parent->sched_policy;
        pthread->sched_priority = parent->sched_priority;
+       SLIST_INIT(&pthread->cr_stack);
        /* Respect the attributes */
        if (attr) {
                if (attr->stacksize)                                    /* don't set a 0 stacksize */
@@ -855,14 +858,24 @@ static void __pth_exit_cb(struct uthread *uthread, void *junk)
                exit(0);
 }
 
-void pthread_exit(void *ret)
+static inline void pthread_exit_no_cleanup(void *ret)
 {
        struct pthread_tcb *pthread = pthread_self();
        pthread->retval = ret;
        destroy_dtls();
+       while (SLIST_FIRST(&pthread->cr_stack))
+               pthread_cleanup_pop(FALSE);
        uthread_yield(FALSE, __pth_exit_cb, 0);
 }
 
+void pthread_exit(void *ret)
+{
+       struct pthread_tcb *pthread = pthread_self();
+       while (SLIST_FIRST(&pthread->cr_stack))
+               pthread_cleanup_pop(TRUE);
+       pthread_exit_no_cleanup(ret);
+}
+
 /* Callback/bottom half of yield.  For those writing these pth callbacks, the
  * minimum is call generic, set state (communicate with runnable), then do
  * something that causes it to be runnable in the future (or right now). */
@@ -882,6 +895,34 @@ int pthread_yield(void)
        return 0;
 }
 
+int pthread_cancel(pthread_t __th)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}
+
+void pthread_cleanup_push(void (*routine)(void *), void *arg)
+{
+       struct pthread_tcb *p = pthread_self();
+       struct pthread_cleanup_routine *r = malloc(sizeof(*r));
+       r->routine = routine;
+       r->arg = arg;
+       SLIST_INSERT_HEAD(&p->cr_stack, r, cr_next);
+}
+
+void pthread_cleanup_pop(int execute)
+{
+       struct pthread_tcb *p = pthread_self();
+       struct pthread_cleanup_routine *r = SLIST_FIRST(&p->cr_stack);
+       if (r) {
+               SLIST_REMOVE_HEAD(&p->cr_stack, cr_next);
+               if (execute)
+                       r->routine(r->arg);
+               free(r);
+       }
+}
+
 int pthread_mutexattr_init(pthread_mutexattr_t* attr)
 {
   attr->type = PTHREAD_MUTEX_DEFAULT;
@@ -1452,22 +1493,3 @@ int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
        abort();
        return -1;
 }
-
-int pthread_cancel (pthread_t __th)
-{
-       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
-       abort();
-       return -1;
-}
-
-void pthread_cleanup_push(void (*routine)(void *), void *arg)
-{
-       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
-       abort();
-}
-
-void pthread_cleanup_pop(int execute)
-{
-       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
-       abort();
-}
index 826218e..dbd1e32 100644 (file)
@@ -24,6 +24,14 @@ __BEGIN_DECLS
 #define PTH_BLK_MUTEX          8       /* blocked externally, possibly on a mutex */
 #define PTH_BLK_PAUSED         9       /* handed back to us from uthread code */
 
+/* Entry for a pthread_cleanup_routine on the stack of cleanup handlers. */
+struct pthread_cleanup_routine {
+       SLIST_ENTRY(pthread_cleanup_routine) cr_next;
+       void (*routine)(void *);
+       void *arg;
+};
+SLIST_HEAD(pthread_cleanup_stack, pthread_cleanup_routine);
+
 /* Pthread struct.  First has to be the uthread struct, which the vcore code
  * will access directly (as if pthread_tcb is a struct uthread). */
 struct pthread_tcb;
@@ -48,6 +56,7 @@ struct pthread_tcb {
        struct sigdata *sigdata;
        int sched_policy;
        int sched_priority;             /* careful, GNU #defines this to __sched_priority */
+       struct pthread_cleanup_stack cr_stack;
 };
 typedef struct pthread_tcb* pthread_t;
 SLIST_HEAD(pthread_list, pthread_tcb);
@@ -233,6 +242,9 @@ int pthread_equal(pthread_t __thread1, pthread_t __thread2);
 int pthread_getattr_np(pthread_t __th, pthread_attr_t *__attr);
 int pthread_attr_getstack(const pthread_attr_t *__attr,
                            void **__stackaddr, size_t *__stacksize);
+int pthread_cancel(pthread_t __th);
+void pthread_cleanup_push(void (*routine)(void *), void *arg);
+void pthread_cleanup_pop(int execute);
 
 /* Scheduling Stuff, mostly ignored by the actual 2LS */
 int pthread_attr_setschedparam(pthread_attr_t *attr,
@@ -268,9 +280,6 @@ extern int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
                    pthread_mutex_t *__restrict __mutex,
                    const struct timespec *__restrict __abstime)
      __nonnull ((1, 2, 3));
-extern int pthread_cancel (pthread_t __th);
-void pthread_cleanup_push(void (*routine)(void *), void *arg);
-void pthread_cleanup_pop(int execute);
 
 __END_DECLS