/*
 * 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;
}
