]> Savannah Git Hosting - gnulib.git/commitdiff
windows-tls: Implement TLS key destructors for native Windows.
authorBruno Haible <bruno@clisp.org>
Wed, 26 Jun 2019 01:32:46 +0000 (03:32 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 26 Jun 2019 01:32:46 +0000 (03:32 +0200)
* lib/windows-tls.h (glwthread_tls_process_destructors): New
declaration.
(GLWTHREAD_DESTRUCTOR_ITERATIONS): New macro.
* lib/windows-tls.c: Include <limits.h>, windows-once.h.
(dtor_table_init_once, dtor_table_lock: New variables.
(struct dtor): New type.
(dtor_table, dtors_count, dtors_used, dtors_allocated,
dtor_processing_threads): New variables.
(dtor_table_initialize, dtor_table_ensure_initialized,
dtor_table_shrink_used, glwthread_tls_process_destructors): New
functions.
(glwthread_tls_key_create, glwthread_tls_key_delete): Rewritten to
handle non-NULL destructors.
* modules/windows-tls (Depends-on): Add windows-once.
* lib/glthread/tls.h (glthread_tls_key_init, glthread_tls_key_destroy):
Use the functions declared in windows-tls.h.
* lib/threads.in.h (TSS_DTOR_ITERATIONS): Define using
GLWTHREAD_DESTRUCTOR_ITERATIONS.
* lib/windows-thread.c: Include windows-tls.h.
(wrapper_func, glwthread_thread_exit): Invoke
glwthread_tls_process_destructors.
* modules/windows-thread (Depends-on): Add windows-tls.

ChangeLog
lib/glthread/tls.h
lib/threads.in.h
lib/windows-thread.c
lib/windows-tls.c
lib/windows-tls.h
modules/windows-thread
modules/windows-tls

index 00f09c0c4fd40ec181c81dcc9f3989899199a1b7..9e6d4dae5b499e6122c5c3016dd0c54fca3b4b96 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,29 @@
+2019-06-25  Bruno Haible  <bruno@clisp.org>
+
+       windows-tls: Implement TLS key destructors for native Windows.
+       * lib/windows-tls.h (glwthread_tls_process_destructors): New
+       declaration.
+       (GLWTHREAD_DESTRUCTOR_ITERATIONS): New macro.
+       * lib/windows-tls.c: Include <limits.h>, windows-once.h.
+       (dtor_table_init_once, dtor_table_lock: New variables.
+       (struct dtor): New type.
+       (dtor_table, dtors_count, dtors_used, dtors_allocated,
+       dtor_processing_threads): New variables.
+       (dtor_table_initialize, dtor_table_ensure_initialized,
+       dtor_table_shrink_used, glwthread_tls_process_destructors): New
+       functions.
+       (glwthread_tls_key_create, glwthread_tls_key_delete): Rewritten to
+       handle non-NULL destructors.
+       * modules/windows-tls (Depends-on): Add windows-once.
+       * lib/glthread/tls.h (glthread_tls_key_init, glthread_tls_key_destroy):
+       Use the functions declared in windows-tls.h.
+       * lib/threads.in.h (TSS_DTOR_ITERATIONS): Define using
+       GLWTHREAD_DESTRUCTOR_ITERATIONS.
+       * lib/windows-thread.c: Include windows-tls.h.
+       (wrapper_func, glwthread_thread_exit): Invoke
+       glwthread_tls_process_destructors.
+       * modules/windows-thread (Depends-on): Add windows-tls.
+
 2019-06-25  Bruno Haible  <bruno@clisp.org>
 
        threadlib: Avoid autoconf warning "was expanded before it was required".
index 7a74234788bf64dcc0100c7f480f3e4131c22ce3..6c67de8911ee17d6d416c1c35eecfbb18c2a45c2 100644 (file)
@@ -242,14 +242,13 @@ extern void *glthread_tls_get_multithreaded (thread_key_t key);
 
 typedef glwthread_tls_key_t gl_tls_key_t;
 # define glthread_tls_key_init(KEY, DESTRUCTOR) \
-    /* The destructor is unsupported.  */    \
-    ((*(KEY) = TlsAlloc ()) == (DWORD)-1 ? EAGAIN : ((void) (DESTRUCTOR), 0))
+    glwthread_tls_key_create (KEY, DESTRUCTOR)
 # define gl_tls_get(NAME) \
     TlsGetValue (NAME)
 # define glthread_tls_set(KEY, POINTER) \
     (!TlsSetValue (*(KEY), POINTER) ? EINVAL : 0)
 # define glthread_tls_key_destroy(KEY) \
-    (!TlsFree (*(KEY)) ? EINVAL : 0)
+    glwthread_tls_key_delete (*(KEY))
 
 #endif
 
index b7fed722574df4c1847cac4bb27625b3db18b870..0ed87b337496036105b9b2155f522a1600e43d2f 100644 (file)
@@ -565,7 +565,7 @@ _GL_WARN_ON_USE (cnd_destroy, "cnd_destroy is unportable - "
 #  include "windows-tls.h"
 
 typedef glwthread_tls_key_t tss_t;
-#  define TSS_DTOR_ITERATIONS 0    /* Destructors are currently unsupported.  */
+#  define TSS_DTOR_ITERATIONS GLWTHREAD_DESTRUCTOR_ITERATIONS
 
 # else
 /* Use POSIX threads.  */
index 8bb8ad673f281c268927c42d077e6658bb4bc769..b42bd32baf89d5ff1bc72fc24178c907cccb5a5b 100644 (file)
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 
 #include "windows-once.h"
+#include "windows-tls.h"
 
 /* The Thread-Local Storage (TLS) key that allows to access each thread's
    'struct glwthread_thread_struct *' pointer.  */
@@ -136,6 +137,9 @@ wrapper_func (void *varg)
      otherwise.  */
   thread->result = thread->func (thread->arg);
 
+  /* Process the TLS destructors.  */
+  glwthread_tls_process_destructors ();
+
   if (thread->detached)
     {
       /* Clean up the thread, like thrd_join would do.  */
@@ -233,6 +237,7 @@ glwthread_thread_exit (void *retval)
 {
   glwthread_thread_t thread = glwthread_thread_self ();
   thread->result = retval;
+  glwthread_tls_process_destructors ();
   _endthreadex (0); /* calls ExitThread (0) */
   abort ();
 }
index 0f1979e103da50fa166f354c7c9ec527264a66cf..02dfdfb7d6ab0a86ede37b09faa5490a2b590308 100644 (file)
 #include "windows-tls.h"
 
 #include <errno.h>
+#include <limits.h>
 
-int
-glwthread_tls_key_create (glwthread_tls_key_t *keyp, void (*destructor) (void *))
-{
-  /* TODO: The destructor is unsupported.  */
-  (void) destructor;
-
-  if ((*keyp = TlsAlloc ()) == (DWORD)-1)
-    return EAGAIN;
-  return 0;
-}
+#include "windows-once.h"
 
 void *
 glwthread_tls_get (glwthread_tls_key_t key)
@@ -48,9 +40,298 @@ glwthread_tls_set (glwthread_tls_key_t key, void *value)
   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;
index 5e795454e3157023f5ff7b7b0b26a6ef0518d702..3bccd2ed73d81d007d46e46f45a1f5d66347ea83 100644 (file)
@@ -32,6 +32,8 @@ extern int glwthread_tls_key_create (glwthread_tls_key_t *keyp, void (*destructo
 extern void *glwthread_tls_get (glwthread_tls_key_t key);
 extern int glwthread_tls_set (glwthread_tls_key_t key, void *value);
 extern int glwthread_tls_key_delete (glwthread_tls_key_t key);
+extern void glwthread_tls_process_destructors (void);
+#define GLWTHREAD_DESTRUCTOR_ITERATIONS 4
 
 #ifdef __cplusplus
 }
index 3f353446e654a8c982b68295055b4430b168cb9a..3c1b7ac042ab6d48d0edd6ec8a070cb1cd55d44d 100644 (file)
@@ -7,6 +7,7 @@ lib/windows-thread.c
 
 Depends-on:
 windows-once
+windows-tls
 
 configure.ac:
 AC_REQUIRE([AC_CANONICAL_HOST])
index 32a32eb4727170bbce47b853574bfaa3294f04b3..301d7c3d7583b07968e1bef22dea0ede459bf9b7 100644 (file)
@@ -6,6 +6,7 @@ lib/windows-tls.h
 lib/windows-tls.c
 
 Depends-on:
+windows-once
 
 configure.ac:
 AC_REQUIRE([AC_CANONICAL_HOST])