/* Multithread-safety test for rand().
Copyright (C) 2023 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 , 2023. */
/* Whether to help the scheduler through explicit yield().
Uncomment this to see if the operating system has a fair scheduler. */
#define EXPLICIT_YIELD 1
/* Number of simultaneous threads. */
#define THREAD_COUNT 4
/* Number of rand() invocations operations performed in each thread.
This value is chosen so that the unit test terminates quickly.
To reliably determine whether a rand() implementation is multithread-safe,
set REPEAT_COUNT to 1000000 and run the test 100 times:
$ for i in `seq 100`; do ./test-random-mt; done
*/
#define REPEAT_COUNT 1000000
/* Specification. */
#include
#include
#include
#include
#if EXPLICIT_YIELD
# include
# define yield() sched_yield ()
#else
# define yield()
#endif
/* This test runs REPEAT_COUNT invocations of rand() in each thread and stores
the result, then compares the first REPEAT_COUNT among these
THREAD_COUNT * REPEAT_COUNT
random numbers against a precomputed sequence with the same seed. */
static void *
random_invocator_thread (void *arg)
{
int *storage = (int *) arg;
int repeat;
for (repeat = 0; repeat < REPEAT_COUNT; repeat++)
{
storage[repeat] = rand ();
yield ();
}
return NULL;
}
int
main ()
{
unsigned int seed = 19891109;
/* First, get the expected sequence of rand() results. */
srand (seed);
int *expected = (int *) malloc (REPEAT_COUNT * sizeof (int));
assert (expected != NULL);
{
int repeat;
for (repeat = 0; repeat < REPEAT_COUNT; repeat++)
expected[repeat] = rand ();
}
/* Then, run REPEAT_COUNT invocations of rand() each, in THREAD_COUNT
separate threads. */
pthread_t threads[THREAD_COUNT];
int *thread_results[THREAD_COUNT];
srand (seed);
{
int i;
for (i = 0; i < THREAD_COUNT; i++)
{
thread_results[i] = (int *) malloc (REPEAT_COUNT * sizeof (int));
assert (thread_results[i] != NULL);
}
for (i = 0; i < THREAD_COUNT; i++)
assert (pthread_create (&threads[i], NULL, random_invocator_thread, thread_results[i]) == 0);
}
/* Wait for the threads to terminate. */
{
int i;
for (i = 0; i < THREAD_COUNT; i++)
assert (pthread_join (threads[i], NULL) == 0);
}
/* Finally, determine whether the threads produced the same sequence of
rand() results. */
{
int expected_index;
int result_index[THREAD_COUNT];
int i;
for (i = 0; i < THREAD_COUNT; i++)
result_index[i] = 0;
for (expected_index = 0; expected_index < REPEAT_COUNT; expected_index++)
{
int expected_value = expected[expected_index];
for (i = 0; i < THREAD_COUNT; i++)
{
if (thread_results[i][result_index[i]] == expected_value)
{
result_index[i]++;
break;
}
}
if (i == THREAD_COUNT)
{
if (expected_index == 0)
{
/* This occurs on platforms like OpenBSD, where srand() has no
effect and rand() always return non-deterministic values.
Mark the test as SKIP. */
fprintf (stderr, "Skipping test: rand() is non-deterministic.\n");
return 77;
}
else
{
fprintf (stderr, "Expected value #%d not found in multithreaded results.\n",
expected_index);
return 1;
}
}
}
}
return 0;
}