FIX #294: fork-safe pthread modetags/v0.2.9.rc2^2
@@ -74,6 +74,21 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
#include <sys/resource.h> | |||
#endif | |||
#ifndef likely | |||
#ifdef __GNUC__ | |||
#define likely(x) __builtin_expect(!!(x), 1) | |||
#else | |||
#define likely(x) (x) | |||
#endif | |||
#endif | |||
#ifndef unlikely | |||
#ifdef __GNUC__ | |||
#define unlikely(x) __builtin_expect(!!(x), 0) | |||
#else | |||
#define unlikely(x) (x) | |||
#endif | |||
#endif | |||
#ifdef SMP_SERVER | |||
#undef MONITOR | |||
@@ -83,8 +98,6 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
#define ATTRIBUTE_SIZE 128 | |||
extern void openblas_warning(int verbose, const char * msg); | |||
/* This is a thread server model implementation. The threads are */ | |||
/* spawned at first access to blas library, and still remains until */ | |||
/* destruction routine is called. The number of threads are */ | |||
@@ -586,6 +599,10 @@ static BLASULONG exec_queue_lock = 0; | |||
int exec_blas_async(BLASLONG pos, blas_queue_t *queue){ | |||
#ifdef SMP_SERVER | |||
// Handle lazy re-init of the thread-pool after a POSIX fork | |||
if (unlikely(blas_server_avail == 0)) blas_thread_init(); | |||
#endif | |||
BLASLONG i = 0; | |||
blas_queue_t *current = queue; | |||
#if defined(OS_LINUX) && !defined(NO_AFFINITY) && !defined(PARAMTEST) | |||
@@ -710,7 +727,11 @@ int exec_blas_async_wait(BLASLONG num, blas_queue_t *queue){ | |||
/* Execute Threads */ | |||
int exec_blas(BLASLONG num, blas_queue_t *queue){ | |||
int (*routine)(blas_arg_t *, void *, void *, double *, double *, BLASLONG); | |||
#ifdef SMP_SERVER | |||
// Handle lazy re-init of the thread-pool after a POSIX fork | |||
if (unlikely(blas_server_avail == 0)) blas_thread_init(); | |||
#endif | |||
int (*routine)(blas_arg_t *, void *, void *, double *, double *, BLASLONG); | |||
#ifdef TIMING_DEBUG | |||
BLASULONG start, stop; | |||
@@ -923,17 +944,5 @@ int BLASFUNC(blas_thread_shutdown)(void){ | |||
return 0; | |||
} | |||
/* | |||
https://github.com/xianyi/OpenBLAS/issues/294 | |||
Use pthread_atfork to close blas_thread_server before fork. | |||
Then, re-init blas_thread_server after fork at child and parent. | |||
*/ | |||
void openblas_fork_handler() | |||
{ | |||
int err; | |||
err = pthread_atfork (BLASFUNC(blas_thread_shutdown), blas_thread_init, blas_thread_init); | |||
if(err != 0) | |||
openblas_warning(0, "OpenBLAS cannot install fork handler. You may meet hang after fork.\n"); | |||
} | |||
#endif | |||
@@ -315,9 +315,4 @@ int exec_blas(BLASLONG num, blas_queue_t *queue){ | |||
return 0; | |||
} | |||
void openblas_fork_handler() | |||
{ | |||
} | |||
#endif |
@@ -498,8 +498,3 @@ void openblas_set_num_threads(int num) | |||
{ | |||
goto_set_num_threads(num); | |||
} | |||
void openblas_fork_handler() | |||
{ | |||
} |
@@ -143,6 +143,8 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
gotoblas_t *gotoblas = NULL; | |||
#endif | |||
extern void openblas_warning(int verbose, const char * msg); | |||
#ifndef SMP | |||
#define blas_cpu_number 1 | |||
@@ -253,6 +255,23 @@ int goto_get_num_procs (void) { | |||
return blas_cpu_number; | |||
} | |||
void openblas_fork_handler() | |||
{ | |||
// This handler shuts down the OpenBLAS-managed PTHREAD pool when OpenBLAS is | |||
// built with "make USE_OPENMP=0". | |||
// Hanging can still happen when OpenBLAS is built against the libgomp | |||
// implementation of OpenMP. The problem is tracked at: | |||
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=60035 | |||
// In the mean time build with USE_OPENMP=0 or link against another | |||
// implementation of OpenMP. | |||
#ifndef OS_WINDOWS | |||
int err; | |||
err = pthread_atfork (BLASFUNC(blas_thread_shutdown), NULL, NULL); | |||
if(err != 0) | |||
openblas_warning(0, "OpenBLAS Warning ... cannot install fork handler. You may meet hang after fork.\n"); | |||
#endif | |||
} | |||
int blas_get_cpu_number(void){ | |||
char *p; | |||
#if defined(OS_LINUX) || defined(OS_WINDOWS) || defined(OS_FREEBSD) || defined(OS_DARWIN) | |||
@@ -1268,6 +1287,9 @@ void CONSTRUCTOR gotoblas_init(void) { | |||
if (gotoblas_initialized) return; | |||
#ifdef SMP | |||
openblas_fork_handler(); | |||
#endif | |||
#ifdef PROFILE | |||
moncontrol (0); | |||
@@ -1288,11 +1310,7 @@ void CONSTRUCTOR gotoblas_init(void) { | |||
#ifdef SMP | |||
if (blas_cpu_number == 0) blas_get_cpu_number(); | |||
#ifdef SMP_SERVER | |||
if (blas_server_avail == 0) { | |||
blas_thread_init(); | |||
//deal with pthread and fork. | |||
openblas_fork_handler(); | |||
} | |||
if (blas_server_avail == 0) blas_thread_init(); | |||
#endif | |||
#endif | |||
@@ -11,7 +11,7 @@ CUNIT_LIB=$(CUNIT_DIR)/lib/libcunit.a | |||
CFLAGS+=-I$(CUNIT_DIR)/include | |||
OBJS=main.o test_rot.o test_swap.o test_axpy.o test_dotu.o test_rotmg.o test_dsdot.o test_amax.o | |||
OBJS=main.o test_rot.o test_swap.o test_axpy.o test_dotu.o test_rotmg.o test_dsdot.o test_amax.o test_fork.o | |||
all : run_test | |||
@@ -63,4 +63,6 @@ void test_dsdot_n_1(void); | |||
void test_samax(void); | |||
void test_fork_safety(void); | |||
#endif |
@@ -60,6 +60,14 @@ CU_TestInfo test_level1[]={ | |||
{"Testing dsdot with n == 1",test_dsdot_n_1}, | |||
{"Testing samax", test_samax}, | |||
#if !defined(USE_OPENMP) && !defined(OS_WINDOWS) | |||
// The GNU OpenMP implementation libgomp is not fork-safe (as of 4.8.2): | |||
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=60035 | |||
// Hence skip this test when OpenBLAS is built with OpenMP. | |||
{"Testing fork safety", test_fork_safety}, | |||
#endif | |||
CU_TEST_INFO_NULL, | |||
}; | |||
@@ -0,0 +1,123 @@ | |||
/***************************************************************************** | |||
Copyright (c) 2014, Lab of Parallel Software and Computational Science,ICSAS | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are | |||
met: | |||
1. Redistributions of source code must retain the above copyright | |||
notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright | |||
notice, this list of conditions and the following disclaimer in | |||
the documentation and/or other materials provided with the | |||
distribution. | |||
3. Neither the name of the ISCAS nor the names of its contributors may | |||
be used to endorse or promote products derived from this software | |||
without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | |||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
**********************************************************************************/ | |||
#ifndef OS_WINDOWS | |||
#include "common_utest.h" | |||
#include <sys/wait.h> | |||
#include <cblas.h> | |||
void* xmalloc(size_t n) | |||
{ | |||
void* tmp; | |||
tmp = malloc(n); | |||
if (tmp == NULL) { | |||
fprintf(stderr, "You are about to die\n"); | |||
exit(1); | |||
} else { | |||
return tmp; | |||
} | |||
} | |||
void check_dgemm(double *a, double *b, double *result, double *expected, int n) | |||
{ | |||
int i; | |||
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, n, n, n, | |||
1.0, a, n, b, n, 0.0, result, n); | |||
for(i = 0; i < n * n; ++i) { | |||
CU_ASSERT_DOUBLE_EQUAL(expected[i], result[i], CHECK_EPS); | |||
} | |||
} | |||
void test_fork_safety(void) | |||
{ | |||
int n = 1000; | |||
int i; | |||
double *a, *b, *c, *d; | |||
size_t n_bytes; | |||
pid_t fork_pid; | |||
pid_t fork_pid_nested; | |||
n_bytes = sizeof(*a) * n * n; | |||
a = xmalloc(n_bytes); | |||
b = xmalloc(n_bytes); | |||
c = xmalloc(n_bytes); | |||
d = xmalloc(n_bytes); | |||
// Put ones in a and b | |||
for(i = 0; i < n * n; ++i) { | |||
a[i] = 1; | |||
b[i] = 1; | |||
} | |||
// Compute a DGEMM product in the parent process prior to forking to | |||
// ensure that the OpenBLAS thread pool is initialized. | |||
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, n, n, n, | |||
1.0, a, n, b, n, 0.0, c, n); | |||
fork_pid = fork(); | |||
if (fork_pid == -1) { | |||
CU_FAIL("Failed to fork process."); | |||
} else if (fork_pid == 0) { | |||
// Compute a DGEMM product in the child process to check that the | |||
// thread pool as been properly been reinitialized after the fork. | |||
check_dgemm(a, b, d, c, n); | |||
// Nested fork to check that the pthread_atfork protection can work | |||
// recursively | |||
fork_pid_nested = fork(); | |||
if (fork_pid_nested == -1) { | |||
CU_FAIL("Failed to fork process."); | |||
exit(1); | |||
} else if (fork_pid_nested == 0) { | |||
check_dgemm(a, b, d, c, n); | |||
exit(0); | |||
} else { | |||
check_dgemm(a, b, d, c, n); | |||
int child_status = 0; | |||
pid_t wait_pid = wait(&child_status); | |||
CU_ASSERT(wait_pid == fork_pid_nested); | |||
CU_ASSERT(WEXITSTATUS (child_status) == 0); | |||
exit(0); | |||
} | |||
} else { | |||
check_dgemm(a, b, d, c, n); | |||
// Wait for the child to finish and check the exit code. | |||
int child_status = 0; | |||
pid_t wait_pid = wait(&child_status); | |||
CU_ASSERT(wait_pid == fork_pid); | |||
CU_ASSERT(WEXITSTATUS (child_status) == 0); | |||
} | |||
} | |||
#endif |