blob: 73c2c871101517c05cf274b8e68375908991a554 [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 pthread_mutex_lock function.
* The steps are:
* -> For each king of mutex, we create 10*F threads (F is a scalability factor)
* -> we call those threads 1 to 10.
* -> thread 1 sends signal USR2 to the other 9 threads (which have a handler for it)
* -> thread 2 to 6 are loops
* {
* mutex_lock
* if (ctrl) exit
* ctrl = 1
* yield
* ctrl= 0
* mutex unlock
* }
* -> thread 7 & 8 have a timedlock instead of lock
* -> thread 9 & 10 have a trylock instead of lock
*
* -> the whole process stop when receiving signal SIGUSR1.
* This goal is achieved with a "do_it" variable.
*
* NOTE: With gcc/linux, the flag "-lrt" must be specified at link time.
*/
/* We are testing conformance to IEEE Std 1003.1, 2003 Edition */
#define _POSIX_C_SOURCE 200112L
/* We enable the following line to have mutex attributes defined */
#ifndef WITHOUT_XOPEN
#define _XOPEN_SOURCE 600
#endif
/********************************************************************************************/
/****************************** standard includes *****************************************/
/********************************************************************************************/
#include <pthread.h>
#include <errno.h>
#include <semaphore.h>
#include <signal.h>
#include <unistd.h>
#if _POSIX_TIMEOUTS < 0
#error "This sample needs POSIX TIMEOUTS option support"
#endif
#if _POSIX_TIMEOUTS == 0
#warning "This sample needs POSIX TIMEOUTS option support"
#endif
#if _POSIX_TIMERS < 0
#error "This sample needs POSIX TIMERS option support"
#endif
#if _POSIX_TIMERS == 0
#warning "This sample needs POSIX TIMERS option support"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h> /* required for the pthread_mutex_timedlock() function */
/********************************************************************************************/
/****************************** 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 2
#endif
#define N 2 /* N * 10 * 6 * SCALABILITY_FACTOR threads will be created */
/********************************************************************************************/
/*********************************** Test case *****************************************/
/********************************************************************************************/
char do_it=1;
#ifndef WITHOUT_XOPEN
int types[]={PTHREAD_MUTEX_NORMAL,
PTHREAD_MUTEX_ERRORCHECK,
PTHREAD_MUTEX_RECURSIVE,
PTHREAD_MUTEX_DEFAULT};
#endif
/* The following type represents the data
* for one group of ten threads */
typedef struct
{
pthread_t threads[10]; /* The 10 threads */
pthread_mutex_t mtx; /* The mutex those threads work on */
char ctrl; /* The value used to check the behavior */
char sigok; /* Used to tell the threads they can return */
sem_t semsig; /* Semaphore for synchronizing the signal handler */
int id; /* An identifier for the threads group */
int tcnt; /* we need to make sure the threads are started before killing 'em */
pthread_mutex_t tmtx;
unsigned long long sigcnt, opcnt; /* We count every iteration */
} cell_t;
pthread_key_t _c; /* this key will always contain a pointer to the thread's cell */
/***** The next function is in charge of sending signal USR2 to
* all the other threads in its cell, until the end of the test. */
void * sigthr(void * arg)
{
int ret;
int i=0;
cell_t * c = (cell_t *)arg;
do
{
sched_yield();
ret = pthread_mutex_lock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to lock the mutex"); }
i = c->tcnt;
ret = pthread_mutex_unlock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to unlock the mutex"); }
} while (i<9);
/* Until we must stop, do */
while (do_it)
{
/* Wait for the semaphore */
ret = sem_wait(&(c->semsig));
if (ret != 0)
{ UNRESOLVED(errno, "Sem wait failed in signal thread"); }
/* Kill the next thread */
i %= 9;
ret = pthread_kill(c->threads[++i], SIGUSR2);
if (ret != 0)
{ UNRESOLVED(ret, "Thread kill failed in signal thread"); }
/* Increment the signal counter */
c->sigcnt++;
}
/* Tell the other threads they can now stop */
do { c->sigok = 1; }
while (c->sigok == 0);
return NULL;
}
/***** The next function is the signal handler
* for all the others threads in the cell */
void sighdl(int sig)
{
int ret;
cell_t * c = (cell_t *) pthread_getspecific(_c);
ret = sem_post(&(c->semsig));
if (ret != 0)
{ UNRESOLVED(errno, "Unable to post semaphore in signal handler"); }
}
/***** The next function can return only when the sigthr has terminated.
* This avoids the signal thread try to kill a terminated thread. */
void waitsigend(cell_t * c)
{
while (c->sigok == 0)
{ sched_yield(); }
}
/***** The next function aims to control that no other thread
* owns the mutex at the same time */
void control(cell_t * c, char * loc)
{
*loc++; /* change the local control value */
if (c->ctrl != 0)
{ FAILED("Got a non-zero value - two threads owns the mutex"); }
c->ctrl = *loc;
sched_yield();
if (c->ctrl != *loc)
{ FAILED("Got a different value - another thread touched protected data"); }
c->ctrl = 0;
/* Avoid some values for the next control */
if (*loc == 120)
*loc = -120;
if (*loc == -1)
*loc = 1;
}
/***** The next 3 functions are the worker threads
*/
void * lockthr(void * arg)
{
int ret;
char loc; /* Local value for control */
cell_t * c = (cell_t *)arg;
/* Set the thread local data key value (used in the signal handler) */
ret = pthread_setspecific(_c, arg);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to assign the thread-local-data key"); }
/* Signal we're started */
ret = pthread_mutex_lock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to lock the mutex"); }
c->tcnt += 1;
ret = pthread_mutex_unlock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to unlock the mutex"); }
do
{
/* Lock, control, then unlock */
ret = pthread_mutex_lock(&(c->mtx));
if (ret != 0)
{ UNRESOLVED(ret, "Mutex lock failed in worker thread"); }
control(c, &loc);
ret = pthread_mutex_unlock(&(c->mtx));
if (ret != 0)
{ UNRESOLVED(ret, "Mutex unlock failed in worker thread"); }
/* Increment the operation counter */
c->opcnt++;
}
while (do_it);
/* Wait for the signal thread to terminate before we can exit */
waitsigend(c);
return NULL;
}
void * timedlockthr(void * arg)
{
int ret;
char loc; /* Local value for control */
struct timespec ts;
cell_t * c = (cell_t *)arg;
/* Set the thread local data key value (used in the signal handler) */
ret = pthread_setspecific(_c, arg);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to assign the thread-local-data key"); }
/* Signal we're started */
ret = pthread_mutex_lock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to lock the mutex"); }
c->tcnt += 1;
ret = pthread_mutex_unlock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to unlock the mutex"); }
do
{
/* Lock, control, then unlock */
do
{
ret = clock_gettime(CLOCK_REALTIME, &ts);
if (ret != 0)
{ UNRESOLVED(errno, "Unable to get time for timeout"); }
ts.tv_sec++; /* We will wait for 1 second */
ret = pthread_mutex_timedlock(&(c->mtx), &ts);
} while (ret == ETIMEDOUT);
if (ret != 0)
{ UNRESOLVED(ret, "Timed mutex lock failed in worker thread"); }
control(c, &loc);
ret = pthread_mutex_unlock(&(c->mtx));
if (ret != 0)
{ UNRESOLVED(ret, "Mutex unlock failed in worker thread"); }
/* Increment the operation counter */
c->opcnt++;
}
while (do_it);
/* Wait for the signal thread to terminate before we can exit */
waitsigend(c);
return NULL;
}
void * trylockthr(void * arg)
{
int ret;
char loc; /* Local value for control */
cell_t * c = (cell_t *)arg;
/* Set the thread local data key value (used in the signal handler) */
ret = pthread_setspecific(_c, arg);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to assign the thread-local-data key"); }
/* Signal we're started */
ret = pthread_mutex_lock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to lock the mutex"); }
c->tcnt += 1;
ret = pthread_mutex_unlock(&(c->tmtx));
if (ret != 0) { UNRESOLVED(ret, "Failed to unlock the mutex"); }
do
{
/* Lock, control, then unlock */
do
{
ret = pthread_mutex_trylock(&(c->mtx));
} while (ret == EBUSY);
if (ret != 0)
{ UNRESOLVED(ret, "Mutex lock try failed in worker thread"); }
control(c, &loc);
ret = pthread_mutex_unlock(&(c->mtx));
if (ret != 0)
{ UNRESOLVED(ret, "Mutex unlock failed in worker thread"); }
/* Increment the operation counter */
c->opcnt++;
}
while (do_it);
/* Wait for the signal thread to terminate before we can exit */
waitsigend(c);
return NULL;
}
/***** The next function initializes a cell_t object
* This includes running the threads */
void cell_init(int id, cell_t * c, pthread_mutexattr_t *pma)
{
int ret, i;
pthread_attr_t pa; /* We will specify a minimal stack size */
/* mark this group with its ID */
c->id = id;
/* Initialize some other values */
c->sigok = 0;
c->ctrl = 0;
c->sigcnt = 0;
c->opcnt = 0;
c->tcnt = 0;
/* Initialize the mutex */
ret = pthread_mutex_init(&(c->tmtx), NULL);
if (ret != 0)
{ UNRESOLVED(ret, "Mutex init failed"); }
ret = pthread_mutex_init(&(c->mtx), pma);
if (ret != 0)
{ UNRESOLVED(ret, "Mutex init failed"); }
#if VERBOSE > 1
output("Mutex initialized in cell %i\n", id);
#endif
/* Initialize the semaphore */
ret = sem_init(&(c->semsig), 0, 0);
if (ret != 0)
{ UNRESOLVED(errno, "Sem init failed"); }
#if VERBOSE > 1
output("Semaphore initialized in cell %i\n", id);
#endif
/* Create the thread attribute with the minimal size */
ret = pthread_attr_init(&pa);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to create pthread attribute object"); }
ret = pthread_attr_setstacksize(&pa, sysconf(_SC_THREAD_STACK_MIN));
if (ret != 0)
{ UNRESOLVED(ret, "Unable to specify minimal stack size"); }
/* Create the signal thread */
ret = pthread_create(&(c->threads[0]), &pa, sigthr, (void *) c);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to create the signal thread"); }
/* Create 5 "lock" threads */
for (i=1; i<=5; i++)
{
ret = pthread_create(&(c->threads[i]), &pa, lockthr, (void *) c);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to create a locker thread"); }
}
/* Create 2 "timedlock" threads */
for (i=6; i<=7; i++)
{
ret = pthread_create(&(c->threads[i]), &pa, timedlockthr, (void *) c);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to create a (timed) locker thread"); }
}
/* Create 2 "trylock" threads */
for (i=8; i<=9; i++)
{
ret = pthread_create(&(c->threads[i]), &pa, trylockthr, (void *) c);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to create a (try) locker thread"); }
}
#if VERBOSE > 1
output("All threads initialized in cell %i\n", id);
#endif
/* Destroy the thread attribute object */
ret = pthread_attr_destroy(&pa);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to destroy thread attribute object"); }
/* Tell the signal thread to start working */
ret = sem_post(&(c->semsig));
if (ret != 0)
{ UNRESOLVED(ret, "Unable to post signal semaphore"); }
}
/***** The next function destroys a cell_t object
* This includes stopping the threads */
void cell_fini(
int id,
cell_t * c,
unsigned long long * globalopcount,
unsigned long long * globalsigcount )
{
int ret, i;
/* Just a basic check */
if (id != c->id)
{
output("Something is wrong: Cell %i has id %i\n", id, c->id);
FAILED("Some memory has been corrupted");
}
/* Start with joining the threads */
for (i=0; i<10; i++)
{
ret = pthread_join(c->threads[i], NULL);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to join a thread"); }
}
/* Destroy the semaphore and the mutex */
ret = sem_destroy(&(c->semsig));
if (ret != 0)
{ UNRESOLVED(errno, "Unable to destroy the semaphore"); }
ret = pthread_mutex_destroy(&(c->mtx));
if (ret != 0)
{
output("Unable to destroy the mutex in cell %i (ret = %i)\n", id, ret);
FAILED("Mutex destruction failed");
}
/* Report the cell counters */
*globalopcount += c->opcnt;
*globalsigcount += c->sigcnt;
#if VERBOSE > 1
output("Counters for cell %i:\n\t%llu locks and unlocks\n\t%llu signals\n",
id,
c->opcnt,
c->sigcnt);
#endif
/* We are done with this cell. */
}
/**** Next function is called when the process is killed with SIGUSR1
* It tells every threads in every cells to stop their work.
*/
void globalsig(int sig)
{
output("Signal received, processing. Please wait...\n");
do { do_it=0; }
while (do_it);
}
/******
* Last but not least, the main function
*/
int main(int argc, char * argv[])
{
/* Main is responsible for :
* the mutex attributes initializing
* the creation of the cells
* the destruction of everything on SIGUSR1 reception
*/
int ret;
int i;
struct sigaction sa;
unsigned long long globopcnt=0, globsigcnt=0;
#ifndef WITHOUT_XOPEN
int sz = 2 + (sizeof(types) / sizeof(int));
#else
int sz = 2;
#endif
pthread_mutexattr_t ma[sz-1];
pthread_mutexattr_t *pma[sz];
cell_t data[sz * N * SCALABILITY_FACTOR];
pma[sz-1] = NULL;
#if VERBOSE > 0
output("Mutex lock / unlock stress sample is starting\n");
output("Kill with SIGUSR1 to stop the process\n");
output("\t kill -USR1 <pid>\n\n");
#endif
/* Initialize the mutex attributes */
for (i=0; i<sz-1; i++)
{
pma[i] = &ma[i];
ret = pthread_mutexattr_init(pma[i]);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to init a mutex attribute object"); }
#ifndef WITHOUT_XOPEN /* we have the mutex attribute types */
if (i != 0)
{
ret = pthread_mutexattr_settype(pma[i], types[i-1]);
if (ret != 0)
{
UNRESOLVED(ret, "Unable to set type of a mutex attribute object");
}
}
#endif
}
#if VERBOSE > 1
output("%i mutex attribute objects were initialized\n", sz - 1);
#endif
/* Initialize the thread-local-data key */
ret = pthread_key_create(&_c, NULL);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to initialize TLD key"); }
#if VERBOSE > 1
output("TLD key initialized\n");
#endif
/* Register the signal handler for SIGUSR1 */
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = globalsig;
if ((ret = sigaction (SIGUSR1, &sa, NULL)))
{ UNRESOLVED(ret, "Unable to register signal handler"); }
/* Register the signal handler for SIGUSR2 */
sa.sa_handler = sighdl;
if ((ret = sigaction (SIGUSR2, &sa, NULL)))
{ UNRESOLVED(ret, "Unable to register signal handler"); }
/* Start every threads */
#if VERBOSE > 0
output("%i cells of 10 threads are being created...\n", sz * N * SCALABILITY_FACTOR);
#endif
for (i=0; i< sz * N * SCALABILITY_FACTOR; i++)
cell_init(i, &data[i], pma[i % sz]);
#if VERBOSE > 0
output("All threads created and running.\n");
#endif
/* We stay here while not interrupted */
do { sched_yield(); }
while (do_it);
#if VERBOSE > 0
output("Starting to join the threads...\n");
#endif
/* Everybody is stopping, we must join them, and destroy the cell data */
for (i=0; i< sz * N * SCALABILITY_FACTOR; i++)
cell_fini(i, &data[i], &globopcnt, &globsigcnt);
/* Destroy the mutex attributes objects */
for (i=0; i<sz-1; i++)
{
ret = pthread_mutexattr_destroy(pma[i]);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to destroy a mutex attribute object"); }
}
/* Destroy the thread-local-data key */
ret = pthread_key_delete(_c);
if (ret != 0)
{ UNRESOLVED(ret, "Unable to destroy TLD key"); }
#if VERBOSE > 1
output("TLD key destroyed\n");
#endif
/* output the total counters */
#if VERBOSE > 1
output("===============================================\n");
#endif
#if VERBOSE > 0
output("Total counters:\n\t%llu locks and unlocks\n\t%llu signals\n",
globopcnt,
globsigcnt);
output("pthread_mutex_lock stress test passed.\n");
#endif
PASSED;
}