Blame gl/tests/windows-tls.c

Packit Service 4684c1
/* Thread-local storage (native Windows implementation).
Packit Service 4684c1
   Copyright (C) 2005-2020 Free Software Foundation, Inc.
Packit Service 4684c1
Packit Service 4684c1
   This program is free software: you can redistribute it and/or modify
Packit Service 4684c1
   it under the terms of the GNU General Public License as published by
Packit Service 4684c1
   the Free Software Foundation; either version 3 of the License, or
Packit Service 4684c1
   (at your option) any later version.
Packit Service 4684c1
Packit Service 4684c1
   This program is distributed in the hope that it will be useful,
Packit Service 4684c1
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 4684c1
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 4684c1
   GNU General Public License for more details.
Packit Service 4684c1
Packit Service 4684c1
   You should have received a copy of the GNU General Public License
Packit Service 4684c1
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
Packit Service 4684c1
Packit Service 4684c1
/* Written by Bruno Haible <bruno@clisp.org>, 2005.  */
Packit Service 4684c1
Packit Service 4684c1
#include <config.h>
Packit Service 4684c1
Packit Service 4684c1
/* Specification.  */
Packit Service 4684c1
#include "windows-tls.h"
Packit Service 4684c1
Packit Service 4684c1
#include <errno.h>
Packit Service 4684c1
#include <limits.h>
Packit Service 4684c1
#include <stdlib.h>
Packit Service 4684c1
Packit Service 4684c1
#include "windows-once.h"
Packit Service 4684c1
Packit Service 4684c1
void *
Packit Service 4684c1
glwthread_tls_get (glwthread_tls_key_t key)
Packit Service 4684c1
{
Packit Service 4684c1
  return TlsGetValue (key);
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
int
Packit Service 4684c1
glwthread_tls_set (glwthread_tls_key_t key, void *value)
Packit Service 4684c1
{
Packit Service 4684c1
  if (!TlsSetValue (key, value))
Packit Service 4684c1
    return EINVAL;
Packit Service 4684c1
  return 0;
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
/* The following variables keep track of TLS keys with non-NULL destructor.  */
Packit Service 4684c1
Packit Service 4684c1
static glwthread_once_t dtor_table_init_once = GLWTHREAD_ONCE_INIT;
Packit Service 4684c1
Packit Service 4684c1
static CRITICAL_SECTION dtor_table_lock;
Packit Service 4684c1
Packit Service 4684c1
struct dtor { glwthread_tls_key_t key; void (*destructor) (void *); };
Packit Service 4684c1
Packit Service 4684c1
/* The table of dtors.  */
Packit Service 4684c1
static struct dtor *dtor_table;
Packit Service 4684c1
/* Number of active entries in the dtor_table.  */
Packit Service 4684c1
static unsigned int dtors_count;
Packit Service 4684c1
/* Valid indices into dtor_table are 0..dtors_used-1.  */
Packit Service 4684c1
static unsigned int dtors_used;
Packit Service 4684c1
/* Allocation size of dtor_table.  */
Packit Service 4684c1
static unsigned int dtors_allocated;
Packit Service 4684c1
/* Invariant: 0 <= dtors_count <= dtors_used <= dtors_allocated.  */
Packit Service 4684c1
Packit Service 4684c1
/* Number of threads that are currently processing destructors.  */
Packit Service 4684c1
static unsigned int dtor_processing_threads;
Packit Service 4684c1
Packit Service 4684c1
static void
Packit Service 4684c1
dtor_table_initialize (void)
Packit Service 4684c1
{
Packit Service 4684c1
  InitializeCriticalSection (&dtor_table_lock);
Packit Service 4684c1
  /* The other variables are already initialized to NULL or 0, respectively.  */
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
static void
Packit Service 4684c1
dtor_table_ensure_initialized (void)
Packit Service 4684c1
{
Packit Service 4684c1
  glwthread_once (&dtor_table_init_once, dtor_table_initialize);
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
/* Shrinks dtors_used down to dtors_count, by replacing inactive entries
Packit Service 4684c1
   with active ones.  */
Packit Service 4684c1
static void
Packit Service 4684c1
dtor_table_shrink_used (void)
Packit Service 4684c1
{
Packit Service 4684c1
  unsigned int i = 0;
Packit Service 4684c1
  unsigned int j = dtors_used;
Packit Service 4684c1
Packit Service 4684c1
  for (;;)
Packit Service 4684c1
    {
Packit Service 4684c1
      BOOL i_found = FALSE;
Packit Service 4684c1
      BOOL j_found = FALSE;
Packit Service 4684c1
      /* Find the next inactive entry, from the left.  */
Packit Service 4684c1
      for (; i < dtors_count;)
Packit Service 4684c1
        {
Packit Service 4684c1
          if (dtor_table[i].destructor == NULL)
Packit Service 4684c1
            {
Packit Service 4684c1
              i_found = TRUE;
Packit Service 4684c1
              break;
Packit Service 4684c1
            }
Packit Service 4684c1
          i++;
Packit Service 4684c1
        }
Packit Service 4684c1
Packit Service 4684c1
      /* Find the next active entry, from the right.  */
Packit Service 4684c1
      for (; j > dtors_count;)
Packit Service 4684c1
        {
Packit Service 4684c1
          j--;
Packit Service 4684c1
          if (dtor_table[j].destructor != NULL)
Packit Service 4684c1
            {
Packit Service 4684c1
              j_found = TRUE;
Packit Service 4684c1
              break;
Packit Service 4684c1
            }
Packit Service 4684c1
        }
Packit Service 4684c1
Packit Service 4684c1
      if (i_found != j_found)
Packit Service 4684c1
        /* dtors_count was apparently wrong.  */
Packit Service 4684c1
        abort ();
Packit Service 4684c1
Packit Service 4684c1
      if (!i_found)
Packit Service 4684c1
        break;
Packit Service 4684c1
Packit Service 4684c1
      /* i_found and j_found are TRUE.  Swap the two entries.  */
Packit Service 4684c1
      dtor_table[i] = dtor_table[j];
Packit Service 4684c1
Packit Service 4684c1
      i++;
Packit Service 4684c1
    }
Packit Service 4684c1
Packit Service 4684c1
  dtors_used = dtors_count;
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
void
Packit Service 4684c1
glwthread_tls_process_destructors (void)
Packit Service 4684c1
{
Packit Service 4684c1
  unsigned int repeat;
Packit Service 4684c1
Packit Service 4684c1
  dtor_table_ensure_initialized ();
Packit Service 4684c1
Packit Service 4684c1
  EnterCriticalSection (&dtor_table_lock);
Packit Service 4684c1
  if (dtor_processing_threads == 0)
Packit Service 4684c1
    {
Packit Service 4684c1
      /* Now it's the appropriate time for shrinking dtors_used.  */
Packit Service 4684c1
      if (dtors_used > dtors_count)
Packit Service 4684c1
        dtor_table_shrink_used ();
Packit Service 4684c1
    }
Packit Service 4684c1
  dtor_processing_threads++;
Packit Service 4684c1
Packit Service 4684c1
  for (repeat = GLWTHREAD_DESTRUCTOR_ITERATIONS; repeat > 0; repeat--)
Packit Service 4684c1
    {
Packit Service 4684c1
      unsigned int destructors_run = 0;
Packit Service 4684c1
Packit Service 4684c1
      /* Iterate across dtor_table.  We don't need to make a copy of dtor_table,
Packit Service 4684c1
         because
Packit Service 4684c1
           * When another thread calls glwthread_tls_key_create with a non-NULL
Packit Service 4684c1
             destructor argument, this will possibly reallocate the dtor_table
Packit Service 4684c1
             array and increase dtors_allocated as well as dtors_used and
Packit Service 4684c1
             dtors_count, but it will not change dtors_used nor the contents of
Packit Service 4684c1
             the first dtors_used entries of dtor_table.
Packit Service 4684c1
           * When another thread calls glwthread_tls_key_delete, this will
Packit Service 4684c1
             possibly set some 'destructor' member to NULL, thus marking an
Packit Service 4684c1
             entry as inactive, but it will not otherwise change dtors_used nor
Packit Service 4684c1
             the contents of the first dtors_used entries of dtor_table.  */
Packit Service 4684c1
      unsigned int i_limit = dtors_used;
Packit Service 4684c1
      unsigned int i;
Packit Service 4684c1
Packit Service 4684c1
      for (i = 0; i < i_limit; i++)
Packit Service 4684c1
        {
Packit Service 4684c1
          struct dtor current = dtor_table[i];
Packit Service 4684c1
          if (current.destructor != NULL)
Packit Service 4684c1
            {
Packit Service 4684c1
              /* The current dtor has not been deleted yet.  */
Packit Service 4684c1
              void *current_value = glwthread_tls_get (current.key);
Packit Service 4684c1
              if (current_value != NULL)
Packit Service 4684c1
                {
Packit Service 4684c1
                  /* The current value is non-NULL.  Run the destructor.  */
Packit Service 4684c1
                  glwthread_tls_set (current.key, NULL);
Packit Service 4684c1
                  LeaveCriticalSection (&dtor_table_lock);
Packit Service 4684c1
                  current.destructor (current_value);
Packit Service 4684c1
                  EnterCriticalSection (&dtor_table_lock);
Packit Service 4684c1
                  destructors_run++;
Packit Service 4684c1
                }
Packit Service 4684c1
            }
Packit Service 4684c1
        }
Packit Service 4684c1
Packit Service 4684c1
      /* When all TLS values were already NULL, no further iterations are
Packit Service 4684c1
         needed.  */
Packit Service 4684c1
      if (destructors_run == 0)
Packit Service 4684c1
        break;
Packit Service 4684c1
    }
Packit Service 4684c1
Packit Service 4684c1
  dtor_processing_threads--;
Packit Service 4684c1
  LeaveCriticalSection (&dtor_table_lock);
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
int
Packit Service 4684c1
glwthread_tls_key_create (glwthread_tls_key_t *keyp, void (*destructor) (void *))
Packit Service 4684c1
{
Packit Service 4684c1
  if (destructor != NULL)
Packit Service 4684c1
    {
Packit Service 4684c1
      dtor_table_ensure_initialized ();
Packit Service 4684c1
Packit Service 4684c1
      EnterCriticalSection (&dtor_table_lock);
Packit Service 4684c1
      if (dtor_processing_threads == 0)
Packit Service 4684c1
        {
Packit Service 4684c1
          /* Now it's the appropriate time for shrinking dtors_used.  */
Packit Service 4684c1
          if (dtors_used > dtors_count)
Packit Service 4684c1
            dtor_table_shrink_used ();
Packit Service 4684c1
        }
Packit Service 4684c1
Packit Service 4684c1
      while (dtors_used == dtors_allocated)
Packit Service 4684c1
        {
Packit Service 4684c1
          /* Need to grow the dtor_table.  */
Packit Service 4684c1
          unsigned int new_allocated = 2 * dtors_allocated + 1;
Packit Service 4684c1
          if (new_allocated < 7)
Packit Service 4684c1
            new_allocated = 7;
Packit Service 4684c1
          if (new_allocated <= dtors_allocated) /* overflow? */
Packit Service 4684c1
            new_allocated = UINT_MAX;
Packit Service 4684c1
Packit Service 4684c1
          LeaveCriticalSection (&dtor_table_lock);
Packit Service 4684c1
          {
Packit Service 4684c1
            struct dtor *new_table =
Packit Service 4684c1
              (struct dtor *) malloc (new_allocated * sizeof (struct dtor));
Packit Service 4684c1
            if (new_table == NULL)
Packit Service 4684c1
              return ENOMEM;
Packit Service 4684c1
            EnterCriticalSection (&dtor_table_lock);
Packit Service 4684c1
            /* Attention! dtors_used, dtors_allocated may have changed!  */
Packit Service 4684c1
            if (dtors_used < new_allocated)
Packit Service 4684c1
              {
Packit Service 4684c1
                if (dtors_allocated < new_allocated)
Packit Service 4684c1
                  {
Packit Service 4684c1
                    /* The new_table is useful.  */
Packit Service 4684c1
                    memcpy (new_table, dtor_table,
Packit Service 4684c1
                            dtors_used * sizeof (struct dtor));
Packit Service 4684c1
                    dtor_table = new_table;
Packit Service 4684c1
                    dtors_allocated = new_allocated;
Packit Service 4684c1
                  }
Packit Service 4684c1
                else
Packit Service 4684c1
                  {
Packit Service 4684c1
                    /* The new_table is not useful, since another thread
Packit Service 4684c1
                       meanwhile allocated a drop_table that is at least
Packit Service 4684c1
                       as large.  */
Packit Service 4684c1
                    free (new_table);
Packit Service 4684c1
                  }
Packit Service 4684c1
                break;
Packit Service 4684c1
              }
Packit Service 4684c1
            /* The new_table is not useful, since other threads increased
Packit Service 4684c1
               dtors_used.  Free it any retry.  */
Packit Service 4684c1
            free (new_table);
Packit Service 4684c1
          }
Packit Service 4684c1
        }
Packit Service 4684c1
      /* Here dtors_used < dtors_allocated.  */
Packit Service 4684c1
      {
Packit Service 4684c1
        /* Allocate a new key.  */
Packit Service 4684c1
        glwthread_tls_key_t key = TlsAlloc ();
Packit Service 4684c1
        if (key == (DWORD)-1)
Packit Service 4684c1
          {
Packit Service 4684c1
            LeaveCriticalSection (&dtor_table_lock);
Packit Service 4684c1
            return EAGAIN;
Packit Service 4684c1
          }
Packit Service 4684c1
        /* Store the new dtor in the dtor_table, after all used entries.
Packit Service 4684c1
           Do not overwrite inactive entries with indices < dtors_used, in order
Packit Service 4684c1
           not to disturb glwthread_tls_process_destructors invocations that may
Packit Service 4684c1
           be executing in other threads.  */
Packit Service 4684c1
        dtor_table[dtors_used].key = key;
Packit Service 4684c1
        dtor_table[dtors_used].destructor = destructor;
Packit Service 4684c1
        dtors_used++;
Packit Service 4684c1
        dtors_count++;
Packit Service 4684c1
        LeaveCriticalSection (&dtor_table_lock);
Packit Service 4684c1
        *keyp = key;
Packit Service 4684c1
      }
Packit Service 4684c1
    }
Packit Service 4684c1
  else
Packit Service 4684c1
    {
Packit Service 4684c1
      /* Allocate a new key.  */
Packit Service 4684c1
      glwthread_tls_key_t key = TlsAlloc ();
Packit Service 4684c1
      if (key == (DWORD)-1)
Packit Service 4684c1
        return EAGAIN;
Packit Service 4684c1
      *keyp = key;
Packit Service 4684c1
    }
Packit Service 4684c1
  return 0;
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
int
Packit Service 4684c1
glwthread_tls_key_delete (glwthread_tls_key_t key)
Packit Service 4684c1
{
Packit Service 4684c1
  /* Should the destructor be called for all threads that are currently running?
Packit Service 4684c1
     Probably not, because
Packit Service 4684c1
       - ISO C does not specify when the destructor is to be invoked at all.
Packit Service 4684c1
       - In POSIX, the destructor functions specified with pthread_key_create()
Packit Service 4684c1
         are invoked at thread exit.
Packit Service 4684c1
       - It would be hard to implement, because there are no primitives for
Packit Service 4684c1
         accessing thread-specific values from a different thread.  */
Packit Service 4684c1
  dtor_table_ensure_initialized ();
Packit Service 4684c1
Packit Service 4684c1
  EnterCriticalSection (&dtor_table_lock);
Packit Service 4684c1
  if (dtor_processing_threads == 0)
Packit Service 4684c1
    {
Packit Service 4684c1
      /* Now it's the appropriate time for shrinking dtors_used.  */
Packit Service 4684c1
      if (dtors_used > dtors_count)
Packit Service 4684c1
        dtor_table_shrink_used ();
Packit Service 4684c1
      /* Here dtors_used == dtors_count.  */
Packit Service 4684c1
Packit Service 4684c1
      /* Find the key in dtor_table.  */
Packit Service 4684c1
      {
Packit Service 4684c1
        unsigned int i_limit = dtors_used;
Packit Service 4684c1
        unsigned int i;
Packit Service 4684c1
Packit Service 4684c1
        for (i = 0; i < i_limit; i++)
Packit Service 4684c1
          if (dtor_table[i].key == key)
Packit Service 4684c1
            {
Packit Service 4684c1
              if (i < dtors_used - 1)
Packit Service 4684c1
                /* Swap the entries i and dtors_used - 1.  */
Packit Service 4684c1
                dtor_table[i] = dtor_table[dtors_used - 1];
Packit Service 4684c1
              dtors_count = dtors_used = dtors_used - 1;
Packit Service 4684c1
              break;
Packit Service 4684c1
            }
Packit Service 4684c1
      }
Packit Service 4684c1
    }
Packit Service 4684c1
  else
Packit Service 4684c1
    {
Packit Service 4684c1
      /* Be careful not to disturb the glwthread_tls_process_destructors
Packit Service 4684c1
         invocations that are executing in other threads.  */
Packit Service 4684c1
      unsigned int i_limit = dtors_used;
Packit Service 4684c1
      unsigned int i;
Packit Service 4684c1
Packit Service 4684c1
      for (i = 0; i < i_limit; i++)
Packit Service 4684c1
        if (dtor_table[i].destructor != NULL /* skip inactive entries */
Packit Service 4684c1
            && dtor_table[i].key == key)
Packit Service 4684c1
          {
Packit Service 4684c1
            /* Mark this entry as inactive.  */
Packit Service 4684c1
            dtor_table[i].destructor = NULL;
Packit Service 4684c1
            dtors_count = dtors_count - 1;
Packit Service 4684c1
            break;
Packit Service 4684c1
          }
Packit Service 4684c1
    }
Packit Service 4684c1
  LeaveCriticalSection (&dtor_table_lock);
Packit Service 4684c1
  /* Now we have ensured that glwthread_tls_process_destructors will no longer
Packit Service 4684c1
     use this key.  */
Packit Service 4684c1
Packit Service 4684c1
  if (!TlsFree (key))
Packit Service 4684c1
    return EINVAL;
Packit Service 4684c1
  return 0;
Packit Service 4684c1
}