fix deletion of pthread tsd keys that still have non-null values stored
[musl] / src / thread / pthread_key_create.c
1 #include "pthread_impl.h"
2
3 volatile size_t __pthread_tsd_size = sizeof(void *) * PTHREAD_KEYS_MAX;
4 void *__pthread_tsd_main[PTHREAD_KEYS_MAX] = { 0 };
5
6 static void (*keys[PTHREAD_KEYS_MAX])(void *);
7
8 static pthread_rwlock_t key_lock = PTHREAD_RWLOCK_INITIALIZER;
9
10 static pthread_key_t next_key;
11
12 static void nodtor(void *dummy)
13 {
14 }
15
16 static void dirty(void *dummy)
17 {
18 }
19
20 struct cleanup_args {
21         pthread_t caller;
22         int ret;
23 };
24
25 static void clean_dirty_tsd_callback(void *p)
26 {
27         struct cleanup_args *args = p;
28         pthread_t self = __pthread_self();
29         pthread_key_t i;
30         for (i=0; i<PTHREAD_KEYS_MAX; i++) {
31                 if (keys[i] == dirty && self->tsd[i])
32                         self->tsd[i] = 0;
33         }
34         /* Arbitrary choice to avoid data race. */
35         if (args->caller == self) args->ret = 0;
36 }
37
38 static int clean_dirty_tsd(void)
39 {
40         struct cleanup_args args = {
41                 .caller = __pthread_self(),
42                 .ret = EAGAIN
43         };
44         __pthread_key_delete_synccall(clean_dirty_tsd_callback, &args);
45         return args.ret;
46 }
47
48 int __pthread_key_create(pthread_key_t *k, void (*dtor)(void *))
49 {
50         pthread_key_t j = next_key;
51         pthread_t self = __pthread_self();
52         int found_dirty = 0;
53
54         /* This can only happen in the main thread before
55          * pthread_create has been called. */
56         if (!self->tsd) self->tsd = __pthread_tsd_main;
57
58         /* Purely a sentinel value since null means slot is free. */
59         if (!dtor) dtor = nodtor;
60
61         pthread_rwlock_wrlock(&key_lock);
62         do {
63                 if (!keys[j]) {
64                         keys[next_key = *k = j] = dtor;
65                         pthread_rwlock_unlock(&key_lock);
66                         return 0;
67                 } else if (keys[j] == dirty) {
68                         found_dirty = 1;
69                 }
70         } while ((j=(j+1)%PTHREAD_KEYS_MAX) != next_key);
71
72         /* It's possible that all slots are in use or __synccall fails. */
73         if (!found_dirty || clean_dirty_tsd()) {
74                 pthread_rwlock_unlock(&key_lock);
75                 return EAGAIN;
76         }
77
78         /* If this point is reached there is necessarily a newly-cleaned
79          * slot to allocate to satisfy the caller's request. Find it and
80          * mark any additional previously-dirty slots clean. */
81         for (j=0; j<PTHREAD_KEYS_MAX; j++) {
82                 if (keys[j] == dirty) {
83                         if (dtor) {
84                                 keys[next_key = *k = j] = dtor;
85                                 dtor = 0;
86                         } else {
87                                 keys[j] = 0;
88                         }
89                 }
90         }
91
92         pthread_rwlock_unlock(&key_lock);
93         return 0;
94 }
95
96 int __pthread_key_delete_impl(pthread_key_t k)
97 {
98         pthread_rwlock_wrlock(&key_lock);
99         keys[k] = dirty;
100         pthread_rwlock_unlock(&key_lock);
101         return 0;
102 }
103
104 void __pthread_tsd_run_dtors()
105 {
106         pthread_t self = __pthread_self();
107         int i, j;
108         for (j=0; self->tsd_used && j<PTHREAD_DESTRUCTOR_ITERATIONS; j++) {
109                 pthread_rwlock_rdlock(&key_lock);
110                 self->tsd_used = 0;
111                 for (i=0; i<PTHREAD_KEYS_MAX; i++) {
112                         void *val = self->tsd[i];
113                         void (*dtor)(void *) = keys[i];
114                         self->tsd[i] = 0;
115                         if (val && dtor && dtor != nodtor && dtor != dirty) {
116                                 pthread_rwlock_unlock(&key_lock);
117                                 dtor(val);
118                                 pthread_rwlock_rdlock(&key_lock);
119                         }
120                 }
121                 pthread_rwlock_unlock(&key_lock);
122         }
123 }
124
125 weak_alias(__pthread_key_create, pthread_key_create);