commit 82aed10c9858617a3964cbb3d34ebdd35a1804b4 Author: Nathaniel J. Smith Date: Sun Oct 19 20:23:52 2014 +0100 patch 1 diff --git a/libgomp/libgomp.h b/libgomp/libgomp.h index a1482cc..ef3a7f4 100644 --- a/libgomp/libgomp.h +++ b/libgomp/libgomp.h @@ -441,6 +441,9 @@ struct gomp_thread /* User pthread thread pool */ struct gomp_thread_pool *thread_pool; + + /* This is to enable best-effort cleanup after fork. */ + int we_are_forked; }; diff --git a/libgomp/team.c b/libgomp/team.c index e6a6d8f..19b3cc8 100644 --- a/libgomp/team.c +++ b/libgomp/team.c @@ -86,6 +86,7 @@ gomp_thread_start (void *xdata) thr->ts = data->ts; thr->task = data->task; thr->place = data->place; + thr->we_are_forked = 0; thr->ts.team->ordered_release[thr->ts.team_id] = &thr->release; @@ -266,6 +267,62 @@ gomp_free_thread (void *arg __attribute__((unused))) } } +/* According to POSIX, if a process which uses threads calls fork(), then + there are very few things that the resulting child process can do safely -- + mostly just exec(). + + However, in practice, (almost?) all POSIX implementations do allow + arbitrary code to run inside the child, *if* the parent process's threads + are in a well-defined state when the fork occurs. And this circumstance can + easily arise in OMP-using programs, e.g. when a library function like DGEMM + uses OMP internally, and some other unrelated part of the program calls + fork() at some other time, when no OMP sections are running. + + Therefore, we make a best effort attempt to handle the case: + + OMP section (in parent) -> OMP quiesce -> fork -> OMP section (in child) + + "Best-effort" here means that: + - Your system may or may not be able to handle this kind of code at all; + our goal is just to make sure that if it fails it's not gomp's fault. + - All threadprivate variables will be reset in the child. Fortunately this + is entirely compliant with the spec, according to the rule of nasal + demons. + - We must have minimal speed impact, and no correctness impact, on + compliant programs. + + Our approach is to use pthread_atfork to make a note whenever a fork() has + occurred. *All* we do is make a note. We can't actually shut down our + thread pool in the parent, because this would violate the OMP spec (it + would cause threadprivate variables to disappear whenever the process did + fork+exec). And we can't immediately shut it down in the child, because + that requires calling non-async-signal-safe functions, and thus would + violate POSIX in the case where the host program is just trying to + fork+exec. In fact, we can't even access our we_are_forked flag from the + child, because it's stored in TLS and accessing TLS on some platforms + requires a non-async-signal-safe call to pthread_getspecific(). So what we + do is set the flag in the parent just before calling fork, let the child + process inherit the flag, and then unset the flag in the parent. This is + safe, because in the parent the flag is only visible to the thread calling + fork(), and by the time fork() has returned the flag is set back to its + correct value. +*/ +static void +gomp_before_fork_callback (void) +{ + struct gomp_thread *thr = gomp_thread (); + /* Use increment/decrement to handle the case where the child of our child + enters an OMP section. */ + thr->we_are_forked++; +} + +static void +gomp_after_fork_parent_callback (void) +{ + struct gomp_thread *thr = gomp_thread (); + thr->we_are_forked--; +} + /* Launch a team. */ void @@ -287,6 +344,15 @@ gomp_team_start (void (*fn) (void *), void *data, unsigned nthreads, struct gomp_thread **affinity_thr = NULL; thr = gomp_thread (); + if (__builtin_expect (thr->we_are_forked, 0)) + { + /* There was some parent process who was using OMP, and then called + fork(). We are the main thread of the resulting child process. Our + thread structure contains stale data referring to the parent thread + who called fork(). Reset it to reflect our new main-thread + status. (This leaks, but that's better than deadlocking.) */ + memset (thr, 0, sizeof(struct gomp_thread)); + } nested = thr->ts.team != NULL; if (__builtin_expect (thr->thread_pool == NULL, 0)) { @@ -925,6 +991,10 @@ initialize_team (void) if (pthread_key_create (&gomp_thread_destructor, gomp_free_thread) != 0) gomp_fatal ("could not create thread pool destructor."); + + pthread_atfork (gomp_before_fork_callback, + gomp_after_fork_parent_callback, + NULL); } static void __attribute__((destructor)) diff --git a/libgomp/testsuite/libgomp.c/fork-1.c b/libgomp/testsuite/libgomp.c/fork-1.c new file mode 100644 index 0000000..97bb391 --- /dev/null +++ b/libgomp/testsuite/libgomp.c/fork-1.c @@ -0,0 +1,80 @@ +/* This test requires fork(). It ought to work everywhere that fork() does, + though. Unfortunately that is not so easy to write down... */ +/* { dg-do run + {target *-*-linux* *-*-gnu* *-*-freebsd* *-*-darwin* *-*-solaris* } } */ +/* { dg-timeout 10 } */ + +#include +#include +#include +#include +#include + +static int saw[4]; + +static void +check_parallel (int exit_on_failure) +{ + memset (saw, 0, sizeof (saw)); + #pragma omp parallel num_threads (2) + { + int iam = omp_get_thread_num (); + saw[iam] = 1; + } + + // Encode failure in status code to report to parent process + if (exit_on_failure) + { + if (saw[0] != 1) + _exit(1); + else if (saw[1] != 1) + _exit(2); + else if (saw[2] != 0) + _exit(3); + else if (saw[3] != 0) + _exit(4); + else + _exit(0); + } + // Use regular assertions + else + { + assert (saw[0] == 1); + assert (saw[1] == 1); + assert (saw[2] == 0); + assert (saw[3] == 0); + } +} + +int +main () +{ + // Initialize the OMP thread pool in the parent process + check_parallel (0); + pid_t fork_pid = fork(); + if (fork_pid == -1) + return 1; + else if (fork_pid == 0) + { + // Call OMP again in the child process and encode failures in exit + // code. + check_parallel (1); + } + else + { + // Check that OMP runtime is still functional in parent process after + // the fork. + check_parallel (0); + + // Wait for the child to finish and check the exit code. + int child_status = 0; + pid_t wait_pid = wait(&child_status); + assert (wait_pid == fork_pid); + assert (WEXITSTATUS (child_status) == 0); + + // Check that the termination of the child process did not impact + // OMP in parent process. + check_parallel (0); + } + return 0; +}