29d794863c
Previously, a thread M invoking fork would acquire locks in this order: (M1) malloc arena locks (in the registered fork handler) (M2) libio list lock A thread F invoking flush (NULL) would acquire locks in this order: (F1) libio list lock (F2) individual _IO_FILE locks A thread G running getdelim would use this order: (G1) _IO_FILE lock (G2) malloc arena lock After executing (M1), (F1), (G1), none of the threads can make progress. This commit changes the fork lock order to: (M'1) libio list lock (M'2) malloc arena locks It explicitly encodes the lock order in the implementations of fork, and does not rely on the registration order, thus avoiding the deadlock.
221 lines
5.3 KiB
C
221 lines
5.3 KiB
C
/* Test concurrent fork, getline, and fflush (NULL).
|
|
Copyright (C) 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; see the file COPYING.LIB. If
|
|
not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <malloc.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
|
|
static int do_test (void);
|
|
#define TEST_FUNCTION do_test ()
|
|
#include "../test-skeleton.c"
|
|
|
|
enum {
|
|
/* Number of threads which call fork. */
|
|
fork_thread_count = 4,
|
|
/* Number of threads which call getline (and, indirectly,
|
|
malloc). */
|
|
read_thread_count = 8,
|
|
};
|
|
|
|
static bool termination_requested;
|
|
|
|
static void *
|
|
fork_thread_function (void *closure)
|
|
{
|
|
while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
|
|
{
|
|
pid_t pid = fork ();
|
|
if (pid < 0)
|
|
{
|
|
printf ("error: fork: %m\n");
|
|
abort ();
|
|
}
|
|
else if (pid == 0)
|
|
_exit (17);
|
|
|
|
int status;
|
|
if (waitpid (pid, &status, 0) < 0)
|
|
{
|
|
printf ("error: waitpid: %m\n");
|
|
abort ();
|
|
}
|
|
if (!WIFEXITED (status) || WEXITSTATUS (status) != 17)
|
|
{
|
|
printf ("error: waitpid returned invalid status: %d\n", status);
|
|
abort ();
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static char *file_to_read;
|
|
|
|
static void *
|
|
read_thread_function (void *closure)
|
|
{
|
|
FILE *f = fopen (file_to_read, "r");
|
|
if (f == NULL)
|
|
{
|
|
printf ("error: fopen (%s): %m\n", file_to_read);
|
|
abort ();
|
|
}
|
|
|
|
while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
|
|
{
|
|
rewind (f);
|
|
char *line = NULL;
|
|
size_t line_allocated = 0;
|
|
ssize_t ret = getline (&line, &line_allocated, f);
|
|
if (ret < 0)
|
|
{
|
|
printf ("error: getline: %m\n");
|
|
abort ();
|
|
}
|
|
free (line);
|
|
}
|
|
fclose (f);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *
|
|
flushall_thread_function (void *closure)
|
|
{
|
|
while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
|
|
if (fflush (NULL) != 0)
|
|
{
|
|
printf ("error: fflush (NULL): %m\n");
|
|
abort ();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
create_threads (pthread_t *threads, size_t count, void *(*func) (void *))
|
|
{
|
|
for (size_t i = 0; i < count; ++i)
|
|
{
|
|
int ret = pthread_create (threads + i, NULL, func, NULL);
|
|
if (ret != 0)
|
|
{
|
|
errno = ret;
|
|
printf ("error: pthread_create: %m\n");
|
|
abort ();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
join_threads (pthread_t *threads, size_t count)
|
|
{
|
|
for (size_t i = 0; i < count; ++i)
|
|
{
|
|
int ret = pthread_join (threads[i], NULL);
|
|
if (ret != 0)
|
|
{
|
|
errno = ret;
|
|
printf ("error: pthread_join: %m\n");
|
|
abort ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create a file which consists of a single long line, and assigns
|
|
file_to_read. The hope is that this triggers an allocation in
|
|
getline which needs a lock. */
|
|
static void
|
|
create_file_with_large_line (void)
|
|
{
|
|
int fd = create_temp_file ("bug19431-large-line", &file_to_read);
|
|
if (fd < 0)
|
|
{
|
|
printf ("error: create_temp_file: %m\n");
|
|
abort ();
|
|
}
|
|
FILE *f = fdopen (fd, "w+");
|
|
if (f == NULL)
|
|
{
|
|
printf ("error: fdopen: %m\n");
|
|
abort ();
|
|
}
|
|
for (int i = 0; i < 50000; ++i)
|
|
fputc ('x', f);
|
|
fputc ('\n', f);
|
|
if (ferror (f))
|
|
{
|
|
printf ("error: fputc: %m\n");
|
|
abort ();
|
|
}
|
|
if (fclose (f) != 0)
|
|
{
|
|
printf ("error: fclose: %m\n");
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
static int
|
|
do_test (void)
|
|
{
|
|
/* Make sure that we do not exceed the arena limit with the number
|
|
of threads we configured. */
|
|
if (mallopt (M_ARENA_MAX, 400) == 0)
|
|
{
|
|
printf ("error: mallopt (M_ARENA_MAX) failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Leave some room for shutting down all threads gracefully. */
|
|
int timeout = 3;
|
|
if (timeout > TIMEOUT)
|
|
timeout = TIMEOUT - 1;
|
|
|
|
create_file_with_large_line ();
|
|
|
|
pthread_t fork_threads[fork_thread_count];
|
|
create_threads (fork_threads, fork_thread_count, fork_thread_function);
|
|
pthread_t read_threads[read_thread_count];
|
|
create_threads (read_threads, read_thread_count, read_thread_function);
|
|
pthread_t flushall_threads[1];
|
|
create_threads (flushall_threads, 1, flushall_thread_function);
|
|
|
|
struct timespec ts = {timeout, 0};
|
|
if (nanosleep (&ts, NULL))
|
|
{
|
|
printf ("error: error: nanosleep: %m\n");
|
|
abort ();
|
|
}
|
|
|
|
__atomic_store_n (&termination_requested, true, __ATOMIC_RELAXED);
|
|
|
|
join_threads (flushall_threads, 1);
|
|
join_threads (read_threads, read_thread_count);
|
|
join_threads (fork_threads, fork_thread_count);
|
|
|
|
free (file_to_read);
|
|
|
|
return 0;
|
|
}
|