>From d6bfe479691b16501375442865e328f7b0449279 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Wed, 26 Jun 2019 03:32:46 +0200 Subject: [PATCH 1/3] 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 , 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 | 26 +++++ lib/glthread/tls.h | 5 +- lib/threads.in.h | 2 +- lib/windows-thread.c | 5 + lib/windows-tls.c | 301 +++++++++++++++++++++++++++++++++++++++++++++++-- lib/windows-tls.h | 2 + modules/windows-thread | 1 + modules/windows-tls | 1 + 8 files changed, 329 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 00f09c0..9e6d4da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,31 @@ 2019-06-25 Bruno Haible + 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 , 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 + threadlib: Avoid autoconf warning "was expanded before it was required". * modules/threadlib (configure.ac): Require gl_THREADLIB. diff --git a/lib/glthread/tls.h b/lib/glthread/tls.h index 7a74234..6c67de8 100644 --- a/lib/glthread/tls.h +++ b/lib/glthread/tls.h @@ -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 diff --git a/lib/threads.in.h b/lib/threads.in.h index b7fed72..0ed87b3 100644 --- a/lib/threads.in.h +++ b/lib/threads.in.h @@ -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. */ diff --git a/lib/windows-thread.c b/lib/windows-thread.c index 8bb8ad6..b42bd32 100644 --- a/lib/windows-thread.c +++ b/lib/windows-thread.c @@ -27,6 +27,7 @@ #include #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 (); } diff --git a/lib/windows-tls.c b/lib/windows-tls.c index 0f1979e..02dfdfb 100644 --- a/lib/windows-tls.c +++ b/lib/windows-tls.c @@ -22,17 +22,9 @@ #include "windows-tls.h" #include +#include -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; diff --git a/lib/windows-tls.h b/lib/windows-tls.h index 5e79545..3bccd2e 100644 --- a/lib/windows-tls.h +++ b/lib/windows-tls.h @@ -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 } diff --git a/modules/windows-thread b/modules/windows-thread index 3f35344..3c1b7ac 100644 --- a/modules/windows-thread +++ b/modules/windows-thread @@ -7,6 +7,7 @@ lib/windows-thread.c Depends-on: windows-once +windows-tls configure.ac: AC_REQUIRE([AC_CANONICAL_HOST]) diff --git a/modules/windows-tls b/modules/windows-tls index 32a32eb..301d7c3 100644 --- a/modules/windows-tls +++ b/modules/windows-tls @@ -6,6 +6,7 @@ lib/windows-tls.h lib/windows-tls.c Depends-on: +windows-once configure.ac: AC_REQUIRE([AC_CANONICAL_HOST]) -- 2.7.4