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