summaryrefslogtreecommitdiffstats
path: root/libc/bionic/pthread_key.cpp
blob: 27eab2706d722f9b1ebe1f55c3c81ba6048def93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * Copyright (C) 2008 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <pthread.h>

#include "private/bionic_tls.h"
#include "pthread_internal.h"

/* A technical note regarding our thread-local-storage (TLS) implementation:
 *
 * There can be up to BIONIC_TLS_SLOTS independent TLS keys in a given process,
 * The keys below TLS_SLOT_FIRST_USER_SLOT are reserved for Bionic to hold
 * special thread-specific variables like errno or a pointer to
 * the current thread's descriptor. These entries cannot be accessed through
 * pthread_getspecific() / pthread_setspecific() or pthread_key_delete()
 *
 * The 'tls_map_t' type defined below implements a shared global map of
 * currently created/allocated TLS keys and the destructors associated
 * with them.
 *
 * The global TLS map simply contains a bitmap of allocated keys, and
 * an array of destructors.
 *
 * Each thread has a TLS area that is a simple array of BIONIC_TLS_SLOTS void*
 * pointers. the TLS area of the main thread is stack-allocated in
 * __libc_init_common, while the TLS area of other threads is placed at
 * the top of their stack in pthread_create.
 *
 * When pthread_key_delete() is called it will erase the key's bitmap bit
 * and its destructor, and will also clear the key data in the TLS area of
 * all created threads. As mandated by Posix, it is the responsibility of
 * the caller of pthread_key_delete() to properly reclaim the objects that
 * were pointed to by these data fields (either before or after the call).
 */

#define TLSMAP_BITS       32
#define TLSMAP_WORDS      ((BIONIC_TLS_SLOTS+TLSMAP_BITS-1)/TLSMAP_BITS)
#define TLSMAP_WORD(m,k)  (m).map[(k)/TLSMAP_BITS]
#define TLSMAP_MASK(k)    (1U << ((k)&(TLSMAP_BITS-1)))

static inline bool IsValidUserKey(pthread_key_t key) {
  return (key >= TLS_SLOT_FIRST_USER_SLOT && key < BIONIC_TLS_SLOTS);
}

typedef void (*key_destructor_t)(void*);

struct tls_map_t {
  bool is_initialized;

  /* bitmap of allocated keys */
  uint32_t map[TLSMAP_WORDS];

  key_destructor_t key_destructors[BIONIC_TLS_SLOTS];
};

class ScopedTlsMapAccess {
 public:
  ScopedTlsMapAccess() {
    Lock();

    // If this is the first time the TLS map has been accessed,
    // mark the slots belonging to well-known keys as being in use.
    // This isn't currently necessary because the well-known keys
    // can only be accessed directly by bionic itself, do not have
    // destructors, and all the functions that touch the TLS map
    // start after the maximum well-known slot.
    if (!s_tls_map_.is_initialized) {
      for (pthread_key_t key = 0; key < TLS_SLOT_FIRST_USER_SLOT; ++key) {
        SetInUse(key, NULL);
      }
      s_tls_map_.is_initialized = true;
    }
  }

  ~ScopedTlsMapAccess() {
    Unlock();
  }

  int CreateKey(pthread_key_t* result, void (*key_destructor)(void*)) {
    // Take the first unallocated key.
    for (int key = 0; key < BIONIC_TLS_SLOTS; ++key) {
      if (!IsInUse(key)) {
        SetInUse(key, key_destructor);
        *result = key;
        return 0;
      }
    }

    // We hit PTHREAD_KEYS_MAX. POSIX says EAGAIN for this case.
    return EAGAIN;
  }

  void DeleteKey(pthread_key_t key) {
    TLSMAP_WORD(s_tls_map_, key) &= ~TLSMAP_MASK(key);
    s_tls_map_.key_destructors[key] = NULL;
  }

  bool IsInUse(pthread_key_t key) {
    return (TLSMAP_WORD(s_tls_map_, key) & TLSMAP_MASK(key)) != 0;
  }

  void SetInUse(pthread_key_t key, void (*key_destructor)(void*)) {
    TLSMAP_WORD(s_tls_map_, key) |= TLSMAP_MASK(key);
    s_tls_map_.key_destructors[key] = key_destructor;
  }

  // Called from pthread_exit() to remove all TLS key data
  // from this thread's TLS area. This must call the destructor of all keys
  // that have a non-NULL data value and a non-NULL destructor.
  void CleanAll() {
    void** tls = __get_tls();

    // Because destructors can do funky things like deleting/creating other
    // keys, we need to implement this in a loop.
    for (int rounds = PTHREAD_DESTRUCTOR_ITERATIONS; rounds > 0; --rounds) {
      size_t called_destructor_count = 0;
      for (int key = 0; key < BIONIC_TLS_SLOTS; ++key) {
        if (IsInUse(key)) {
          void* data = tls[key];
          void (*key_destructor)(void*) = s_tls_map_.key_destructors[key];

          if (data != NULL && key_destructor != NULL) {
            // we need to clear the key data now, this will prevent the
            // destructor (or a later one) from seeing the old value if
            // it calls pthread_getspecific() for some odd reason

            // we do not do this if 'key_destructor == NULL' just in case another
            // destructor function might be responsible for manually
            // releasing the corresponding data.
            tls[key] = NULL;

            // because the destructor is free to call pthread_key_create
            // and/or pthread_key_delete, we need to temporarily unlock
            // the TLS map
            Unlock();
            (*key_destructor)(data);
            Lock();
            ++called_destructor_count;
          }
        }
      }

      // If we didn't call any destructors, there is no need to check the TLS data again.
      if (called_destructor_count == 0) {
        break;
      }
    }
  }

 private:
  static tls_map_t s_tls_map_;
  static pthread_mutex_t s_tls_map_lock_;

  void Lock() {
    pthread_mutex_lock(&s_tls_map_lock_);
  }

  void Unlock() {
    pthread_mutex_unlock(&s_tls_map_lock_);
  }
};

__LIBC_HIDDEN__ tls_map_t ScopedTlsMapAccess::s_tls_map_;
__LIBC_HIDDEN__ pthread_mutex_t ScopedTlsMapAccess::s_tls_map_lock_;

__LIBC_HIDDEN__ void pthread_key_clean_all() {
  ScopedTlsMapAccess tls_map;
  tls_map.CleanAll();
}

int pthread_key_create(pthread_key_t* key, void (*key_destructor)(void*)) {
  ScopedTlsMapAccess tls_map;
  return tls_map.CreateKey(key, key_destructor);
}

// Deletes a pthread_key_t. note that the standard mandates that this does
// not call the destructors for non-NULL key values. Instead, it is the
// responsibility of the caller to properly dispose of the corresponding data
// and resources, using any means it finds suitable.
int pthread_key_delete(pthread_key_t key) {
  ScopedTlsMapAccess tls_map;

  if (!IsValidUserKey(key) || !tls_map.IsInUse(key)) {
    return EINVAL;
  }

  // Clear value in all threads.
  pthread_mutex_lock(&g_thread_list_lock);
  for (pthread_internal_t*  t = g_thread_list; t != NULL; t = t->next) {
    // Skip zombie threads. They don't have a valid TLS area any more.
    // Similarly, it is possible to have t->tls == NULL for threads that
    // were just recently created through pthread_create() but whose
    // startup trampoline (__pthread_start) hasn't been run yet by the
    // scheduler. t->tls will also be NULL after a thread's stack has been
    // unmapped but before the ongoing pthread_join() is finished.
    if (t->tid == 0 || t->tls == NULL) {
      continue;
    }

    t->tls[key] = NULL;
  }
  tls_map.DeleteKey(key);

  pthread_mutex_unlock(&g_thread_list_lock);
  return 0;
}

void* pthread_getspecific(pthread_key_t key) {
  if (!IsValidUserKey(key)) {
    return NULL;
  }

  // For performance reasons, we do not lock/unlock the global TLS map
  // to check that the key is properly allocated. If the key was not
  // allocated, the value read from the TLS should always be NULL
  // due to pthread_key_delete() clearing the values for all threads.
  return __get_tls()[key];
}

int pthread_setspecific(pthread_key_t key, const void* ptr) {
  ScopedTlsMapAccess tls_map;

  if (!IsValidUserKey(key) || !tls_map.IsInUse(key)) {
    return EINVAL;
  }

  __get_tls()[key] = const_cast<void*>(ptr);
  return 0;
}