| /* Test malloc with concurrent thread termination. |
| Copyright (C) 2015-2016 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This thread spawns a number of outer threads, equal to the arena |
| limit. The outer threads run a loop which start and join two |
| different kinds of threads: the first kind allocates (attaching an |
| arena to the thread; malloc_first_thread) and waits, the second |
| kind waits and allocates (wait_first_threads). Both kinds of |
| threads exit immediately after waiting. The hope is that this will |
| exhibit races in thread termination and arena management, |
| particularly related to the arena free list. */ |
| |
| #include <errno.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #define TIMEOUT 7 |
| |
| static bool termination_requested; |
| static int inner_thread_count = 4; |
| 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_thread_count); |
| if (threads == NULL) |
| { |
| printf ("error: calloc: %m\n"); |
| abort (); |
| } |
| |
| while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED)) |
| { |
| pthread_barrier_t barrier; |
| int ret = pthread_barrier_init (&barrier, NULL, inner_thread_count + 1); |
| if (ret != 0) |
| { |
| errno = ret; |
| printf ("pthread_barrier_init: %m\n"); |
| abort (); |
| } |
| for (int i = 0; i < inner_thread_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_thread_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; |
| } |
| |
| static int |
| do_test (void) |
| { |
| /* The number of top-level threads should be equal to the number of |
| arenas. See arena_get2. */ |
| long outer_thread_count = sysconf (_SC_NPROCESSORS_ONLN); |
| if (outer_thread_count >= 1) |
| { |
| /* See NARENAS_FROM_NCORES in malloc.c. */ |
| if (sizeof (long) == 4) |
| outer_thread_count *= 2; |
| else |
| outer_thread_count *= 8; |
| } |
| |
| /* Leave some room for shutting down all threads gracefully. */ |
| int timeout = TIMEOUT - 2; |
| |
| pthread_t *threads = calloc (sizeof (*threads), outer_thread_count); |
| if (threads == NULL) |
| { |
| printf ("error: calloc: %m\n"); |
| abort (); |
| } |
| |
| for (long i = 0; i < outer_thread_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_n (&termination_requested, true, __ATOMIC_RELAXED); |
| |
| for (long i = 0; i < outer_thread_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; |
| } |
| |
| #define TEST_FUNCTION do_test () |
| #include "../test-skeleton.c" |