From b54962689a9ba8c2b06e7b693099df715e1cd70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sat, 1 Mar 2014 00:03:56 +0100 Subject: [PATCH] posix: Add pthread_cleanup handlers With `pthread_cleanup_(push|pop)` you can define a function that should be ran if the thread is exited while it is inside this scope. A thread can be ended here through an explicit call to `pthread_exit()`, or if cancellation was requested and a cancellation point was hit. `pthread_cleanup_*` is mostly only useful together with cancellation points, and cancellation points are only useful with a cleanup functionality. Cancellation points are at least partially implemented by means of `pthread_testcancel()`. C.f. ["Cancellation Points"][1]. [1]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_05_02 --- sys/posix/pthread/include/pthread.h | 36 ++++++++++ sys/posix/pthread/include/pthreadtypes.h | 7 ++ sys/posix/pthread/pthread.c | 30 ++++++++ tests/test_pthread_cleanup/Makefile | 12 ++++ tests/test_pthread_cleanup/main.c | 75 ++++++++++++++++++++ tests/test_pthread_cleanup/tests/01-test | 87 ++++++++++++++++++++++++ 6 files changed, 247 insertions(+) create mode 100644 tests/test_pthread_cleanup/Makefile create mode 100644 tests/test_pthread_cleanup/main.c create mode 100755 tests/test_pthread_cleanup/tests/01-test diff --git a/sys/posix/pthread/include/pthread.h b/sys/posix/pthread/include/pthread.h index 899f2ee5e4..5362f70a95 100644 --- a/sys/posix/pthread/include/pthread.h +++ b/sys/posix/pthread/include/pthread.h @@ -88,6 +88,42 @@ int pthread_cancel(pthread_t th); cancelled. */ void pthread_testcancel(void); +void __pthread_cleanup_push(__pthread_cleanup_datum_t *datum); +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. + */ + +/* The pthread_cleanup_push() function shall push the specified cancellation + * cleanup handler routine onto the calling thread's cancellation cleanup stack. + * The cancellation cleanup handler shall be popped from the cancellation + * cleanup stack and invoked with the argument arg when: + * The thread exits (that is, calls pthread_exit()). + * The thread acts upon a cancellation request. + * The thread calls pthread_cleanup_pop() with a non-zero execute argument. */ +#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) + +/* 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). */ +#define pthread_cleanup_pop(EXECUTE) \ + ____execute__ = (EXECUTE); \ + } while (0); \ + __pthread_cleanup_pop(&____datum__, ____execute__); \ + } while (0) + #include "pthread_mutex.h" #include "pthread_rwlock.h" diff --git a/sys/posix/pthread/include/pthreadtypes.h b/sys/posix/pthread/include/pthreadtypes.h index 3fdf0c7ad8..39a07b39d6 100644 --- a/sys/posix/pthread/include/pthreadtypes.h +++ b/sys/posix/pthread/include/pthreadtypes.h @@ -33,4 +33,11 @@ typedef unsigned long int pthread_rwlockattr_t; typedef volatile int pthread_spinlock_t; +typedef struct __pthread_cleanup_datum +{ + struct __pthread_cleanup_datum *__next; + void (*__routine)(void *arg); + void *__arg; +} __pthread_cleanup_datum_t; + #endif /* PTHREADTYPES_H_ */ 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..174f79c860 --- /dev/null +++ b/tests/test_pthread_cleanup/Makefile @@ -0,0 +1,12 @@ +PROJECT = test_pthread_cleanup +include ../Makefile.tests_common + +USEMODULE += pthread + +ifeq ($(BOARD),native) + CFLAGS += -isystem $(RIOTBASE)/sys/posix/pthread/include +else + INCLUDES += -I$(RIOTBASE)/sys/posix/pthread/include +endif + +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