summaryrefslogtreecommitdiffstats
path: root/lib/windows-tls.c
blob: 3b6ff80e5101b7556c70172a6edfc5a44043e9d6 (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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/* Thread-local storage (native Windows implementation).
   Copyright (C) 2005-2023 Free Software Foundation, Inc.

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible <bruno@clisp.org>, 2005.  */

#include <config.h>

/* Specification.  */
#include "windows-tls.h"

#include <errno.h>
#include <limits.h>
#include <stdlib.h>

#include "windows-once.h"

void *
glwthread_tls_get (glwthread_tls_key_t key)
{
  return TlsGetValue (key);
}

int
glwthread_tls_set (glwthread_tls_key_t key, void *value)
{
  if (!TlsSetValue (key, value))
    return EINVAL;
  return 0;
}

/* The following variables keep track of TLS keys with non-NULL destructor.  */

static glwthread_once_t dtor_table_init_once = GLWTHREAD_ONCE_INIT;

static CRITICAL_SECTION dtor_table_lock;

struct dtor { glwthread_tls_key_t key; void (*destructor) (void *); };

/* The table of dtors.  */
static struct dtor *dtor_table;
/* Number of active entries in the dtor_table.  */
static unsigned int dtors_count;
/* Valid indices into dtor_table are 0..dtors_used-1.  */
static unsigned int dtors_used;
/* Allocation size of dtor_table.  */
static unsigned int dtors_allocated;
/* Invariant: 0 <= dtors_count <= dtors_used <= dtors_allocated.  */

/* Number of threads that are currently processing destructors.  */
static unsigned int dtor_processing_threads;

static void
dtor_table_initialize (void)
{
  InitializeCriticalSection (&dtor_table_lock);
  /* The other variables are already initialized to NULL or 0, respectively.  */
}

static void
dtor_table_ensure_initialized (void)
{
  glwthread_once (&dtor_table_init_once, dtor_table_initialize);
}

/* Shrinks dtors_used down to dtors_count, by replacing inactive entries
   with active ones.  */
static void
dtor_table_shrink_used (void)
{
  unsigned int i = 0;
  unsigned int j = dtors_used;

  for (;;)
    {
      BOOL i_found = FALSE;
      BOOL j_found = FALSE;
      /* Find the next inactive entry, from the left.  */
      for (; i < dtors_count;)
        {
          if (dtor_table[i].destructor == NULL)
            {
              i_found = TRUE;
              break;
            }
          i++;
        }

      /* Find the next active entry, from the right.  */
      for (; j > dtors_count;)
        {
          j--;
          if (dtor_table[j].destructor != NULL)
            {
              j_found = TRUE;
              break;
            }
        }

      if (i_found != j_found)
        /* dtors_count was apparently wrong.  */
        abort ();

      if (!i_found)
        break;

      /* i_found and j_found are TRUE.  Swap the two entries.  */
      dtor_table[i] = dtor_table[j];

      i++;
    }

  dtors_used = dtors_count;
}

void
glwthread_tls_process_destructors (void)
{
  unsigned int repeat;

  dtor_table_ensure_initialized ();

  EnterCriticalSection (&dtor_table_lock);
  if (dtor_processing_threads == 0)
    {
      /* Now it's the appropriate time for shrinking dtors_used.  */
      if (dtors_used > dtors_count)
        dtor_table_shrink_used ();
    }
  dtor_processing_threads++;

  for (repeat = GLWTHREAD_DESTRUCTOR_ITERATIONS; repeat > 0; repeat--)
    {
      unsigned int destructors_run = 0;

      /* Iterate across dtor_table.  We don't need to make a copy of dtor_table,
         because
           * When another thread calls glwthread_tls_key_create with a non-NULL
             destructor argument, this will possibly reallocate the dtor_table
             array and increase dtors_allocated as well as dtors_used and
             dtors_count, but it will not change dtors_used nor the contents of
             the first dtors_used entries of dtor_table.
           * When another thread calls glwthread_tls_key_delete, this will
             possibly set some 'destructor' member to NULL, thus marking an
             entry as inactive, but it will not otherwise change dtors_used nor
             the contents of the first dtors_used entries of dtor_table.  */
      unsigned int i_limit = dtors_used;
      unsigned int i;

      for (i = 0; i < i_limit; i++)
        {
          struct dtor current = dtor_table[i];
          if (current.destructor != NULL)
            {
              /* The current dtor has not been deleted yet.  */
              void *current_value = glwthread_tls_get (current.key);
              if (current_value != NULL)
                {
                  /* The current value is non-NULL.  Run the destructor.  */
                  glwthread_tls_set (current.key, NULL);
                  LeaveCriticalSection (&dtor_table_lock);
                  current.destructor (current_value);
                  EnterCriticalSection (&dtor_table_lock);
                  destructors_run++;
                }
            }
        }

      /* When all TLS values were already NULL, no further iterations are
         needed.  */
      if (destructors_run == 0)
        break;
    }

  dtor_processing_threads--;
  LeaveCriticalSection (&dtor_table_lock);
}

int
glwthread_tls_key_create (glwthread_tls_key_t *keyp, void (*destructor) (void *))
{
  if (destructor != NULL)
    {
      dtor_table_ensure_initialized ();

      EnterCriticalSection (&dtor_table_lock);
      if (dtor_processing_threads == 0)
        {
          /* Now it's the appropriate time for shrinking dtors_used.  */
          if (dtors_used > dtors_count)
            dtor_table_shrink_used ();
        }

      while (dtors_used == dtors_allocated)
        {
          /* Need to grow the dtor_table.  */
          unsigned int new_allocated = 2 * dtors_allocated + 1;
          if (new_allocated < 7)
            new_allocated = 7;
          if (new_allocated <= dtors_allocated) /* overflow? */
            new_allocated = UINT_MAX;

          LeaveCriticalSection (&dtor_table_lock);
          {
            struct dtor *new_table =
              (struct dtor *) malloc (new_allocated * sizeof (struct dtor));
            if (new_table == NULL)
              return ENOMEM;
            EnterCriticalSection (&dtor_table_lock);
            /* Attention! dtors_used, dtors_allocated may have changed!  */
            if (dtors_used < new_allocated)
              {
                if (dtors_allocated < new_allocated)
                  {
                    /* The new_table is useful.  */
                    memcpy (new_table, dtor_table,
                            dtors_used * sizeof (struct dtor));
                    dtor_table = new_table;
                    dtors_allocated = new_allocated;
                  }
                else
                  {
                    /* The new_table is not useful, since another thread
                       meanwhile allocated a drop_table that is at least
                       as large.  */
                    free (new_table);
                  }
                break;
              }
            /* The new_table is not useful, since other threads increased
               dtors_used.  Free it any retry.  */
            free (new_table);
          }
        }
      /* Here dtors_used < dtors_allocated.  */
      {
        /* Allocate a new key.  */
        glwthread_tls_key_t key = TlsAlloc ();
        if (key == (DWORD)-1)
          {
            LeaveCriticalSection (&dtor_table_lock);
            return EAGAIN;
          }
        /* Store the new dtor in the dtor_table, after all used entries.
           Do not overwrite inactive entries with indices < dtors_used, in order
           not to disturb glwthread_tls_process_destructors invocations that may
           be executing in other threads.  */
        dtor_table[dtors_used].key = key;
        dtor_table[dtors_used].destructor = destructor;
        dtors_used++;
        dtors_count++;
        LeaveCriticalSection (&dtor_table_lock);
        *keyp = key;
      }
    }
  else
    {
      /* Allocate a new key.  */
      glwthread_tls_key_t key = TlsAlloc ();
      if (key == (DWORD)-1)
        return EAGAIN;
      *keyp = key;
    }
  return 0;
}

int
glwthread_tls_key_delete (glwthread_tls_key_t key)
{
  /* Should the destructor be called for all threads that are currently running?
     Probably not, because
       - ISO C does not specify when the destructor is to be invoked at all.
       - In POSIX, the destructor functions specified with pthread_key_create()
         are invoked at thread exit.
       - It would be hard to implement, because there are no primitives for
         accessing thread-specific values from a different thread.  */
  dtor_table_ensure_initialized ();

  EnterCriticalSection (&dtor_table_lock);
  if (dtor_processing_threads == 0)
    {
      /* Now it's the appropriate time for shrinking dtors_used.  */
      if (dtors_used > dtors_count)
        dtor_table_shrink_used ();
      /* Here dtors_used == dtors_count.  */

      /* Find the key in dtor_table.  */
      {
        unsigned int i_limit = dtors_used;
        unsigned int i;

        for (i = 0; i < i_limit; i++)
          if (dtor_table[i].key == key)
            {
              if (i < dtors_used - 1)
                /* Swap the entries i and dtors_used - 1.  */
                dtor_table[i] = dtor_table[dtors_used - 1];
              dtors_count = dtors_used = dtors_used - 1;
              break;
            }
      }
    }
  else
    {
      /* Be careful not to disturb the glwthread_tls_process_destructors
         invocations that are executing in other threads.  */
      unsigned int i_limit = dtors_used;
      unsigned int i;

      for (i = 0; i < i_limit; i++)
        if (dtor_table[i].destructor != NULL /* skip inactive entries */
            && dtor_table[i].key == key)
          {
            /* Mark this entry as inactive.  */
            dtor_table[i].destructor = NULL;
            dtors_count = dtors_count - 1;
            break;
          }
    }
  LeaveCriticalSection (&dtor_table_lock);
  /* Now we have ensured that glwthread_tls_process_destructors will no longer
     use this key.  */

  if (!TlsFree (key))
    return EINVAL;
  return 0;
}