blob: ea65b75c02add4729abd08150629258f893c4210 [file] [log] [blame]
/*
* 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 <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <string.h>
#include <time.h>
/********************************************************************************************/
/****************************** 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; i<NTOT; i++)
{
ret = pthread_create(&th[i], &ta, threaded_A, &(td->cd[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; i<NTOT; i++)
{
ret = pthread_join(th[i], NULL);
if (ret != 0) { UNRESOLVED(ret, "[Parent] Failed to join a thread"); }
}
/* Destroy everything */
for (i=0; i< NTOT ; i++)
{
/* destroy the condvar */
ret = pthread_cond_destroy(&(td->cd[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;
}