#include <pthread.h>
#include <stdio.h>
#include <strings.h>
#include <assert.h>

static pthread_barrier_t bar;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cnd = PTHREAD_COND_INITIALIZER;
static int called = 0;

#ifndef ASSERT
#define ASSERT(x) assert(x)
#endif /* ASSERT */

static void cleanupmutex(void * arg)
{
	printf("cancellation cleanup handler called\n");
	if (arg) {
		ASSERT( pthread_mutex_unlock((pthread_mutex_t *)arg) == 0 );
		called++;
	}
	
}

static void * mythread(void * a)
{
	int ret;
	
	/* lock mutex */
	ASSERT( pthread_mutex_lock(&mtx) == 0 );
		
	/* Push cleanup */
	pthread_cleanup_push(cleanupmutex, &mtx);
	
	printf("thread synchronization (mutex acquired)\n");
	
	/* Wake the other thread */
	ret = pthread_barrier_wait(&bar);
	ASSERT( (ret == 0) || (ret == PTHREAD_BARRIER_SERIAL_THREAD) );
	
	/* Now wait for the condition, this unlocks the mutex */
	do {
		printf("thread waiting cond\n");
		ASSERT( pthread_cond_wait(&cnd, &mtx) == 0);
		printf("thread woken\n");
	} while (1);
	
	/* Cleanup, never reached */
	pthread_cleanup_pop(1);
	return NULL;
}

int main(int argc, char * argv[])
{
	int ret;
	pthread_t thr;
	void * dummy;
	
	/* initialize the barrier */
	ASSERT( pthread_barrier_init(&bar, NULL, 2) == 0 );
	
	printf("Creating thread\n");
	
	/* Create the thread */
	ASSERT( pthread_create(&thr, NULL, mythread, NULL) == 0 );
	
	printf("main synchronization\n");
	ret = pthread_barrier_wait(&bar);
	ASSERT( (ret == 0) || (ret == PTHREAD_BARRIER_SERIAL_THREAD) );
	
	ASSERT( pthread_mutex_lock(&mtx) == 0 );
	printf("main: thread is now waiting for condvar\n");
	
	/* Cancel the thread */
	ASSERT( pthread_cancel(thr) == 0 );
	
	/* Now, unlock, so that the thread can actually really exit */
	ASSERT( pthread_mutex_unlock(&mtx) == 0 );
	
	/* Release thread resources */
	ASSERT( pthread_join(thr, &dummy) == 0 );
	
	if (called == 1)
		printf("Test successful!\n");
	else
		printf("Test failed! Cleanup was not called (& lock not released)\n");
	
	return 0;
	
}
