exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / windows-tls.c
blob6b73074d0e22afc48c9f5cdb2bf24f04e5587d4b
1 /* Thread-local storage (native Windows implementation).
2 Copyright (C) 2005-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
9 This file 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 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Bruno Haible <bruno@clisp.org>, 2005. */
19 #include <config.h>
21 /* Specification. */
22 #include "windows-tls.h"
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdlib.h>
28 #include "windows-once.h"
30 void *
31 glwthread_tls_get (glwthread_tls_key_t key)
33 return TlsGetValue (key);
36 int
37 glwthread_tls_set (glwthread_tls_key_t key, void *value)
39 if (!TlsSetValue (key, value))
40 return EINVAL;
41 return 0;
44 /* The following variables keep track of TLS keys with non-NULL destructor. */
46 static glwthread_once_t dtor_table_init_once = GLWTHREAD_ONCE_INIT;
48 static CRITICAL_SECTION dtor_table_lock;
50 struct dtor { glwthread_tls_key_t key; void (*destructor) (void *); };
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. */
62 /* Number of threads that are currently processing destructors. */
63 static unsigned int dtor_processing_threads;
65 static void
66 dtor_table_initialize (void)
68 InitializeCriticalSection (&dtor_table_lock);
69 /* The other variables are already initialized to NULL or 0, respectively. */
72 static void
73 dtor_table_ensure_initialized (void)
75 glwthread_once (&dtor_table_init_once, dtor_table_initialize);
78 /* Shrinks dtors_used down to dtors_count, by replacing inactive entries
79 with active ones. */
80 static void
81 dtor_table_shrink_used (void)
83 unsigned int i = 0;
84 unsigned int j = dtors_used;
86 for (;;)
88 BOOL i_found = FALSE;
89 BOOL j_found = FALSE;
90 /* Find the next inactive entry, from the left. */
91 for (; i < dtors_count;)
93 if (dtor_table[i].destructor == NULL)
95 i_found = TRUE;
96 break;
98 i++;
101 /* Find the next active entry, from the right. */
102 for (; j > dtors_count;)
104 j--;
105 if (dtor_table[j].destructor != NULL)
107 j_found = TRUE;
108 break;
112 if (i_found != j_found)
113 /* dtors_count was apparently wrong. */
114 abort ();
116 if (!i_found)
117 break;
119 /* i_found and j_found are TRUE. Swap the two entries. */
120 dtor_table[i] = dtor_table[j];
122 i++;
125 dtors_used = dtors_count;
128 void
129 glwthread_tls_process_destructors (void)
131 unsigned int repeat;
133 dtor_table_ensure_initialized ();
135 EnterCriticalSection (&dtor_table_lock);
136 if (dtor_processing_threads == 0)
138 /* Now it's the appropriate time for shrinking dtors_used. */
139 if (dtors_used > dtors_count)
140 dtor_table_shrink_used ();
142 dtor_processing_threads++;
144 for (repeat = GLWTHREAD_DESTRUCTOR_ITERATIONS; repeat > 0; repeat--)
146 unsigned int destructors_run = 0;
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;
162 for (i = 0; i < i_limit; i++)
164 struct dtor current = dtor_table[i];
165 if (current.destructor != NULL)
167 /* The current dtor has not been deleted yet. */
168 void *current_value = glwthread_tls_get (current.key);
169 if (current_value != NULL)
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++;
181 /* When all TLS values were already NULL, no further iterations are
182 needed. */
183 if (destructors_run == 0)
184 break;
187 dtor_processing_threads--;
188 LeaveCriticalSection (&dtor_table_lock);
192 glwthread_tls_key_create (glwthread_tls_key_t *keyp, void (*destructor) (void *))
194 if (destructor != NULL)
196 dtor_table_ensure_initialized ();
198 EnterCriticalSection (&dtor_table_lock);
199 if (dtor_processing_threads == 0)
201 /* Now it's the appropriate time for shrinking dtors_used. */
202 if (dtors_used > dtors_count)
203 dtor_table_shrink_used ();
206 while (dtors_used == dtors_allocated)
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;
215 LeaveCriticalSection (&dtor_table_lock);
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)
225 if (dtors_allocated < new_allocated)
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;
233 else
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);
240 break;
242 /* The new_table is not useful, since other threads increased
243 dtors_used. Free it any retry. */
244 free (new_table);
247 /* Here dtors_used < dtors_allocated. */
249 /* Allocate a new key. */
250 glwthread_tls_key_t key = TlsAlloc ();
251 if (key == (DWORD)-1)
253 LeaveCriticalSection (&dtor_table_lock);
254 return EAGAIN;
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;
268 else
270 /* Allocate a new key. */
271 glwthread_tls_key_t key = TlsAlloc ();
272 if (key == (DWORD)-1)
273 return EAGAIN;
274 *keyp = key;
276 return 0;
280 glwthread_tls_key_delete (glwthread_tls_key_t key)
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 ();
291 EnterCriticalSection (&dtor_table_lock);
292 if (dtor_processing_threads == 0)
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. */
299 /* Find the key in dtor_table. */
301 unsigned int i_limit = dtors_used;
302 unsigned int i;
304 for (i = 0; i < i_limit; i++)
305 if (dtor_table[i].key == key)
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;
315 else
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;
322 for (i = 0; i < i_limit; i++)
323 if (dtor_table[i].destructor != NULL /* skip inactive entries */
324 && dtor_table[i].key == key)
326 /* Mark this entry as inactive. */
327 dtor_table[i].destructor = NULL;
328 dtors_count = dtors_count - 1;
329 break;
332 LeaveCriticalSection (&dtor_table_lock);
333 /* Now we have ensured that glwthread_tls_process_destructors will no longer
334 use this key. */
336 if (!TlsFree (key))
337 return EINVAL;
338 return 0;