mirror of
https://git.savannah.gnu.org/git/gnulib.git
synced 2025-08-08 17:22:05 +03:00
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.
This commit is contained in:
10
ChangeLog
10
ChangeLog
@@ -1,3 +1,13 @@
|
|||||||
|
2021-02-15 Bruno Haible <bruno@clisp.org>
|
||||||
|
|
||||||
|
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 <bruno@clisp.org>
|
2021-02-14 Bruno Haible <bruno@clisp.org>
|
||||||
|
|
||||||
simple-atomic: Add tests.
|
simple-atomic: Add tests.
|
||||||
|
@@ -1,12 +1,20 @@
|
|||||||
Files:
|
Files:
|
||||||
tests/test-linked_list.c
|
tests/test-linked_list.c
|
||||||
|
tests/test-asyncsafe-linked_list.sh
|
||||||
|
tests/test-asyncsafe-linked_list.c
|
||||||
tests/macros.h
|
tests/macros.h
|
||||||
|
|
||||||
Depends-on:
|
Depends-on:
|
||||||
array-list
|
array-list
|
||||||
|
thread
|
||||||
|
yield
|
||||||
|
nanosleep
|
||||||
|
sigaction
|
||||||
|
sigprocmask
|
||||||
|
|
||||||
configure.ac:
|
configure.ac:
|
||||||
|
|
||||||
Makefile.am:
|
Makefile.am:
|
||||||
TESTS += test-linked_list
|
TESTS += test-linked_list test-asyncsafe-linked_list.sh
|
||||||
check_PROGRAMS += test-linked_list
|
check_PROGRAMS += test-linked_list test-asyncsafe-linked_list
|
||||||
|
test_asyncsafe_linked_list_LDADD = $(LDADD) @LIBMULTITHREAD@ @YIELD_LIB@
|
||||||
|
322
tests/test-asyncsafe-linked_list.c
Normal file
322
tests/test-asyncsafe-linked_list.c
Normal file
@@ -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 <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
/* Written by Bruno Haible <bruno@clisp.org>, 2021. */
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
|
||||||
|
/* 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 <signal.h>
|
||||||
|
# include <stdint.h>
|
||||||
|
# include <stdlib.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
# include <time.h>
|
||||||
|
|
||||||
|
# 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 <stdio.h>
|
||||||
|
|
||||||
|
int
|
||||||
|
main ()
|
||||||
|
{
|
||||||
|
fputs ("Skipping test: POSIX compatible multithreading not enabled\n", stderr);
|
||||||
|
return 77;
|
||||||
|
}
|
||||||
|
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
# include <stdio.h>
|
||||||
|
|
||||||
|
int
|
||||||
|
main ()
|
||||||
|
{
|
||||||
|
fputs ("Skipping test: signal-safety of linked lists is not enabled\n", stderr);
|
||||||
|
return 77;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
16
tests/test-asyncsafe-linked_list.sh
Executable file
16
tests/test-asyncsafe-linked_list.sh
Executable file
@@ -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
|
Reference in New Issue
Block a user