#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <string.h>
#include <err.h>
#include "bench.h"

static char name[] =  "Mutex Lock/Unlock, Contention";
static char doc[] = 
"\tThis is the time interval between when one thread calls\n"
"\tpthread_mutex_unlock() and another thread that was blocked\n"
"\ton pthread_mutex_lock() returns with the lock held.";

/*

The order of locking looks like this:

	A		B		1  2  3
	===============	===============	== == ==
	lock(2)				   A  
	yield()				   A
			lock(1)		   A  B
			lock(3)		B  A  B
			yield()		B  A  B
	lock(1)				Ba A  B
-------
			unlock(1)	 a A  B
			lock(2)		 a Ab B
	^				A  Ab B
	unlock(2)			A   b B
	lock(3)				A   b Ba
			^		A  B  Ba
			unlock(3)	A  B   a
			lock(1)		Ab B   a
	^				Ab B  A
	unlock(1)			 b B  A
	lock(2)				 b Ba A
			^		B  Ba A
			unlock(2)	B   a A
			lock(3)		B   a Ab
	^				B  A  Ab
	unlock(3)			B  A   b
	lock(1)				Ba A   b
			^		Ba A  B
-------
			unlock(1)	 a A  B
			unlock(3)	 a A
			exit
	^				A  A
	unlock(1)			   A
	unlock(2)
	exit

	In every cycle, there will be 6 transitions and 6 lock/unlock
	pairs.  So, to compute the transition time, we subtract the
	lock/unlock time computed without contention.
*/

static pthread_mutex_t m1, m2, m3;
static bench_t ba, bb;

void *
thread_a(arg)
{
	pthread_set_name_np(pthread_self(), "ta");
	pthread_mutex_lock(&m2);
	sched_yield();

	pthread_mutex_lock(&m1);
	bench_amortize(&ba, BENCH_LOOPS) {
		pthread_mutex_unlock(&m2);
		pthread_mutex_lock(&m3);
		pthread_mutex_unlock(&m1);
		pthread_mutex_lock(&m2);
		pthread_mutex_unlock(&m3);
		pthread_mutex_lock(&m1);
	}
	pthread_mutex_unlock(&m1);
	pthread_mutex_unlock(&m2);
	return (NULL);
}

void *
thread_b(arg)
{
	pthread_set_name_np(pthread_self(), "tb");
	pthread_mutex_lock(&m1);
	pthread_mutex_lock(&m3);
	sched_yield();

	bench_amortize(&bb, BENCH_LOOPS) {
		pthread_mutex_unlock(&m1);
		pthread_mutex_lock(&m2);
		pthread_mutex_unlock(&m3);
		pthread_mutex_lock(&m1);
		pthread_mutex_unlock(&m2);
		pthread_mutex_lock(&m3);
	}
	pthread_mutex_unlock(&m1);
	pthread_mutex_unlock(&m3);
	return (NULL);
}

int
main() {
	pthread_t ta, tb;

	bench_init(&ba, name, doc, "from unlock to lock inclusive");
	bench_init(&bb, NULL, NULL, NULL);

	bench_header(&ba);

	pthread_mutex_init(&m1, NULL);
	pthread_mutex_init(&m2, NULL);
	pthread_mutex_init(&m3, NULL);

	pthread_create(&ta, NULL, thread_a, NULL);
	pthread_create(&tb, NULL, thread_b, NULL);

	pthread_join(ta, NULL);
	pthread_join(tb, NULL);

	ba.divisor = bb.divisor = 6;

	bench_report(&ba);
/*	bench_report(&bb); */
	exit(0);
}