/* Test malloc with concurrent thread creation and termination.
Copyright (C) 2015 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
. */
#include
#include
#include
#include
#include
#include
#include
static void
usage (char **argv)
{
fprintf (stderr, "usage: %s OUTER INNER TIMEOUT\n", argv[0]);
exit (2);
}
static _Atomic bool termination_requested;
static int inner_count;
static size_t malloc_size = 32;
static void
__attribute__ ((noinline, noclone))
unoptimized_free (void *ptr)
{
free (ptr);
}
static void *
malloc_first_thread (void * closure)
{
pthread_barrier_t *barrier = closure;
void *ptr = malloc (malloc_size);
if (ptr == NULL)
{
printf ("error: malloc: %m\n");
abort ();
}
int ret = pthread_barrier_wait (barrier);
if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
{
errno = ret;
printf ("error: pthread_barrier_wait: %m\n");
abort ();
}
unoptimized_free (ptr);
return NULL;
}
static void *
wait_first_thread (void * closure)
{
pthread_barrier_t *barrier = closure;
int ret = pthread_barrier_wait (barrier);
if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
{
errno = ret;
printf ("error: pthread_barrier_wait: %m\n");
abort ();
}
void *ptr = malloc (malloc_size);
if (ptr == NULL)
{
printf ("error: malloc: %m\n");
abort ();
}
unoptimized_free (ptr);
return NULL;
}
static void *
outer_thread (void *closure)
{
pthread_t *threads = calloc (sizeof (*threads), inner_count);
if (threads == NULL)
{
printf ("error: calloc: %m\n");
abort ();
}
while (!atomic_load_explicit (&termination_requested,
memory_order_relaxed)) {
pthread_barrier_t barrier;
int ret = pthread_barrier_init (&barrier, NULL, inner_count + 1);
if (ret != 0)
{
errno = ret;
printf ("pthread_barrier_init: %m\n");
abort ();
}
for (int i = 0; i < inner_count; ++i)
{
void *(*func) (void *);
if ((i % 2) == 0)
func = malloc_first_thread;
else
func = wait_first_thread;
ret = pthread_create (threads + i, NULL, func, &barrier);
if (ret != 0)
{
errno = ret;
printf ("error: pthread_create: %m\n");
abort ();
}
}
ret = pthread_barrier_wait (&barrier);
if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
{
errno = ret;
printf ("pthread_wait: %m\n");
abort ();
}
for (int i = 0; i < inner_count; ++i)
{
ret = pthread_join (threads[i], NULL);
if (ret != 0)
{
ret = errno;
printf ("error: pthread_join: %m\n");
abort ();
}
}
ret = pthread_barrier_destroy (&barrier);
if (ret != 0)
{
ret = errno;
printf ("pthread_barrier_destroy: %m\n");
abort ();
}
}
free (threads);
return NULL;
}
int
main (int argc, char **argv)
{
if (argc != 4)
usage (argv);
int outer_count = atoi (argv[1]);
inner_count = atoi (argv[2]);
int timeout = atoi (argv[3]);
if ((inner_count % 2) != 0)
++inner_count;
if (outer_count <= 0 || inner_count <= 0 || timeout <= 0)
usage (argv);
pthread_t *threads = calloc (sizeof (*threads), outer_count);
if (threads == NULL)
{
printf ("error: calloc: %m\n");
abort ();
}
for (int i = 0; i < outer_count; ++i)
{
int ret = pthread_create (threads + i, NULL, outer_thread, NULL);
if (ret != 0)
{
errno = ret;
printf ("error: pthread_create: %m\n");
abort ();
}
}
struct timespec ts = {timeout, 0};
if (nanosleep (&ts, NULL))
{
printf ("error: error: nanosleep: %m\n");
abort ();
}
atomic_store_explicit (&termination_requested, true, memory_order_relaxed);
for (int i = 0; i < outer_count; ++i)
{
int ret = pthread_join (threads[i], NULL);
if (ret != 0)
{
errno = ret;
printf ("error: pthread_join: %m\n");
abort ();
}
}
free (threads);
return 0;
}