Finally got around at implementing support for destructors in the 'tls' and 'tss' modules for native Windows.
I'm also enhancing the unit tests of these modules. These new tests happen to crash on musl libc (Alpine Linux 3.9); I've reported the bug on the musl libc list. 2019-06-25 Bruno Haible <br...@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 <br...@clisp.org> tls tests: Add tests for destructors and races. * tests/test-tls.c: Include glthread/lock.h. (test_tls_dtorcheck1, test_tls_dtorcheck2, test_tls_racecheck): New functions. (main): Invoke them. * modules/tls-tests (Depends-on): Add lock. 2019-06-25 Bruno Haible <br...@clisp.org> tss tests: Add tests for destructors and races. * tests/test-tss.c (worker_thread): Fix typo in debug message. (test_tss_dtorcheck1, test_tss_dtorcheck2, test_tss_racecheck): New functions. (main): Invoke them. * modules/tls-tests (Depends-on): Add mtx.
>From d6bfe479691b16501375442865e328f7b0449279 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> 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 <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 | 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 <br...@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 <br...@clisp.org> + 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 <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 (); } 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 <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; 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
>From d1e821c9ade4128c23672133da264af4e11c1cba Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Wed, 26 Jun 2019 03:32:51 +0200 Subject: [PATCH 2/3] tls tests: Add tests for destructors and races. * tests/test-tls.c: Include glthread/lock.h. (test_tls_dtorcheck1, test_tls_dtorcheck2, test_tls_racecheck): New functions. (main): Invoke them. * modules/tls-tests (Depends-on): Add lock. --- ChangeLog | 9 ++ modules/tls-tests | 1 + tests/test-tls.c | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 337 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9e6d4da..3a0b8d6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ 2019-06-25 Bruno Haible <br...@clisp.org> + tls tests: Add tests for destructors and races. + * tests/test-tls.c: Include glthread/lock.h. + (test_tls_dtorcheck1, test_tls_dtorcheck2, test_tls_racecheck): New + functions. + (main): Invoke them. + * modules/tls-tests (Depends-on): Add lock. + +2019-06-25 Bruno Haible <br...@clisp.org> + windows-tls: Implement TLS key destructors for native Windows. * lib/windows-tls.h (glwthread_tls_process_destructors): New declaration. diff --git a/modules/tls-tests b/modules/tls-tests index b93f673..aec1935 100644 --- a/modules/tls-tests +++ b/modules/tls-tests @@ -3,6 +3,7 @@ tests/test-tls.c Depends-on: thread +lock stdint yield diff --git a/tests/test-tls.c b/tests/test-tls.c index 0cd6b1d..2ce9f0d 100644 --- a/tests/test-tls.c +++ b/tests/test-tls.c @@ -40,12 +40,6 @@ /* Whether to print debugging messages. */ #define ENABLE_DEBUGGING 0 -/* Number of simultaneous threads. */ -#define THREAD_COUNT 16 - -/* Number of operations performed in each thread. */ -#define REPEAT_COUNT 50000 - #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -53,6 +47,7 @@ #include "glthread/tls.h" #include "glthread/thread.h" +#include "glthread/lock.h" #include "glthread/yield.h" #if HAVE_DECL_ALARM @@ -84,6 +79,12 @@ perhaps_yield (void) /* ----------------------- Test thread-local storage ----------------------- */ +/* Number of simultaneous threads. */ +#define THREAD_COUNT 16 + +/* Number of operations performed in each thread. */ +#define REPEAT_COUNT 50000 + #define KEYS_COUNT 4 static gl_tls_key_t mykeys[KEYS_COUNT]; @@ -184,6 +185,307 @@ test_tls (void) } } +#undef KEYS_COUNT +#undef REPEAT_COUNT +#undef THREAD_COUNT + + +/* --------------- Test thread-local storage with destructors --------------- */ + +/* Number of simultaneous threads. */ +#define THREAD_COUNT 10 + +/* Number of keys to allocate in each thread. */ +#define KEYS_COUNT 10 + +gl_lock_define_initialized(static, sumlock) +static uintptr_t sum; + +static void +inc_sum (uintptr_t value) +{ + gl_lock_lock (sumlock); + sum += value; + gl_lock_unlock (sumlock); +} + +static void +destructor0 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 0) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor1 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 1) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor2 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 2) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor3 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 3) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor4 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 4) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor5 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 5) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor6 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 6) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor7 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 7) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor8 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 8) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor9 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 9) + abort (); + inc_sum ((uintptr_t) value); +} + +static void (*destructor_table[10]) (void *) = + { + destructor0, + destructor1, + destructor2, + destructor3, + destructor4, + destructor5, + destructor6, + destructor7, + destructor8, + destructor9 + }; + +static gl_tls_key_t dtorcheck_keys[THREAD_COUNT][KEYS_COUNT]; + +/* Worker thread that uses destructors that verify that the destructor belongs + to the right thread. */ +static void * +dtorcheck1_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + gl_tls_key_t *keys = dtorcheck_keys[id]; /* an array of KEYS_COUNT keys */ + int i; + + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_key_init (keys[i], destructor_table[i]); + + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_set (keys[i], (void *) (uintptr_t) (10 * id + i + 1)); + + return NULL; +} + +static void +test_tls_dtorcheck1 (void) +{ + gl_thread_t threads[THREAD_COUNT]; + unsigned int id; + int i; + uintptr_t expected_sum; + + sum = 0; + + /* Spawn the threads. */ + for (id = 0; id < THREAD_COUNT; id++) + threads[id] = gl_thread_create (dtorcheck1_thread, (void *) (uintptr_t) id); + + /* Wait for the threads to terminate. */ + for (id = 0; id < THREAD_COUNT; id++) + gl_thread_join (threads[id], NULL); + + /* Clean up the keys. */ + for (id = 0; id < THREAD_COUNT; id++) + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_key_destroy (dtorcheck_keys[id][i]); + + /* Check that the destructor was invoked for each key. */ + expected_sum = 10 * KEYS_COUNT * (THREAD_COUNT * (THREAD_COUNT - 1) / 2) + + THREAD_COUNT * (KEYS_COUNT * (KEYS_COUNT - 1) / 2) + + THREAD_COUNT * KEYS_COUNT; + if (sum != expected_sum) + abort (); +} + +/* Worker thread that uses destructors that verify that the destructor belongs + to the right key allocated within the thread. */ +static void * +dtorcheck2_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + gl_tls_key_t *keys = dtorcheck_keys[id]; /* an array of KEYS_COUNT keys */ + int i; + + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_key_init (keys[i], destructor_table[id]); + + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_set (keys[i], (void *) (uintptr_t) (10 * i + id + 1)); + + return NULL; +} + +static void +test_tls_dtorcheck2 (void) +{ + gl_thread_t threads[THREAD_COUNT]; + unsigned int id; + int i; + uintptr_t expected_sum; + + sum = 0; + + /* Spawn the threads. */ + for (id = 0; id < THREAD_COUNT; id++) + threads[id] = gl_thread_create (dtorcheck2_thread, (void *) (uintptr_t) id); + + /* Wait for the threads to terminate. */ + for (id = 0; id < THREAD_COUNT; id++) + gl_thread_join (threads[id], NULL); + + /* Clean up the keys. */ + for (id = 0; id < THREAD_COUNT; id++) + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_key_destroy (dtorcheck_keys[id][i]); + + /* Check that the destructor was invoked for each key. */ + expected_sum = 10 * THREAD_COUNT * (KEYS_COUNT * (KEYS_COUNT - 1) / 2) + + KEYS_COUNT * (THREAD_COUNT * (THREAD_COUNT - 1) / 2) + + THREAD_COUNT * KEYS_COUNT; + if (sum != expected_sum) + abort (); +} + +#undef KEYS_COUNT +#undef THREAD_COUNT + + +/* --- Test thread-local storage with with races between init and destroy --- */ + +/* Number of simultaneous threads. */ +#define THREAD_COUNT 10 + +/* Number of keys to allocate in each thread. */ +#define KEYS_COUNT 10 + +/* Number of times to destroy and reallocate a key in each thread. */ +#define REPEAT_COUNT 100000 + +static gl_tls_key_t racecheck_keys[THREAD_COUNT][KEYS_COUNT]; + +/* Worker thread that does many destructions and reallocations of keys, and also + uses destructors that verify that the destructor belongs to the right key. */ +static void * +racecheck_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + gl_tls_key_t *keys = racecheck_keys[id]; /* an array of KEYS_COUNT keys */ + int repeat; + int i; + + dbgprintf ("Worker %p started\n", gl_thread_self_pointer ()); + + for (i = 0; i < KEYS_COUNT; i++) + { + gl_tls_key_init (keys[i], destructor_table[i]); + gl_tls_set (keys[i], (void *) (uintptr_t) (10 * id + i + 1)); + } + + for (repeat = REPEAT_COUNT; repeat > 0; repeat--) + { + i = ((unsigned int) rand () >> 3) % KEYS_COUNT; + dbgprintf ("Worker %p reallocating key %d\n", gl_thread_self_pointer (), i); + gl_tls_key_destroy (keys[i]); + gl_tls_key_init (keys[i], destructor_table[i]); + gl_tls_set (keys[i], (void *) (uintptr_t) (10 * id + i + 1)); + } + + dbgprintf ("Worker %p dying.\n", gl_thread_self_pointer ()); + return NULL; +} + +static void +test_tls_racecheck (void) +{ + gl_thread_t threads[THREAD_COUNT]; + unsigned int id; + int i; + uintptr_t expected_sum; + + sum = 0; + + /* Spawn the threads. */ + for (id = 0; id < THREAD_COUNT; id++) + threads[id] = gl_thread_create (racecheck_thread, (void *) (uintptr_t) id); + + /* Wait for the threads to terminate. */ + for (id = 0; id < THREAD_COUNT; id++) + gl_thread_join (threads[id], NULL); + + /* Clean up the keys. */ + for (id = 0; id < THREAD_COUNT; id++) + for (i = 0; i < KEYS_COUNT; i++) + gl_tls_key_destroy (racecheck_keys[id][i]); + + /* Check that the destructor was invoked for each key. */ + expected_sum = 10 * KEYS_COUNT * (THREAD_COUNT * (THREAD_COUNT - 1) / 2) + + THREAD_COUNT * (KEYS_COUNT * (KEYS_COUNT - 1) / 2) + + THREAD_COUNT * KEYS_COUNT; + if (sum != expected_sum) + abort (); +} + +#undef REPEAT_COUNT +#undef KEYS_COUNT +#undef THREAD_COUNT + /* -------------------------------------------------------------------------- */ @@ -207,6 +509,25 @@ main () test_tls (); printf (" OK\n"); fflush (stdout); + printf ("Starting test_tls_dtorcheck1 ..."); fflush (stdout); + test_tls_dtorcheck1 (); + printf (" OK\n"); fflush (stdout); + + printf ("Starting test_tls_dtorcheck2 ..."); fflush (stdout); + test_tls_dtorcheck2 (); + printf (" OK\n"); fflush (stdout); + + /* This test hangs with the mingw-w64 winpthreads. */ +#if (defined _WIN32 && ! defined __CYGWIN__) && TEST_POSIX_THREADS + fputs ("Skipping test: it is known to hang with the mingw-w64 winpthreads.\n", + stderr); + exit (77); +#else + printf ("Starting test_tls_racecheck ..."); fflush (stdout); + test_tls_racecheck (); + printf (" OK\n"); fflush (stdout); +#endif + return 0; } -- 2.7.4
>From bacccb4e51dfdcf9c4481c6e27afc3d0c244d0d0 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Wed, 26 Jun 2019 03:32:57 +0200 Subject: [PATCH 3/3] tss tests: Add tests for destructors and races. * tests/test-tss.c (worker_thread): Fix typo in debug message. (test_tss_dtorcheck1, test_tss_dtorcheck2, test_tss_racecheck): New functions. (main): Invoke them. * modules/tls-tests (Depends-on): Add mtx. --- ChangeLog | 9 ++ modules/tss-tests | 1 + tests/test-tss.c | 338 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 340 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3a0b8d6..1e00145 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ 2019-06-25 Bruno Haible <br...@clisp.org> + tss tests: Add tests for destructors and races. + * tests/test-tss.c (worker_thread): Fix typo in debug message. + (test_tss_dtorcheck1, test_tss_dtorcheck2, test_tss_racecheck): New + functions. + (main): Invoke them. + * modules/tls-tests (Depends-on): Add mtx. + +2019-06-25 Bruno Haible <br...@clisp.org> + tls tests: Add tests for destructors and races. * tests/test-tls.c: Include glthread/lock.h. (test_tls_dtorcheck1, test_tls_dtorcheck2, test_tls_racecheck): New diff --git a/modules/tss-tests b/modules/tss-tests index 0fbb20e..c96c10d 100644 --- a/modules/tss-tests +++ b/modules/tss-tests @@ -4,6 +4,7 @@ tests/macros.h Depends-on: thrd +mtx stdint configure.ac: diff --git a/tests/test-tss.c b/tests/test-tss.c index 081e1b0..0870297 100644 --- a/tests/test-tss.c +++ b/tests/test-tss.c @@ -25,12 +25,6 @@ /* Whether to print debugging messages. */ #define ENABLE_DEBUGGING 0 -/* Number of simultaneous threads. */ -#define THREAD_COUNT 16 - -/* Number of operations performed in each thread. */ -#define REPEAT_COUNT 50000 - #include <threads.h> #include <stdint.h> #include <stdio.h> @@ -81,6 +75,12 @@ perhaps_yield (void) /* ----------------------- Test thread-local storage ----------------------- */ +/* Number of simultaneous threads. */ +#define THREAD_COUNT 16 + +/* Number of operations performed in each thread. */ +#define REPEAT_COUNT 50000 + #define KEYS_COUNT 4 static tss_t mykeys[KEYS_COUNT]; @@ -113,14 +113,14 @@ worker_thread (void *arg) perhaps_yield (); /* Initialize the per-thread storage. */ - dbgprintf ("Worker %p before first tls_set\n", thrd_current_pointer ()); + dbgprintf ("Worker %p before first tss_set\n", thrd_current_pointer ()); for (i = 0; i < KEYS_COUNT; i++) { unsigned int *ptr = (unsigned int *) malloc (sizeof (unsigned int)); *ptr = values[i]; ASSERT (tss_set (mykeys[i], ptr) == thrd_success); } - dbgprintf ("Worker %p after first tls_set\n", thrd_current_pointer ()); + dbgprintf ("Worker %p after first tss_set\n", thrd_current_pointer ()); perhaps_yield (); /* Shuffle around the pointers. */ @@ -182,6 +182,314 @@ test_tss (void) } } +#undef KEYS_COUNT +#undef REPEAT_COUNT +#undef THREAD_COUNT + + +/* --------------- Test thread-local storage with destructors --------------- */ + +/* Number of simultaneous threads. */ +#define THREAD_COUNT 10 + +/* Number of keys to allocate in each thread. */ +#define KEYS_COUNT 10 + +static mtx_t sumlock; +static uintptr_t sum; + +static void +inc_sum (uintptr_t value) +{ + ASSERT (mtx_lock (&sumlock) == thrd_success); + sum += value; + ASSERT (mtx_unlock (&sumlock) == thrd_success); +} + +static void +destructor0 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 0) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor1 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 1) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor2 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 2) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor3 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 3) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor4 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 4) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor5 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 5) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor6 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 6) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor7 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 7) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor8 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 8) + abort (); + inc_sum ((uintptr_t) value); +} + +static void +destructor9 (void *value) +{ + if ((((uintptr_t) value - 1) % 10) != 9) + abort (); + inc_sum ((uintptr_t) value); +} + +static void (*destructor_table[10]) (void *) = + { + destructor0, + destructor1, + destructor2, + destructor3, + destructor4, + destructor5, + destructor6, + destructor7, + destructor8, + destructor9 + }; + +static tss_t dtorcheck_keys[THREAD_COUNT][KEYS_COUNT]; + +/* Worker thread that uses destructors that verify that the destructor belongs + to the right thread. */ +static int +dtorcheck1_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + tss_t *keys = dtorcheck_keys[id]; /* an array of KEYS_COUNT keys */ + int i; + + for (i = 0; i < KEYS_COUNT; i++) + ASSERT (tss_create (&keys[i], destructor_table[i]) == thrd_success); + + for (i = 0; i < KEYS_COUNT; i++) + ASSERT (tss_set (keys[i], (void *) (uintptr_t) (10 * id + i + 1)) + == thrd_success); + + return 0; +} + +static void +test_tss_dtorcheck1 (void) +{ + thrd_t threads[THREAD_COUNT]; + unsigned int id; + int i; + uintptr_t expected_sum; + + sum = 0; + + /* Spawn the threads. */ + for (id = 0; id < THREAD_COUNT; id++) + ASSERT (thrd_create (&threads[id], dtorcheck1_thread, (void *) (uintptr_t) id) + == thrd_success); + + /* Wait for the threads to terminate. */ + for (id = 0; id < THREAD_COUNT; id++) + ASSERT (thrd_join (threads[id], NULL) == thrd_success); + + /* Clean up the keys. */ + for (id = 0; id < THREAD_COUNT; id++) + for (i = 0; i < KEYS_COUNT; i++) + tss_delete (dtorcheck_keys[id][i]); + + /* Check that the destructor was invoked for each key. */ + expected_sum = 10 * KEYS_COUNT * (THREAD_COUNT * (THREAD_COUNT - 1) / 2) + + THREAD_COUNT * (KEYS_COUNT * (KEYS_COUNT - 1) / 2) + + THREAD_COUNT * KEYS_COUNT; + if (sum != expected_sum) + abort (); +} + +/* Worker thread that uses destructors that verify that the destructor belongs + to the right key allocated within the thread. */ +static int +dtorcheck2_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + tss_t *keys = dtorcheck_keys[id]; /* an array of KEYS_COUNT keys */ + int i; + + for (i = 0; i < KEYS_COUNT; i++) + ASSERT (tss_create (&keys[i], destructor_table[id]) == thrd_success); + + for (i = 0; i < KEYS_COUNT; i++) + ASSERT (tss_set (keys[i], (void *) (uintptr_t) (10 * i + id + 1)) + == thrd_success); + + return 0; +} + +static void +test_tss_dtorcheck2 (void) +{ + thrd_t threads[THREAD_COUNT]; + unsigned int id; + int i; + uintptr_t expected_sum; + + sum = 0; + + /* Spawn the threads. */ + for (id = 0; id < THREAD_COUNT; id++) + ASSERT (thrd_create (&threads[id], dtorcheck2_thread, (void *) (uintptr_t) id) + == thrd_success); + + /* Wait for the threads to terminate. */ + for (id = 0; id < THREAD_COUNT; id++) + ASSERT (thrd_join (threads[id], NULL) == thrd_success); + + /* Clean up the keys. */ + for (id = 0; id < THREAD_COUNT; id++) + for (i = 0; i < KEYS_COUNT; i++) + tss_delete (dtorcheck_keys[id][i]); + + /* Check that the destructor was invoked for each key. */ + expected_sum = 10 * THREAD_COUNT * (KEYS_COUNT * (KEYS_COUNT - 1) / 2) + + KEYS_COUNT * (THREAD_COUNT * (THREAD_COUNT - 1) / 2) + + THREAD_COUNT * KEYS_COUNT; + if (sum != expected_sum) + abort (); +} + +#undef KEYS_COUNT +#undef THREAD_COUNT + + +/* --- Test thread-local storage with with races between init and destroy --- */ + +/* Number of simultaneous threads. */ +#define THREAD_COUNT 10 + +/* Number of keys to allocate in each thread. */ +#define KEYS_COUNT 10 + +/* Number of times to destroy and reallocate a key in each thread. */ +#define REPEAT_COUNT 100000 + +static tss_t racecheck_keys[THREAD_COUNT][KEYS_COUNT]; + +/* Worker thread that does many destructions and reallocations of keys, and also + uses destructors that verify that the destructor belongs to the right key. */ +static int +racecheck_thread (void *arg) +{ + unsigned int id = (unsigned int) (uintptr_t) arg; + tss_t *keys = racecheck_keys[id]; /* an array of KEYS_COUNT keys */ + int repeat; + int i; + + dbgprintf ("Worker %p started\n", thrd_current_pointer ()); + + for (i = 0; i < KEYS_COUNT; i++) + { + ASSERT (tss_create (&keys[i], destructor_table[i]) == thrd_success); + ASSERT (tss_set (keys[i], (void *) (uintptr_t) (10 * id + i + 1)) + == thrd_success); + } + + for (repeat = REPEAT_COUNT; repeat > 0; repeat--) + { + i = ((unsigned int) rand () >> 3) % KEYS_COUNT; + dbgprintf ("Worker %p reallocating key %d\n", thrd_current_pointer (), i); + tss_delete (keys[i]); + ASSERT (tss_create (&keys[i], destructor_table[i]) == thrd_success); + ASSERT (tss_set (keys[i], (void *) (uintptr_t) (10 * id + i + 1)) + == thrd_success); + } + + dbgprintf ("Worker %p dying.\n", thrd_current_pointer ()); + return 0; +} + +static void +test_tss_racecheck (void) +{ + thrd_t threads[THREAD_COUNT]; + unsigned int id; + int i; + uintptr_t expected_sum; + + sum = 0; + + /* Spawn the threads. */ + for (id = 0; id < THREAD_COUNT; id++) + ASSERT (thrd_create (&threads[id], racecheck_thread, (void *) (uintptr_t) id) + == thrd_success); + + /* Wait for the threads to terminate. */ + for (id = 0; id < THREAD_COUNT; id++) + ASSERT (thrd_join (threads[id], NULL) == thrd_success); + + /* Clean up the keys. */ + for (id = 0; id < THREAD_COUNT; id++) + for (i = 0; i < KEYS_COUNT; i++) + tss_delete (racecheck_keys[id][i]); + + /* Check that the destructor was invoked for each key. */ + expected_sum = 10 * KEYS_COUNT * (THREAD_COUNT * (THREAD_COUNT - 1) / 2) + + THREAD_COUNT * (KEYS_COUNT * (KEYS_COUNT - 1) / 2) + + THREAD_COUNT * KEYS_COUNT; + if (sum != expected_sum) + abort (); +} + +#undef REPEAT_COUNT +#undef KEYS_COUNT +#undef THREAD_COUNT + /* -------------------------------------------------------------------------- */ @@ -196,9 +504,23 @@ main () alarm (alarm_value); #endif + ASSERT (mtx_init (&sumlock, mtx_plain) == thrd_success); + printf ("Starting test_tss ..."); fflush (stdout); test_tss (); printf (" OK\n"); fflush (stdout); + printf ("Starting test_tss_dtorcheck1 ..."); fflush (stdout); + test_tss_dtorcheck1 (); + printf (" OK\n"); fflush (stdout); + + printf ("Starting test_tss_dtorcheck2 ..."); fflush (stdout); + test_tss_dtorcheck2 (); + printf (" OK\n"); fflush (stdout); + + printf ("Starting test_tss_racecheck ..."); fflush (stdout); + test_tss_racecheck (); + printf (" OK\n"); fflush (stdout); + return 0; } -- 2.7.4