diff --git a/sys/posix/pthread/include/pthread.h b/sys/posix/pthread/include/pthread.h index 899f2ee5e4..69488c926b 100644 --- a/sys/posix/pthread/include/pthread.h +++ b/sys/posix/pthread/include/pthread.h @@ -96,4 +96,6 @@ void pthread_testcancel(void); #include "pthread_barrier.h" +#include "pthread_cleanup.h" + #endif /* pthread.h */ diff --git a/sys/posix/pthread/include/pthread_cleanup.h b/sys/posix/pthread/include/pthread_cleanup.h new file mode 100644 index 0000000000..8a2da6fa98 --- /dev/null +++ b/sys/posix/pthread/include/pthread_cleanup.h @@ -0,0 +1,69 @@ +/** + * @brief Internal structure for pthread_cleanup_push() + */ +typedef struct __pthread_cleanup_datum +{ + /** Cleanup handler to call next. */ + struct __pthread_cleanup_datum *__next; + + /** Cleanup routine to call. */ + void (*__routine)(void *arg); + + /** Argument to supply. */ + void *__arg; +} __pthread_cleanup_datum_t; + +/** + * @brief Internal function to be called by pthread_cleanup_push() + * @details The previous top of the stack gets stored in `datum->next`. + * @param[in] datum Allocated in the stack, datum tells how to cleanup. + */ +void __pthread_cleanup_push(__pthread_cleanup_datum_t *datum); + +/** + * @brief Internal function to be called by pthread_cleanup_push() + * @details This function leaves the cleanup frame that was opened with __pthread_cleanup_push(). + * @param[in] datum Parameter that was supplied to __pthread_cleanup_push(). + * @param[in] execute Iff `!= 0` call cleanup handler. + */ +void __pthread_cleanup_pop(__pthread_cleanup_datum_t *datum, int execute); + +/* + * Notice: `pthread_cleanup_*` has to be defined as a macro, because the cleanup + * stack needs extra data. The standard explicitly allows defining these + * functions as macros. The alternative would be malloc. + */ + +/** + * @brief Open a cleanup frame. + * @details This function must be paired with pthread_cleanup_pop(). + * @details The cleanup function is called if the current thread exists + * inside the frame by means of pthread_exit(), or if the thread + * was cancelled. + * @details You must not `return`, `continue`, or `break` out of the cleanup frame. + * @details The frame opens a scope. Variables declared inside this scope + * won't be visible outside. + * @param[in] ROUTINE Function to call on cleanup. + * @param[in] ARG Argument to supply to the cleanup handler. + */ +#define pthread_cleanup_push(ROUTINE, ARG) \ + do { \ + __extension__ __pthread_cleanup_datum_t ____datum__ = { \ + .__routine = (ROUTINE), \ + .__arg = (ARG), \ + }; \ + __extension__ int ____execute__ = 1; \ + __pthread_cleanup_push(&____datum__); \ + do { \ + do { } while (0) + +/** + * @brief Closes a cleaup frame + * @details Must be paired with pthread_cleanup_push(). + * @param[in] EXECUTE Iff `!= 0` call cleanup handler. + */ +#define pthread_cleanup_pop(EXECUTE) \ + ____execute__ = (EXECUTE); \ + } while (0); \ + __pthread_cleanup_pop(&____datum__, ____execute__); \ + } while (0) diff --git a/sys/posix/pthread/pthread.c b/sys/posix/pthread/pthread.c index 5f149dcbcc..904a566236 100644 --- a/sys/posix/pthread/pthread.c +++ b/sys/posix/pthread/pthread.c @@ -62,6 +62,8 @@ typedef struct pthread_thread { void *arg; char *stack; + + __pthread_cleanup_datum_t *cleanup_top; } pthread_thread_t; static pthread_thread_t *volatile pthread_sched_threads[MAXTHREADS]; @@ -160,6 +162,14 @@ int pthread_create(pthread_t *newthread, const pthread_attr_t *attr, void *(*sta void pthread_exit(void *retval) { pthread_thread_t *self = pthread_sched_threads[pthread_self()]; + + while (self->cleanup_top) { + __pthread_cleanup_datum_t *ct = self->cleanup_top; + self->cleanup_top = ct->__next; + + ct->__routine(ct->__arg); + } + self->thread_pid = -1; DEBUG("pthread_exit(%p), self == %p\n", retval, (void *) self); if (self->status != PTS_DETACHED) { @@ -282,3 +292,23 @@ void pthread_testcancel(void) pthread_exit(NULL); } } + +void __pthread_cleanup_push(__pthread_cleanup_datum_t *datum) +{ + pthread_thread_t *self = pthread_sched_threads[pthread_self()]; + datum->__next = self->cleanup_top; + self->cleanup_top = datum; +} + +void __pthread_cleanup_pop(__pthread_cleanup_datum_t *datum, int execute) +{ + pthread_thread_t *self = pthread_sched_threads[pthread_self()]; + self->cleanup_top = datum->__next; + + if (execute != 0) { + /* "The pthread_cleanup_pop() function shall remove the routine at the + * top of the calling thread's cancellation cleanup stack and optionally + * invoke it (if execute is non-zero)." */ + datum->__routine(datum->__arg); + } +} diff --git a/tests/test_pthread_cleanup/Makefile b/tests/test_pthread_cleanup/Makefile new file mode 100644 index 0000000000..a3a4990ff5 --- /dev/null +++ b/tests/test_pthread_cleanup/Makefile @@ -0,0 +1,6 @@ +PROJECT = test_pthread_cleanup +include ../Makefile.tests_common + +USEMODULE += pthread + +include $(RIOTBASE)/Makefile.include diff --git a/tests/test_pthread_cleanup/main.c b/tests/test_pthread_cleanup/main.c new file mode 100644 index 0000000000..76df125396 --- /dev/null +++ b/tests/test_pthread_cleanup/main.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief pthread test application + * + * @author René Kijewski + * + * @} + */ + +#include +#include "pthread.h" + +#define RET_EXIT ((void *) 1234) +#define RET_FAIL ((void *) 5678) + +static void cleanup(void *arg) +{ + printf("Cleanup: <%s>\n", (const char *) arg); +} + +static void *run(void *unused) { + (void) unused; + + /* indentation for visibility */ + puts(""); + pthread_cleanup_push(cleanup, "1"); + puts(""); + pthread_cleanup_push(cleanup, "2"); + puts(""); + pthread_cleanup_push(cleanup, "3"); + puts(""); + pthread_cleanup_push(cleanup, "4"); + puts(""); + pthread_cleanup_push(cleanup, "5"); + puts(""); + pthread_cleanup_pop(1); + puts(""); + pthread_cleanup_pop(0); /* cleanup 4 should not be executed */ + puts(""); + pthread_cleanup_pop(1); + pthread_exit(RET_EXIT); + puts("/"); /* thread exited, should not be printed */ + pthread_cleanup_pop(0); /* should be printed nevertheless */ + puts(""); + pthread_cleanup_pop(1); + puts(""); + + return RET_FAIL; +} + +int main(void) { + puts("Start."); + + pthread_t th_id; + pthread_create(&th_id, NULL, run, NULL); + + void *res; + pthread_join(th_id, (void **) &res); + + printf("Result: %u\n", (int) (intptr_t) res); + puts("Done."); + return 0; +} + diff --git a/tests/test_pthread_cleanup/tests/01-test b/tests/test_pthread_cleanup/tests/01-test new file mode 100755 index 0000000000..6e7b30b030 --- /dev/null +++ b/tests/test_pthread_cleanup/tests/01-test @@ -0,0 +1,87 @@ +#!/usr/bin/env expect + +set timeout 5 + +set pid [spawn make term] +puts "-*- Spawened $pid -*-\n" + +set once 0 +set result 1 +while { $once == 0 } { + set once 1 + + expect { + "Start." {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + expect { + "Cleanup: <5>" {} + timeout { break } + } + expect { + "" {} + timeout { break } + } + # Cleanup 4 has execute == 0. + expect { + "" {} + timeout { break } + } + # pthread_exit is called here + expect { + "Cleanup: <3>" {} + timeout { break } + } + expect { + "Cleanup: <2>" {} + timeout { break } + } + expect { + "Cleanup: <1>" {} + timeout { break } + } + expect { + "Result: 1234" {} + timeout { break } + } + expect { + "Done." {} + timeout { break } + } + + set result 0 +} + +if { $result == 0 } { + puts "\n-*- Test successful! -*-\n" +} else { + puts "\n-*- TEST HAD ERRORS! -*-\n" +} +spawn kill -9 $pid +wait +close +exit $result