diff options
Diffstat (limited to 'linuxthreads')
-rw-r--r-- | linuxthreads/ChangeLog | 18 | ||||
-rw-r--r-- | linuxthreads/Examples/ex18.c | 112 | ||||
-rw-r--r-- | linuxthreads/Makefile | 2 | ||||
-rw-r--r-- | linuxthreads/internals.h | 6 | ||||
-rw-r--r-- | linuxthreads/manager.c | 21 | ||||
-rw-r--r-- | linuxthreads/specific.c | 66 |
6 files changed, 206 insertions, 19 deletions
diff --git a/linuxthreads/ChangeLog b/linuxthreads/ChangeLog index 307388b6f6..dcc18cc6e6 100644 --- a/linuxthreads/ChangeLog +++ b/linuxthreads/ChangeLog @@ -1,3 +1,21 @@ +2001-11-28 Kaz Kylheku <kaz@ashi.footprints.net> + + Bugfix to pthread_key_delete. It was iterating over the thread + manager's linked list of threads, behind the thread manager's + back causing a race. The fix is to have the manager iterate over + the threads instead, using a new request type for doing so. + * internals.h (struct pthread_request): New manager request type + REQ_FOR_EACH_THREAD. + * manager.c (pthread_for_each_thread): New function. + (__pthread_manager): Handle new REQ_FOR_EACH_THREAD request. + * specific.c (struct pthread_key_delete_helper_args): New type. + (pthread_key_delete_helper): New static function. + (pthread_key_delete): Use the new thread manager + REQ_FOR_EACH_THREAD function to iterate over the threads and set + the delete key slot to a null value in each thread. + * Examples/ex18.c: New test. + * Makefile (tests): Add ex18. + 2001-11-22 Wolfram Gloger <wg@malloc.de> * pthread.c (pthread_onexit_process): Don't call free diff --git a/linuxthreads/Examples/ex18.c b/linuxthreads/Examples/ex18.c new file mode 100644 index 0000000000..642f6b44cd --- /dev/null +++ b/linuxthreads/Examples/ex18.c @@ -0,0 +1,112 @@ +/* + * Beat up the pthread_key_create and pthread_key_delete + * functions. + */ + +#if 0 +#define CHATTY +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <pthread.h> + +const int beatup_iterations = 10000; +const int num_threads = 30; +const int max_keys = 500; + +struct key_list { + struct key_list *next; + pthread_key_t key; +}; + +struct key_list *key_list; +pthread_mutex_t key_lock = PTHREAD_MUTEX_INITIALIZER; + +/* + * Create a new key and put it at the tail of a linked list. + * If the linked list grows to a certain length, delete a key from the + * head of * the list. + */ + +static void +beat_up(void) +{ + struct key_list *new = malloc(sizeof *new); + struct key_list **iter, *old_key = 0; + int key_count = 0; + + if (new == 0) { + fprintf(stderr, "malloc failed\n"); + abort(); + } + + new->next = 0; + + if (pthread_key_create(&new->key, 0) != 0) { + fprintf(stderr, "pthread_key_create failed\n"); + abort(); + } + + if (pthread_getspecific(new->key) != 0) { + fprintf(stderr, "new pthread_key_t resolves to non-null value\n"); + abort(); + } + + pthread_setspecific(new->key, (void *) 1); + +#ifdef CHATTY + printf("created key\n"); +#endif + + pthread_mutex_lock(&key_lock); + + for (iter = &key_list; *iter != 0; iter = &(*iter)->next) + key_count++; + + *iter = new; + + if (key_count > max_keys) { + old_key = key_list; + key_list = key_list->next; + } + + pthread_mutex_unlock(&key_lock); + + if (old_key != 0) { +#ifdef CHATTY + printf("deleting key\n"); +#endif + pthread_key_delete(old_key->key); + } +} + +static void * +thread(void *arg) +{ + int i; + for (i = 0; i < beatup_iterations; i++) + beat_up(); + return 0; +} + +int +main(void) +{ + int i; + pthread_attr_t detached_thread; + + pthread_attr_init(&detached_thread); + pthread_attr_setdetachstate(&detached_thread, PTHREAD_CREATE_DETACHED); + + for (i = 0; i < num_threads; i++) { + pthread_t thread_id; + while (pthread_create(&thread_id, &detached_thread, thread, 0) == EAGAIN) { + /* let some threads die, so system can breathe. :) */ + sleep(1); + } + } + + pthread_exit(0); +} diff --git a/linuxthreads/Makefile b/linuxthreads/Makefile index b57abd13d3..cf01102db0 100644 --- a/linuxthreads/Makefile +++ b/linuxthreads/Makefile @@ -60,7 +60,7 @@ endif librt-tests = ex10 ex11 tests = ex1 ex2 ex3 ex4 ex5 ex6 ex7 ex8 ex9 $(librt-tests) ex12 ex13 joinrace \ tststack $(tests-nodelete-$(have-z-nodelete)) ecmutex ex14 ex15 ex16 \ - ex17 tst-cancel tst-context + ex17 ex18 tst-cancel tst-context ifeq (yes,$(build-shared)) tests-nodelete-yes = unload diff --git a/linuxthreads/internals.h b/linuxthreads/internals.h index ae49266278..8297c781c8 100644 --- a/linuxthreads/internals.h +++ b/linuxthreads/internals.h @@ -208,7 +208,7 @@ struct pthread_request { pthread_descr req_thread; /* Thread doing the request */ enum { /* Request kind */ REQ_CREATE, REQ_FREE, REQ_PROCESS_EXIT, REQ_MAIN_THREAD_EXIT, - REQ_POST, REQ_DEBUG, REQ_KICK + REQ_POST, REQ_DEBUG, REQ_KICK, REQ_FOR_EACH_THREAD } req_kind; union { /* Arguments for request */ struct { /* For REQ_CREATE: */ @@ -224,6 +224,10 @@ struct pthread_request { int code; /* exit status */ } exit; void * post; /* For REQ_POST: the semaphore */ + struct { /* For REQ_FOR_EACH_THREAD: callback */ + void (*fn)(void *, pthread_descr); + void *arg; + } for_each; } req_args; }; diff --git a/linuxthreads/manager.c b/linuxthreads/manager.c index 0872146e3f..b1a4542d69 100644 --- a/linuxthreads/manager.c +++ b/linuxthreads/manager.c @@ -100,6 +100,8 @@ static void pthread_handle_exit(pthread_descr issuing_thread, int exitcode) __attribute__ ((noreturn)); static void pthread_reap_children(void); static void pthread_kill_all_threads(int sig, int main_thread_also); +static void pthread_for_each_thread(void *arg, + void (*fn)(void *, pthread_descr)); /* The server thread managing requests for thread creation and termination */ @@ -211,6 +213,11 @@ __pthread_manager(void *arg) /* This is just a prod to get the manager to reap some threads right away, avoiding a potential delay at shutdown. */ break; + case REQ_FOR_EACH_THREAD: + pthread_for_each_thread(request.req_args.for_each.arg, + request.req_args.for_each.fn); + restart(request.req_thread); + break; } } } @@ -902,6 +909,20 @@ static void pthread_kill_all_threads(int sig, int main_thread_also) } } +static void pthread_for_each_thread(void *arg, + void (*fn)(void *, pthread_descr)) +{ + pthread_descr th; + + for (th = __pthread_main_thread->p_nextlive; + th != __pthread_main_thread; + th = th->p_nextlive) { + fn(arg, th); + } + + fn(arg, __pthread_main_thread); +} + /* Process-wide exit() */ static void pthread_handle_exit(pthread_descr issuing_thread, int exitcode) diff --git a/linuxthreads/specific.c b/linuxthreads/specific.c index a7fa8ff5b7..1a0cab10b6 100644 --- a/linuxthreads/specific.c +++ b/linuxthreads/specific.c @@ -20,6 +20,7 @@ #include "pthread.h" #include "internals.h" #include "spinlock.h" +#include "restart.h" #include <bits/libc-lock.h> @@ -58,13 +59,38 @@ int __pthread_key_create(pthread_key_t * key, destr_function destr) } strong_alias (__pthread_key_create, pthread_key_create) -/* Delete a key */ +/* Reset deleted key's value to NULL in each live thread. + * NOTE: this executes in the context of the thread manager! */ + +struct pthread_key_delete_helper_args { + /* Damn, we need lexical closures in C! ;) */ + unsigned int idx1st, idx2nd; + pthread_descr self; +}; + +static void pthread_key_delete_helper(void *arg, pthread_descr th) +{ + struct pthread_key_delete_helper_args *args = arg; + unsigned int idx1st = args->idx1st; + unsigned int idx2nd = args->idx2nd; + pthread_descr self = args->self; + + if (self == 0) + self = args->self = thread_self(); + + if (!th->p_terminated) { + /* pthread_exit() may try to free th->p_specific[idx1st] concurrently. */ + __pthread_lock(THREAD_GETMEM(th, p_lock), self); + if (th->p_specific[idx1st] != NULL) + th->p_specific[idx1st][idx2nd] = NULL; + __pthread_unlock(THREAD_GETMEM(th, p_lock)); + } +} +/* Delete a key */ int pthread_key_delete(pthread_key_t key) { pthread_descr self = thread_self(); - pthread_descr th; - unsigned int idx1st, idx2nd; pthread_mutex_lock(&pthread_keys_mutex); if (key >= PTHREAD_KEYS_MAX || !pthread_keys[key].in_use) { @@ -73,23 +99,29 @@ int pthread_key_delete(pthread_key_t key) } pthread_keys[key].in_use = 0; pthread_keys[key].destr = NULL; + /* Set the value of the key to NULL in all running threads, so that if the key is reallocated later by pthread_key_create, its associated values will be NULL in all threads. */ - idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE; - idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE; - th = self; - do { - /* If the thread already is terminated don't modify the memory. */ - if (!th->p_terminated) { - /* pthread_exit() may try to free th->p_specific[idx1st] concurrently. */ - __pthread_lock(THREAD_GETMEM(th, p_lock), self); - if (th->p_specific[idx1st] != NULL) - th->p_specific[idx1st][idx2nd] = NULL; - __pthread_unlock(THREAD_GETMEM(th, p_lock)); - } - th = th->p_nextlive; - } while (th != self); + + { + struct pthread_key_delete_helper_args args; + struct pthread_request request; + + args.idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE; + args.idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE; + args.self = 0; + + request.req_thread = self; + request.req_kind = REQ_FOR_EACH_THREAD; + request.req_args.for_each.arg = &args; + request.req_args.for_each.fn = pthread_key_delete_helper; + + TEMP_FAILURE_RETRY(__libc_write(__pthread_manager_request, + (char *) &request, sizeof(request))); + suspend(self); + } + pthread_mutex_unlock(&pthread_keys_mutex); return 0; } |