1 /* Thread-local storage (native Windows implementation).
2 Copyright (C) 2005-2020 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Written by Bruno Haible <bruno@clisp.org>, 2005. */
18
19 #include <config.h>
20
21 /* Specification. */
22 #include "windows-tls.h"
23
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdlib.h>
27
28 #include "windows-once.h"
29
30 void *
glwthread_tls_get(glwthread_tls_key_t key)31 glwthread_tls_get (glwthread_tls_key_t key)
32 {
33 return TlsGetValue (key);
34 }
35
36 int
glwthread_tls_set(glwthread_tls_key_t key,void * value)37 glwthread_tls_set (glwthread_tls_key_t key, void *value)
38 {
39 if (!TlsSetValue (key, value))
40 return EINVAL;
41 return 0;
42 }
43
44 /* The following variables keep track of TLS keys with non-NULL destructor. */
45
46 static glwthread_once_t dtor_table_init_once = GLWTHREAD_ONCE_INIT;
47
48 static CRITICAL_SECTION dtor_table_lock;
49
50 struct dtor { glwthread_tls_key_t key; void (*destructor) (void *); };
51
52 /* The table of dtors. */
53 static struct dtor *dtor_table;
54 /* Number of active entries in the dtor_table. */
55 static unsigned int dtors_count;
56 /* Valid indices into dtor_table are 0..dtors_used-1. */
57 static unsigned int dtors_used;
58 /* Allocation size of dtor_table. */
59 static unsigned int dtors_allocated;
60 /* Invariant: 0 <= dtors_count <= dtors_used <= dtors_allocated. */
61
62 /* Number of threads that are currently processing destructors. */
63 static unsigned int dtor_processing_threads;
64
65 static void
dtor_table_initialize(void)66 dtor_table_initialize (void)
67 {
68 InitializeCriticalSection (&dtor_table_lock);
69 /* The other variables are already initialized to NULL or 0, respectively. */
70 }
71
72 static void
dtor_table_ensure_initialized(void)73 dtor_table_ensure_initialized (void)
74 {
75 glwthread_once (&dtor_table_init_once, dtor_table_initialize);
76 }
77
78 /* Shrinks dtors_used down to dtors_count, by replacing inactive entries
79 with active ones. */
80 static void
dtor_table_shrink_used(void)81 dtor_table_shrink_used (void)
82 {
83 unsigned int i = 0;
84 unsigned int j = dtors_used;
85
86 for (;;)
87 {
88 BOOL i_found = FALSE;
89 BOOL j_found = FALSE;
90 /* Find the next inactive entry, from the left. */
91 for (; i < dtors_count;)
92 {
93 if (dtor_table[i].destructor == NULL)
94 {
95 i_found = TRUE;
96 break;
97 }
98 i++;
99 }
100
101 /* Find the next active entry, from the right. */
102 for (; j > dtors_count;)
103 {
104 j--;
105 if (dtor_table[j].destructor != NULL)
106 {
107 j_found = TRUE;
108 break;
109 }
110 }
111
112 if (i_found != j_found)
113 /* dtors_count was apparently wrong. */
114 abort ();
115
116 if (!i_found)
117 break;
118
119 /* i_found and j_found are TRUE. Swap the two entries. */
120 dtor_table[i] = dtor_table[j];
121
122 i++;
123 }
124
125 dtors_used = dtors_count;
126 }
127
128 void
glwthread_tls_process_destructors(void)129 glwthread_tls_process_destructors (void)
130 {
131 unsigned int repeat;
132
133 dtor_table_ensure_initialized ();
134
135 EnterCriticalSection (&dtor_table_lock);
136 if (dtor_processing_threads == 0)
137 {
138 /* Now it's the appropriate time for shrinking dtors_used. */
139 if (dtors_used > dtors_count)
140 dtor_table_shrink_used ();
141 }
142 dtor_processing_threads++;
143
144 for (repeat = GLWTHREAD_DESTRUCTOR_ITERATIONS; repeat > 0; repeat--)
145 {
146 unsigned int destructors_run = 0;
147
148 /* Iterate across dtor_table. We don't need to make a copy of dtor_table,
149 because
150 * When another thread calls glwthread_tls_key_create with a non-NULL
151 destructor argument, this will possibly reallocate the dtor_table
152 array and increase dtors_allocated as well as dtors_used and
153 dtors_count, but it will not change dtors_used nor the contents of
154 the first dtors_used entries of dtor_table.
155 * When another thread calls glwthread_tls_key_delete, this will
156 possibly set some 'destructor' member to NULL, thus marking an
157 entry as inactive, but it will not otherwise change dtors_used nor
158 the contents of the first dtors_used entries of dtor_table. */
159 unsigned int i_limit = dtors_used;
160 unsigned int i;
161
162 for (i = 0; i < i_limit; i++)
163 {
164 struct dtor current = dtor_table[i];
165 if (current.destructor != NULL)
166 {
167 /* The current dtor has not been deleted yet. */
168 void *current_value = glwthread_tls_get (current.key);
169 if (current_value != NULL)
170 {
171 /* The current value is non-NULL. Run the destructor. */
172 glwthread_tls_set (current.key, NULL);
173 LeaveCriticalSection (&dtor_table_lock);
174 current.destructor (current_value);
175 EnterCriticalSection (&dtor_table_lock);
176 destructors_run++;
177 }
178 }
179 }
180
181 /* When all TLS values were already NULL, no further iterations are
182 needed. */
183 if (destructors_run == 0)
184 break;
185 }
186
187 dtor_processing_threads--;
188 LeaveCriticalSection (&dtor_table_lock);
189 }
190
191 int
glwthread_tls_key_create(glwthread_tls_key_t * keyp,void (* destructor)(void *))192 glwthread_tls_key_create (glwthread_tls_key_t *keyp, void (*destructor) (void *))
193 {
194 if (destructor != NULL)
195 {
196 dtor_table_ensure_initialized ();
197
198 EnterCriticalSection (&dtor_table_lock);
199 if (dtor_processing_threads == 0)
200 {
201 /* Now it's the appropriate time for shrinking dtors_used. */
202 if (dtors_used > dtors_count)
203 dtor_table_shrink_used ();
204 }
205
206 while (dtors_used == dtors_allocated)
207 {
208 /* Need to grow the dtor_table. */
209 unsigned int new_allocated = 2 * dtors_allocated + 1;
210 if (new_allocated < 7)
211 new_allocated = 7;
212 if (new_allocated <= dtors_allocated) /* overflow? */
213 new_allocated = UINT_MAX;
214
215 LeaveCriticalSection (&dtor_table_lock);
216 {
217 struct dtor *new_table =
218 (struct dtor *) malloc (new_allocated * sizeof (struct dtor));
219 if (new_table == NULL)
220 return ENOMEM;
221 EnterCriticalSection (&dtor_table_lock);
222 /* Attention! dtors_used, dtors_allocated may have changed! */
223 if (dtors_used < new_allocated)
224 {
225 if (dtors_allocated < new_allocated)
226 {
227 /* The new_table is useful. */
228 memcpy (new_table, dtor_table,
229 dtors_used * sizeof (struct dtor));
230 dtor_table = new_table;
231 dtors_allocated = new_allocated;
232 }
233 else
234 {
235 /* The new_table is not useful, since another thread
236 meanwhile allocated a drop_table that is at least
237 as large. */
238 free (new_table);
239 }
240 break;
241 }
242 /* The new_table is not useful, since other threads increased
243 dtors_used. Free it any retry. */
244 free (new_table);
245 }
246 }
247 /* Here dtors_used < dtors_allocated. */
248 {
249 /* Allocate a new key. */
250 glwthread_tls_key_t key = TlsAlloc ();
251 if (key == (DWORD)-1)
252 {
253 LeaveCriticalSection (&dtor_table_lock);
254 return EAGAIN;
255 }
256 /* Store the new dtor in the dtor_table, after all used entries.
257 Do not overwrite inactive entries with indices < dtors_used, in order
258 not to disturb glwthread_tls_process_destructors invocations that may
259 be executing in other threads. */
260 dtor_table[dtors_used].key = key;
261 dtor_table[dtors_used].destructor = destructor;
262 dtors_used++;
263 dtors_count++;
264 LeaveCriticalSection (&dtor_table_lock);
265 *keyp = key;
266 }
267 }
268 else
269 {
270 /* Allocate a new key. */
271 glwthread_tls_key_t key = TlsAlloc ();
272 if (key == (DWORD)-1)
273 return EAGAIN;
274 *keyp = key;
275 }
276 return 0;
277 }
278
279 int
glwthread_tls_key_delete(glwthread_tls_key_t key)280 glwthread_tls_key_delete (glwthread_tls_key_t key)
281 {
282 /* Should the destructor be called for all threads that are currently running?
283 Probably not, because
284 - ISO C does not specify when the destructor is to be invoked at all.
285 - In POSIX, the destructor functions specified with pthread_key_create()
286 are invoked at thread exit.
287 - It would be hard to implement, because there are no primitives for
288 accessing thread-specific values from a different thread. */
289 dtor_table_ensure_initialized ();
290
291 EnterCriticalSection (&dtor_table_lock);
292 if (dtor_processing_threads == 0)
293 {
294 /* Now it's the appropriate time for shrinking dtors_used. */
295 if (dtors_used > dtors_count)
296 dtor_table_shrink_used ();
297 /* Here dtors_used == dtors_count. */
298
299 /* Find the key in dtor_table. */
300 {
301 unsigned int i_limit = dtors_used;
302 unsigned int i;
303
304 for (i = 0; i < i_limit; i++)
305 if (dtor_table[i].key == key)
306 {
307 if (i < dtors_used - 1)
308 /* Swap the entries i and dtors_used - 1. */
309 dtor_table[i] = dtor_table[dtors_used - 1];
310 dtors_count = dtors_used = dtors_used - 1;
311 break;
312 }
313 }
314 }
315 else
316 {
317 /* Be careful not to disturb the glwthread_tls_process_destructors
318 invocations that are executing in other threads. */
319 unsigned int i_limit = dtors_used;
320 unsigned int i;
321
322 for (i = 0; i < i_limit; i++)
323 if (dtor_table[i].destructor != NULL /* skip inactive entries */
324 && dtor_table[i].key == key)
325 {
326 /* Mark this entry as inactive. */
327 dtor_table[i].destructor = NULL;
328 dtors_count = dtors_count - 1;
329 break;
330 }
331 }
332 LeaveCriticalSection (&dtor_table_lock);
333 /* Now we have ensured that glwthread_tls_process_destructors will no longer
334 use this key. */
335
336 if (!TlsFree (key))
337 return EINVAL;
338 return 0;
339 }
340