>From 29a3fb3ec79f1344a1476907d197b8b37c4699cc Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Tue, 16 Feb 2021 01:31:39 +0100 Subject: [PATCH] linked-list test: Add test for SIGNAL_SAFE_LIST. (It currently fails.) * tests/test-asyncsafe-linked_list.sh: New file. * tests/test-asyncsafe-linked_list.c: New file. * modules/linked-list-tests (Files): Add them. (Depends-on): Add thread, yield, nanosleep, sigaction, sigprocmask. (Makefile.am): Arrange to compile test-asyncsafe-linked_list.c and run test-asyncsafe-linked_list.sh. --- ChangeLog | 10 ++ modules/linked-list-tests | 12 +- tests/test-asyncsafe-linked_list.c | 322 ++++++++++++++++++++++++++++++++++++ tests/test-asyncsafe-linked_list.sh | 16 ++ 4 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 tests/test-asyncsafe-linked_list.c create mode 100755 tests/test-asyncsafe-linked_list.sh diff --git a/ChangeLog b/ChangeLog index 29984bc..5947755 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2021-02-15 Bruno Haible + + linked-list test: Add test for SIGNAL_SAFE_LIST. (It currently fails.) + * tests/test-asyncsafe-linked_list.sh: New file. + * tests/test-asyncsafe-linked_list.c: New file. + * modules/linked-list-tests (Files): Add them. + (Depends-on): Add thread, yield, nanosleep, sigaction, sigprocmask. + (Makefile.am): Arrange to compile test-asyncsafe-linked_list.c and run + test-asyncsafe-linked_list.sh. + 2021-02-14 Bruno Haible simple-atomic: Add tests. diff --git a/modules/linked-list-tests b/modules/linked-list-tests index c34e49e..bf21bee 100644 --- a/modules/linked-list-tests +++ b/modules/linked-list-tests @@ -1,12 +1,20 @@ Files: tests/test-linked_list.c +tests/test-asyncsafe-linked_list.sh +tests/test-asyncsafe-linked_list.c tests/macros.h Depends-on: array-list +thread +yield +nanosleep +sigaction +sigprocmask configure.ac: Makefile.am: -TESTS += test-linked_list -check_PROGRAMS += test-linked_list +TESTS += test-linked_list test-asyncsafe-linked_list.sh +check_PROGRAMS += test-linked_list test-asyncsafe-linked_list +test_asyncsafe_linked_list_LDADD = $(LDADD) @LIBMULTITHREAD@ @YIELD_LIB@ diff --git a/tests/test-asyncsafe-linked_list.c b/tests/test-asyncsafe-linked_list.c new file mode 100644 index 0000000..56e76bb --- /dev/null +++ b/tests/test-asyncsafe-linked_list.c @@ -0,0 +1,322 @@ +/* Test of async-safe sequential list data type implementation. + Copyright (C) 2021 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Bruno Haible , 2021. */ + +#include + +/* Work around GCC bug 44511. */ +#if 4 < __GNUC__ + (3 <= __GNUC_MINOR__) +# pragma GCC diagnostic ignored "-Wreturn-type" +#endif + +#include "gl_linked_list.h" + +#if SIGNAL_SAFE_LIST + +# if USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS /* || USE_WINDOWS_THREADS */ +/* This test works only with POSIX compatible threads. + With Windows threads, send_signal() has to be implemented as + raise (SIGINT); + hence only test == 2 tests anything, and in fact only 5 signals arrive. + This small test is not able to detect a buggy implementation. Therefore + mark the test as skipped in this case. */ + +# include +# include +# include +# include +# include + +# include "glthread/thread.h" +# include "glthread/yield.h" + +# include "macros.h" + +# define RANDOM(n) (rand () % (n)) +# define RANDOM_OBJECT() ((void *) (uintptr_t) RANDOM (10000)) + +/* test == 1: signals are executed in an idle thread. + test == 2: signals are executed in the signal-sending thread. + test == 3: signals are executed in the mutator thread. */ +static int volatile test; + +static uintptr_t volatile sum_before_current_operation; +static uintptr_t volatile sum_after_current_operation; + +static gl_list_t volatile list1; + +static gl_thread_t volatile signal_target; + +/* Statistics. */ +static unsigned int volatile num_mutations; +static unsigned int volatile num_signals_sent; +static unsigned int volatile num_signals_arrived; + +/* Blocks the SIGINT signal in the current thread. */ +static void +block_sigint (void) +{ + sigset_t mask; + + sigemptyset (&mask); + sigaddset (&mask, SIGINT); + sigprocmask (SIG_BLOCK, &mask, NULL); /* equivalent to pthread_sigmask */ +} + +/* This thread is idle. */ +static void * +idle_thread (void *arg) +{ + for (;;) + gl_thread_yield (); + + /*NOTREACHED*/ +} + +/* The signal handler iterates through the list and verifies that the sum of + all elements in the list is one of the two allowed values. */ +static _GL_ASYNC_SAFE void +sigint_handler (int signo) +{ + num_signals_arrived++; + + uintptr_t sum = 0; + { + gl_list_iterator_t iter = gl_list_iterator (list1); + const void *elt; + + while (gl_list_iterator_next (&iter, &elt, NULL)) + sum += (uintptr_t) elt; + gl_list_iterator_free (&iter); + } + if (!(sum == sum_before_current_operation + || sum == sum_after_current_operation)) + { + /* POSIX does not allow uses of stdio from within a signal handler, but + it should work here, because the rest of the program does not use + stdio. */ + fprintf (stderr, "sum = %lu, expected %lu or %lu\n", + (unsigned long) sum, + (unsigned long) sum_before_current_operation, + (unsigned long) sum_after_current_operation); + fflush (stderr); + abort (); + } +} + +/* Sends a SIGINT signal to the current process. */ +static void +send_signal (void) +{ +#if 0 + /* This does not work for test != 2, because raise() sends the signal to the + current thread always, and if test != 2 the signal is eternally blocked + in the current thread; thus it will never get delivered. */ + raise (SIGINT); +#else + /* This works: Either + kill (getpid (), SIGINT); + or + pthread_kill (signal_target, SIGINT); + The difference is that for kill(), the OS determines the (only) thread that + has not blocked the signal, whereas for pthread_kill() we have manually + determined this thread. */ + kill (getpid (), SIGINT); +#endif +} + +/* This thread permanently sends SIGINT signals. */ +static void * +signal_sending_thread (void *arg) +{ + if (test != 2) + block_sigint (); + + int repeat; + + for (repeat = 1000; repeat > 0; repeat--) + { + num_signals_sent++; send_signal (); + num_signals_sent++; send_signal (); + num_signals_sent++; send_signal (); + num_signals_sent++; send_signal (); + num_signals_sent++; send_signal (); + { + struct timespec ts; + ts.tv_sec = 0; ts.tv_nsec = 1000000; + nanosleep (&ts, NULL); + } + gl_thread_yield (); + } + + printf ("Sent %u signals. Received %u signals. Done after %u mutations.\n", + num_signals_sent, num_signals_arrived, num_mutations); + + exit (0); + + /*NOTREACHED*/ +} + +/* This thread repeatedly adds and removes objects from the list. */ +static void * +mutator_thread (void *arg) +{ + if (test != 3) + block_sigint (); + + gl_list_t list2 = (gl_list_t) arg; + + for (num_mutations = 0; ; num_mutations++) + { + unsigned int operation = RANDOM (2); + switch (operation) + { + case 0: /* Add an element. */ + { + const void *obj = RANDOM_OBJECT (); + ASSERT (gl_list_nx_add_first (list2, obj) != NULL); + sum_after_current_operation = + sum_before_current_operation + (uintptr_t) obj; + ASSERT (gl_list_nx_add_first (list1, obj) != NULL); + sum_before_current_operation = sum_after_current_operation; + } + break; + case 1: /* Remove an element. */ + if (gl_list_size (list2) > 0) + { + size_t index = RANDOM (gl_list_size (list2)); + const void *obj = gl_list_get_at (list2, index); + ASSERT (gl_list_remove (list2, obj)); + sum_after_current_operation = + sum_before_current_operation - (uintptr_t) obj; + ASSERT (gl_list_remove (list1, obj)); + sum_before_current_operation = sum_after_current_operation; + } + break; + } + } + + /*NOTREACHED*/ +} + +int +main (int argc, char *argv[]) +{ + test = atoi (argv[1]); + + /* Allow the user to provide a non-default random seed on the command line. */ + if (argc > 2) + srand (atoi (argv[2])); + + gl_list_t list2; + /* Initialize list1 and list2. */ + { + size_t initial_size = RANDOM (50); + const void **contents = + (const void **) malloc (initial_size * sizeof (const void *)); + size_t i; + + for (i = 0; i < initial_size; i++) + contents[i] = RANDOM_OBJECT (); + + list1 = gl_list_nx_create_empty (GL_LINKED_LIST, NULL, NULL, NULL, true); + ASSERT (list1 != NULL); + for (i = 0; i < initial_size; i++) + ASSERT (gl_list_nx_add_first (list1, contents[i]) != NULL); + + list2 = gl_list_nx_create_empty (GL_LINKED_LIST, NULL, NULL, NULL, true); + ASSERT (list2 != NULL); + for (i = 0; i < initial_size; i++) + ASSERT (gl_list_nx_add_last (list2, contents[i]) != NULL); + + uintptr_t initial_sum = 0; + for (i = 0; i < initial_size; i++) + initial_sum += (uintptr_t) contents[i]; + sum_before_current_operation = initial_sum; + sum_after_current_operation = initial_sum; + } + + /* Install the signal handler. + It needs to be done with sigaction(), not signal(), otherwise on Solaris + and AIX the program crashes at the second incoming SIGINT. */ + #if 0 + signal (SIGINT, sigint_handler); + #else + { + struct sigaction action; + action.sa_handler = sigint_handler; + action.sa_flags = SA_RESTART | SA_NODEFER; + sigemptyset (&action.sa_mask); + ASSERT (sigaction (SIGINT, &action, NULL) == 0); + } + #endif + + /* Launch the threads. */ + switch (test) + { + case 1: + { + signal_target = gl_thread_create (idle_thread, NULL); + gl_thread_create (mutator_thread, list2); + signal_sending_thread (NULL); + abort (); + } + + case 2: + { + gl_thread_create (mutator_thread, list2); + signal_target = gl_thread_self (); signal_sending_thread (NULL); + abort (); + } + + case 3: + { + signal_target = gl_thread_create (mutator_thread, list2); + signal_sending_thread (NULL); + abort (); + } + + default: + ASSERT (false); + } +} + +# else + +# include + +int +main () +{ + fputs ("Skipping test: POSIX compatible multithreading not enabled\n", stderr); + return 77; +} + +# endif + +#else + +# include + +int +main () +{ + fputs ("Skipping test: signal-safety of linked lists is not enabled\n", stderr); + return 77; +} + +#endif diff --git a/tests/test-asyncsafe-linked_list.sh b/tests/test-asyncsafe-linked_list.sh new file mode 100755 index 0000000..c2ad176 --- /dev/null +++ b/tests/test-asyncsafe-linked_list.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +st=0 +for i in 1 2 3 ; do + ${CHECKER} ./test-asyncsafe-linked_list${EXEEXT} $i + result=$? + if test $result = 77; then + st=77 + break + fi + if test $result != 0; then + echo test-asyncsafe-linked_list.sh: test case $i failed >&2 + st=1 + fi +done +exit $st -- 2.7.4