Merge pull request #343 from ogrisel/fix-294-fork-safe-pthread
FIX #294: fork-safe pthread mode
This commit is contained in:
		
						commit
						b20ee6924a
					
				| 
						 | 
					@ -74,6 +74,21 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
				
			||||||
#include <sys/resource.h>
 | 
					#include <sys/resource.h>
 | 
				
			||||||
#endif
 | 
					#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
 | 
					#ifdef SMP_SERVER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#undef MONITOR
 | 
					#undef MONITOR
 | 
				
			||||||
| 
						 | 
					@ -83,8 +98,6 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define ATTRIBUTE_SIZE 128
 | 
					#define ATTRIBUTE_SIZE 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern void openblas_warning(int verbose, const char * msg);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* This is a thread server model implementation.  The threads are   */
 | 
					/* This is a thread server model implementation.  The threads are   */
 | 
				
			||||||
/* spawned at first access to blas library, and still remains until */
 | 
					/* spawned at first access to blas library, and still remains until */
 | 
				
			||||||
/* destruction routine is called.  The number of threads are        */
 | 
					/* 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){
 | 
					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;
 | 
					  BLASLONG i = 0;
 | 
				
			||||||
  blas_queue_t *current = queue;
 | 
					  blas_queue_t *current = queue;
 | 
				
			||||||
#if defined(OS_LINUX) && !defined(NO_AFFINITY) && !defined(PARAMTEST)
 | 
					#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 */
 | 
					/* Execute Threads */
 | 
				
			||||||
int exec_blas(BLASLONG num, blas_queue_t *queue){
 | 
					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
 | 
					#ifdef TIMING_DEBUG
 | 
				
			||||||
  BLASULONG start, stop;
 | 
					  BLASULONG start, stop;
 | 
				
			||||||
| 
						 | 
					@ -923,17 +944,5 @@ int BLASFUNC(blas_thread_shutdown)(void){
 | 
				
			||||||
  return 0;
 | 
					  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
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -315,9 +315,4 @@ int exec_blas(BLASLONG num, blas_queue_t *queue){
 | 
				
			||||||
  return 0;
 | 
					  return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void openblas_fork_handler()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -498,8 +498,3 @@ void openblas_set_num_threads(int num)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	goto_set_num_threads(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;
 | 
					gotoblas_t *gotoblas = NULL;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern void openblas_warning(int verbose, const char * msg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef SMP
 | 
					#ifndef SMP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define blas_cpu_number 1
 | 
					#define blas_cpu_number 1
 | 
				
			||||||
| 
						 | 
					@ -253,6 +255,23 @@ int  goto_get_num_procs  (void) {
 | 
				
			||||||
  return blas_cpu_number;
 | 
					  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){
 | 
					int blas_get_cpu_number(void){
 | 
				
			||||||
  char *p;
 | 
					  char *p;
 | 
				
			||||||
#if defined(OS_LINUX) || defined(OS_WINDOWS) || defined(OS_FREEBSD) || defined(OS_DARWIN)
 | 
					#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;
 | 
					  if (gotoblas_initialized) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef SMP
 | 
				
			||||||
 | 
					  openblas_fork_handler();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef PROFILE
 | 
					#ifdef PROFILE
 | 
				
			||||||
   moncontrol (0);
 | 
					   moncontrol (0);
 | 
				
			||||||
| 
						 | 
					@ -1288,11 +1310,7 @@ void CONSTRUCTOR gotoblas_init(void) {
 | 
				
			||||||
#ifdef SMP
 | 
					#ifdef SMP
 | 
				
			||||||
  if (blas_cpu_number == 0) blas_get_cpu_number();
 | 
					  if (blas_cpu_number == 0) blas_get_cpu_number();
 | 
				
			||||||
#ifdef SMP_SERVER
 | 
					#ifdef SMP_SERVER
 | 
				
			||||||
  if (blas_server_avail == 0) {
 | 
					  if (blas_server_avail == 0) blas_thread_init();
 | 
				
			||||||
    blas_thread_init();
 | 
					 | 
				
			||||||
    //deal with pthread and fork.
 | 
					 | 
				
			||||||
    openblas_fork_handler();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ CUNIT_LIB=$(CUNIT_DIR)/lib/libcunit.a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CFLAGS+=-I$(CUNIT_DIR)/include
 | 
					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
 | 
					all : run_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,4 +63,6 @@ void test_dsdot_n_1(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void test_samax(void);
 | 
					void test_samax(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void test_fork_safety(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,6 +60,14 @@ CU_TestInfo test_level1[]={
 | 
				
			||||||
	{"Testing dsdot with n == 1",test_dsdot_n_1},
 | 
						{"Testing dsdot with n == 1",test_dsdot_n_1},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{"Testing samax", test_samax},
 | 
						{"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,
 | 
						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
 | 
				
			||||||
		Loading…
	
		Reference in New Issue