/* * Copyright (c) 2004, Bull S.A.. All rights reserved. * Created by: Sebastien Decugis * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the GNU General Public License along * with this program; if not, write the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston MA 02111-1307, USA. * This file is a stress test for the function pthread_cond_wait. * *It aims to check the following assertion: * When inside the function, the thread releases the mutex * before waiting for the conditionnal variable. * Those two operations are atomic in the mean that * no other thread can gain access to the mutex * then signal (or broadcast) the condition * without the blocked thread behaving as if * this signal (or broadcast) had happened * after it blocked on the conditionnal variable. * The steps are: * -> Create N mutex & N cond vars with different attributes * -> Create N threads A, which * -> lock the mutex * -> create a thread B, which * -> locks the mutex * -> while the boolean is false, * -> broadcasts the condvar * -> sets an alarm timer * -> waits the condition * -> broadcast the condvar * -> unlock the mutex * -> while the boolean is false, * -> set an alarm timer * -> wait the condvar * -> signals the condvar * -> unlock the mutex * -> joins the thread B * -> sets the boolean True when it receives SIGUSR1 * -> report FAILED if the alarm timer expires (meaning one of the signal was lost). * -> joins the N threads A. * * To test for pshared primitive, thread B can be in another process. */ /* We are testing conformance to IEEE Std 1003.1, 2003 Edition */ #define _POSIX_C_SOURCE 200112L /* We need the XSI extention for the mutex attributes and the mkstemp() routine */ #ifndef WITHOUT_XOPEN #define _XOPEN_SOURCE 600 #endif /********************************************************************************************/ /****************************** standard includes *****************************************/ /********************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************/ /****************************** Test framework *****************************************/ /********************************************************************************************/ #include "testfrmw.h" #include "testfrmw.c" /* This header is responsible for defining the following macros: * UNRESOLVED(ret, descr); * where descr is a description of the error and ret is an int (error code for example) * FAILED(descr); * where descr is a short text saying why the test has failed. * PASSED(); * No parameter. * * Both three macros shall terminate the calling process. * The testcase shall not terminate in any other maneer. * * The other file defines the functions * void output_init() * void output(char * string, ...) * * Those may be used to output information. */ /********************************************************************************************/ /********************************** Configuration ******************************************/ /********************************************************************************************/ #ifndef SCALABILITY_FACTOR #define SCALABILITY_FACTOR 1 #endif #ifndef VERBOSE #define VERBOSE 1 #endif /* Number of children for each test scenario */ #define NCHILDREN (5) #define TIMEOUT 120 #ifndef WITHOUT_ALTCLK #define USE_ALTCLK /* make tests with MONOTONIC CLOCK if supported */ #endif /********************************************************************************************/ /*********************************** Test case *****************************************/ /********************************************************************************************/ #ifdef WITHOUT_XOPEN /* We define those to avoid compilation errors, but they won't be used */ #define PTHREAD_MUTEX_DEFAULT 0 #define PTHREAD_MUTEX_NORMAL 0 #define PTHREAD_MUTEX_ERRORCHECK 0 #define PTHREAD_MUTEX_RECURSIVE 0 #endif struct _scenar { int m_type; /* Mutex type to use */ int mc_pshared; /* 0: mutex and cond are process-private (default) ~ !0: Both are process-shared, if supported */ int c_clock; /* 0: cond uses the default clock. ~ !0: Cond uses monotonic clock, if supported. */ int fork; /* 0: Test between threads. ~ !0: Test across processes, if supported (mmap) */ char * descr; /* Case description */ } scenarii[] = { {PTHREAD_MUTEX_DEFAULT, 0, 0, 0, "Default mutex"} ,{PTHREAD_MUTEX_NORMAL, 0, 0, 0, "Normal mutex"} ,{PTHREAD_MUTEX_ERRORCHECK, 0, 0, 0, "Errorcheck mutex"} ,{PTHREAD_MUTEX_RECURSIVE, 0, 0, 0, "Recursive mutex"} ,{PTHREAD_MUTEX_DEFAULT, 1, 0, 0, "PShared default mutex"} ,{PTHREAD_MUTEX_NORMAL, 1, 0, 0, "Pshared normal mutex"} ,{PTHREAD_MUTEX_ERRORCHECK, 1, 0, 0, "Pshared errorcheck mutex"} ,{PTHREAD_MUTEX_RECURSIVE, 1, 0, 0, "Pshared recursive mutex"} ,{PTHREAD_MUTEX_DEFAULT, 1, 0, 1, "Pshared default mutex across processes"} ,{PTHREAD_MUTEX_NORMAL, 1, 0, 1, "Pshared normal mutex across processes"} ,{PTHREAD_MUTEX_ERRORCHECK, 1, 0, 1, "Pshared errorcheck mutex across processes"} ,{PTHREAD_MUTEX_RECURSIVE, 1, 0, 1, "Pshared recursive mutex across processes"} #ifdef USE_ALTCLK ,{PTHREAD_MUTEX_DEFAULT, 1, 1, 1, "Pshared default mutex and alt clock condvar across processes"} ,{PTHREAD_MUTEX_NORMAL, 1, 1, 1, "Pshared normal mutex and alt clock condvar across processes"} ,{PTHREAD_MUTEX_ERRORCHECK, 1, 1, 1, "Pshared errorcheck mutex and alt clock condvar across processes"} ,{PTHREAD_MUTEX_RECURSIVE, 1, 1, 1, "Pshared recursive mutex and alt clock condvar across processes"} ,{PTHREAD_MUTEX_DEFAULT, 0, 1, 0, "Default mutex and alt clock condvar"} ,{PTHREAD_MUTEX_NORMAL, 0, 1, 0, "Normal mutex and alt clock condvar"} ,{PTHREAD_MUTEX_ERRORCHECK, 0, 1, 0, "Errorcheck mutex and alt clock condvar"} ,{PTHREAD_MUTEX_RECURSIVE, 0, 1, 0, "Recursive mutex and alt clock condvar"} ,{PTHREAD_MUTEX_DEFAULT, 1, 1, 0, "PShared default mutex and alt clock condvar"} ,{PTHREAD_MUTEX_NORMAL, 1, 1, 0, "Pshared normal mutex and alt clock condvar"} ,{PTHREAD_MUTEX_ERRORCHECK, 1, 1, 0, "Pshared errorcheck mutex and alt clock condvar"} ,{PTHREAD_MUTEX_RECURSIVE, 1, 1, 0, "Pshared recursive mutex and alt clock condvar"} #endif }; #define NSCENAR (sizeof(scenarii)/sizeof(scenarii[0])) #define NTOT (NSCENAR * SCALABILITY_FACTOR * NCHILDREN) struct childdata { pthread_mutex_t mtx; pthread_cond_t cnd; int fork; int * pBool; }; typedef struct { struct childdata cd[NTOT]; int boolean; } testdata_t; pthread_attr_t ta; /*** * The grand child function (either sub-thread or sub-process) */ void * threaded_B (void * arg) { int ret; struct childdata * cd = (struct childdata *)arg; ret = pthread_mutex_lock(&(cd->mtx)); if (ret != 0) { UNRESOLVED(ret, "[gchild] Unable to lock mutex"); } while (*(cd->pBool) == 0) { ret = pthread_cond_broadcast(&(cd->cnd)); if (ret != 0) { UNRESOLVED(ret, "[gchild] Broadcast failed"); } alarm(TIMEOUT); // even if we are a sub-process, the main process will timeout too ret = pthread_cond_wait(&(cd->cnd), &(cd->mtx)); if (ret != 0) { UNRESOLVED(ret, "[gchild] Failed to wait the cond"); } } /* We shall broadcast again to be sure the parent is not hung */ ret = pthread_cond_broadcast(&(cd->cnd)); if (ret != 0) { UNRESOLVED(ret, "[gchild] Broadcast failed"); } ret = pthread_mutex_unlock(&(cd->mtx)); if (ret != 0) { UNRESOLVED(ret, "[gchild] Failed to finally release the mutex"); } return NULL; } /*** * The child function (always in the main process) */ void * threaded_A (void * arg) { struct childdata * cd = (struct childdata *)arg; int ret, status; pid_t child_p=0, wrc; pthread_t child_t; ret = pthread_mutex_lock(&(cd->mtx)); if (ret != 0) { UNRESOLVED(ret, "[child] Unable to lock mutex"); } /* Create the grand child */ if (cd->fork == 0) { ret = pthread_create(&child_t, &ta, threaded_B, arg); if (ret != 0) { UNRESOLVED(ret, "[child] Failed to create a grand child thread"); } } else { child_p= fork(); if (child_p == -1) { UNRESOLVED(ret, "[child] Failed to create a grand child proces"); } if (child_p == 0) /* grand child */ { threaded_B(arg); exit(0); } } while (*(cd->pBool) == 0) { alarm(TIMEOUT); ret = pthread_cond_wait(&(cd->cnd), &(cd->mtx)); if (ret != 0) { UNRESOLVED(ret, "[child] Failed to wait the cond"); } ret = pthread_cond_signal(&(cd->cnd)); if (ret != 0) { UNRESOLVED(ret, "[child] Signal failed"); } } ret = pthread_mutex_unlock(&(cd->mtx)); if (ret != 0) { UNRESOLVED(ret, "[gchild] Failed to finally release the mutex"); } /* Wait for the grand child termination */ if (cd->fork == 0) { ret = pthread_join(child_t, NULL); if (ret != 0) { UNRESOLVED(ret, "[child] Failed to join a grand child thread"); } } else { wrc = waitpid(child_p, &status, 0); if (wrc != child_p) { output("Expected pid: %i. Got %i\n", (int)child_p, (int)wrc); UNRESOLVED(errno, "Waitpid failed"); } if (WIFSIGNALED(status)) { output("Child process killed with signal %d\n",WTERMSIG(status)); UNRESOLVED( 0 , "Child process was killed"); } if (WIFEXITED(status)) { ret = WEXITSTATUS(status); } else { UNRESOLVED( 0, "Child process was neither killed nor exited"); } } /* the end */ return NULL; } int * pBoolean = NULL; /*** * Signal handler */ void sighdl(int sig) { if (sig == SIGUSR1) { #if VERBOSE > 1 output("Received the USR1 signal; stopping everything\n"); #endif *pBoolean = 1; } if (sig == SIGALRM) { FAILED("A wait operation timed out. A condition signaling was lost."); } } int main(int argc, char * argv[]) { int ret, i, j; struct sigaction sa; pthread_mutexattr_t ma; pthread_condattr_t ca; clockid_t cid = CLOCK_REALTIME; testdata_t * td; testdata_t alternativ; int do_fork; long pshared, monotonic, cs, mf; pthread_t th[NTOT]; output_init(); pshared = sysconf(_SC_THREAD_PROCESS_SHARED); cs = sysconf(_SC_CLOCK_SELECTION); monotonic = sysconf(_SC_MONOTONIC_CLOCK); mf =sysconf(_SC_MAPPED_FILES); #if VERBOSE > 0 output("Test starting\n"); output("System abilities:\n"); output(" TPS : %li\n", pshared); output(" CS : %li\n", cs); output(" MON : %li\n", monotonic); output(" MF : %li\n", mf); if ((mf < 0) || (pshared < 0)) output("Process-shared attributes won't be tested\n"); if ((cs < 0) || (monotonic < 0)) output("Alternative clock won't be tested\n"); #endif /* We are not interested in testing the clock if we have no other clock available.. */ if (monotonic < 0) cs = -1; #ifndef USE_ALTCLK if (cs > 0) output("Implementation supports the MONOTONIC CLOCK but option is disabled in test.\n"); #endif /********** * Allocate space for the testdata structure */ if (mf < 0) { /* Cannot mmap a file, we use an alternative method */ td = &alternativ; pshared = -1; /* We won't do this testing anyway */ #if VERBOSE > 0 output("Testdata allocated in the process memory.\n"); #endif } else { /* We will place the test data in a mmaped file */ char filename[] = "/tmp/cond_timedwait_st1-XXXXXX"; size_t sz, ps; void * mmaped; int fd; char * tmp; /* We now create the temp files */ fd = mkstemp(filename); if (fd == -1) { UNRESOLVED(errno, "Temporary file could not be created"); } /* and make sure the file will be deleted when closed */ unlink(filename); #if VERBOSE > 1 output("Temp file created (%s).\n", filename); #endif ps = (size_t)sysconf(_SC_PAGESIZE); sz= ((sizeof(testdata_t) / ps) + 1) * ps; /* # pages needed to store the testdata */ tmp = calloc( 1 , sz); if (tmp == NULL) { UNRESOLVED(errno, "Memory allocation failed"); } /* Write the data to the file. */ if (write (fd, tmp, sz) != (ssize_t) sz) { UNRESOLVED(sz, "Writting to the file failed"); } free(tmp); /* Now we can map the file in memory */ mmaped = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mmaped == MAP_FAILED) { UNRESOLVED(errno, "mmap failed"); } td = (testdata_t *) mmaped; /* Our datatest structure is now in shared memory */ #if VERBOSE > 1 output("Testdata allocated in shared memory (%ib).\n", sizeof(testdata_t)); #endif } /* Init the signal handler variable */ pBoolean = &(td->boolean); /* Init the structure */ for ( i=0; i< NSCENAR ; i++) { #if VERBOSE > 1 output("[parent] Preparing attributes for: %s\n", scenarii[i].descr); #ifdef WITHOUT_XOPEN output("[parent] Mutex attributes DISABLED -> not used\n"); #endif #endif /* set / reset everything */ do_fork=0; ret = pthread_mutexattr_init(&ma); if (ret != 0) { UNRESOLVED(ret, "[parent] Unable to initialize the mutex attribute object"); } ret = pthread_condattr_init(&ca); if (ret != 0) { UNRESOLVED(ret, "[parent] Unable to initialize the cond attribute object"); } #ifndef WITHOUT_XOPEN /* Set the mutex type */ ret = pthread_mutexattr_settype(&ma, scenarii[i].m_type); if (ret != 0) { UNRESOLVED(ret, "[parent] Unable to set mutex type"); } #if VERBOSE > 1 output("[parent] Mutex type : %i\n", scenarii[i].m_type); #endif #endif /* Set the pshared attributes, if supported */ if ((pshared > 0) && (scenarii[i].mc_pshared != 0)) { ret = pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED); if (ret != 0) { UNRESOLVED(ret, "[parent] Unable to set the mutex process-shared"); } ret = pthread_condattr_setpshared(&ca, PTHREAD_PROCESS_SHARED); if (ret != 0) { UNRESOLVED(ret, "[parent] Unable to set the cond var process-shared"); } #if VERBOSE > 1 output("[parent] Mutex & cond are process-shared\n"); #endif } #if VERBOSE > 1 else { output("[parent] Mutex & cond are process-private\n"); } #endif /* Set the alternative clock, if supported */ #ifdef USE_ALTCLK if ((cs > 0) && (scenarii[i].c_clock != 0)) { ret = pthread_condattr_setclock(&ca, CLOCK_MONOTONIC); if (ret != 0) { UNRESOLVED(ret, "[parent] Unable to set the monotonic clock for the cond"); } #if VERBOSE > 1 output("[parent] Cond uses the Monotonic clock\n"); #endif } #if VERBOSE > 1 else { output("[parent] Cond uses the default clock\n"); } #endif ret = pthread_condattr_getclock(&ca, &cid); if (ret != 0) { UNRESOLVED(ret, "Unable to get clock from cond attr"); } #endif /* Tell whether the test will be across processes */ if ((pshared > 0) && (scenarii[i].fork != 0)) { do_fork = 1; #if VERBOSE > 1 output("[parent] Child will be a new process\n"); #endif } #if VERBOSE > 1 else { output("[parent] Child will be a new thread\n"); } #endif /* Initialize all the mutex and condvars which uses those attributes */ for (j=0; j < SCALABILITY_FACTOR * NCHILDREN; j++) { #define CD (td->cd[i+(j*NSCENAR)]) CD.pBool = &(td->boolean); CD.fork = do_fork; CD.cid = cid; /* initialize the condvar */ ret = pthread_cond_init(&(CD.cnd), &ca); if (ret != 0) { UNRESOLVED(ret, "[parent] Cond init failed"); } /* initialize the mutex */ ret = pthread_mutex_init(&(CD.mtx), &ma); if (ret != 0) { UNRESOLVED(ret, "[parent] Mutex init failed"); } #undef CD } ret = pthread_condattr_destroy(&ca); if (ret != 0) { UNRESOLVED(ret, "Failed to destroy the cond var attribute object"); } ret = pthread_mutexattr_destroy(&ma); if (ret != 0) { UNRESOLVED(ret, "Failed to destroy the mutex attribute object"); } } #if VERBOSE > 1 output("[parent] All condvars & mutex are ready\n"); #endif ret = pthread_attr_init(&ta); if (ret != 0) { UNRESOLVED(ret, "[parent] Failed to initialize a thread attribute object"); } ret = pthread_attr_setstacksize(&ta, sysconf(_SC_THREAD_STACK_MIN)); if (ret != 0) { UNRESOLVED(ret, "[parent] Failed to set thread stack size"); } sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = sighdl; if ((ret = sigaction (SIGUSR1, &sa, NULL))) { UNRESOLVED(ret, "Unable to register signal handler"); } if ((ret = sigaction (SIGALRM, &sa, NULL))) { UNRESOLVED(ret, "Unable to register signal handler"); } #if VERBOSE > 1 output("[parent] Signal handler registered\n"); #endif for (i=0; icd[i])); /* In case of failure we can exit; the child processes will die after a while */ if (ret != 0) { UNRESOLVED(ret, "[Parent] Failed to create a thread"); } #if VERBOSE > 1 if ((i % 10) == 0) output("[parent] %i threads created...\n", i+1); #endif } #if VERBOSE > 1 output("[parent] All %i threads are running...\n", NTOT); #endif for (i=0; icd[i].cnd)); if (ret != 0) { UNRESOLVED(ret, "[parent] Cond destroy failed"); } /* destroy the mutex */ ret = pthread_mutex_init(&(td->cd[i].mtx), &ma); if (ret != 0) { UNRESOLVED(ret, "[parent] Mutex destroy failed"); } } #if VERBOSE > 0 output("Test passed\n"); #endif PASSED; }