summaryrefslogtreecommitdiff
path: root/lib/windows-tls.c
diff options
context:
space:
mode:
authorBruno Haible <bruno@clisp.org>2019-06-26 03:32:46 +0200
committerBruno Haible <bruno@clisp.org>2019-06-26 03:32:46 +0200
commitd6bfe479691b16501375442865e328f7b0449279 (patch)
tree03a5d0604ef7257157f1d2b5deafb45b1919f950 /lib/windows-tls.c
parent14b8ae17a2695e342c8c7af21629abdb0408157a (diff)
downloadgnulib-d6bfe479691b16501375442865e328f7b0449279.tar.gz
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.
Diffstat (limited to 'lib/windows-tls.c')
-rw-r--r--lib/windows-tls.c301
1 files changed, 291 insertions, 10 deletions
diff --git a/lib/windows-tls.c b/lib/windows-tls.c
index 0f1979e103..02dfdfb7d6 100644
--- a/lib/windows-tls.c
+++ b/lib/windows-tls.c
@@ -22,17 +22,9 @@
#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;