/*-
 * Copyright (c) 2005 Robert N. M. Watson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Simple pipelined work for three threads -- pass the memory around, zero
 * it, for a fixed number of iterations.  Somewhere here there is a race
 * condition that occasionally results in a hang.
 */

#include <sys/time.h>
#include <sys/queue.h>

#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define timespecsub(vvp, uvp)                                           \
        do {                                                            \
                (vvp)->tv_sec -= (uvp)->tv_sec;                         \
                (vvp)->tv_nsec -= (uvp)->tv_nsec;                       \
                if ((vvp)->tv_nsec < 0) {                               \
                        (vvp)->tv_sec--;                                \
                        (vvp)->tv_nsec += 1000000000;                   \
                }                                                       \
        } while (0)

#define	NUM_DATAGRAMS	1024
#define	NUM_ITERATIONS	51200
#define	NUM_LOOPS	12

struct datagram {
	LIST_ENTRY(datagram)	d_list;
	char			d_data[256];
};

/*
 * Mutex to protect startup -- wait for them all to be ready before
 * proceeding.  Each of three worker threads.
 */
pthread_mutex_t mutex_big;
pthread_cond_t	cond_big;
pthread_t thread1, thread2, thread3;
int	one_ready, two_ready, three_ready;

/*
 * Data stream - mutexes to protect queues, queues to hold datagrams.
 */
struct queue {
	LIST_HEAD(, datagram)	q_head;
	pthread_mutex_t		q_mutex;
	pthread_cond_t		q_cond;
};

struct queue	queue_one_to_two, queue_two_to_three, queue_three_to_one;

static void
start_barrier(int thread)
{

	assert(pthread_mutex_lock(&mutex_big) == 0);
	switch (thread) {
	case 1:
		one_ready = 1;
		break;
	case 2:
		two_ready = 1;
		break;
	case 3:
		three_ready = 1;
		break;
	default:
		assert(0 == 1);
	}
	assert(pthread_cond_signal(&cond_big) == 0);
	while (!(one_ready && two_ready && three_ready))
		assert(pthread_cond_wait(&cond_big, &mutex_big) == 0);
	assert(pthread_mutex_unlock(&mutex_big) == 0);
}

static void
queue_init(struct queue *queue)
{

	LIST_INIT(&queue->q_head);
	assert(pthread_mutex_init(&queue->q_mutex, NULL) == 0);
	assert(pthread_cond_init(&queue->q_cond, NULL) == 0);
}

static struct datagram *
dequeue_datagram(struct queue *queue)
{
	struct datagram *d;

	assert(pthread_mutex_lock(&queue->q_mutex) == 0);
	while (LIST_EMPTY(&queue->q_head))
		assert(pthread_cond_wait(&queue->q_cond, &queue->q_mutex) ==
		    0);
	d = LIST_FIRST(&queue->q_head);
	LIST_REMOVE(d, d_list);
	assert(pthread_mutex_unlock(&queue->q_mutex) == 0);

	return (d);
}

static void
enqueue_datagram(struct queue *queue, struct datagram *d)
{

	assert(pthread_mutex_lock(&queue->q_mutex) == 0);
	LIST_INSERT_HEAD(&queue->q_head, d, d_list);
	assert(pthread_cond_signal(&queue->q_cond) == 0);
	assert(pthread_mutex_unlock(&queue->q_mutex) == 0);
}

static void *
thread_one(void *arg)
{
	struct datagram *d;
	int i;

	start_barrier(1);

	for (i = 0; i < NUM_ITERATIONS; i++) {
		d = dequeue_datagram(&queue_three_to_one);
		bzero(d, sizeof(*d));
		enqueue_datagram(&queue_one_to_two, d);
	}

	return (NULL);
}

static void *
thread_two(void *arg)
{
	struct datagram *d;
	int i;

	start_barrier(2);

	for (i = 0; i < NUM_ITERATIONS; i++) {
		d = dequeue_datagram(&queue_one_to_two);
		bzero(d, sizeof(*d));
		enqueue_datagram(&queue_two_to_three, d);
	}

	return (NULL);
}

static void *
thread_three(void *arg)
{
	struct datagram *d;
	int i;

	start_barrier(3);

	for (i = 0; i < NUM_ITERATIONS; i++) {
		d = dequeue_datagram(&queue_two_to_three);
		bzero(d, sizeof(*d));
		enqueue_datagram(&queue_three_to_one, d);
	}

	return (NULL);
}

int
main(int argc, char *argv[])
{
	struct timespec start, finish;
	struct datagram *d;
	int i;

	assert(pthread_mutex_init(&mutex_big, NULL) == 0);
	assert(pthread_cond_init(&cond_big, NULL) == 0);

	queue_init(&queue_three_to_one);
	queue_init(&queue_one_to_two);
	queue_init(&queue_two_to_three);

	for (i = 0; i < NUM_DATAGRAMS; i++) {
		d = malloc(sizeof(*d));
		bzero(d, sizeof(*d));
		LIST_INSERT_HEAD(&queue_three_to_one.q_head, d, d_list);
	}

	for (i = 0; i < NUM_LOOPS; i++) {
		assert(clock_gettime(CLOCK_REALTIME, &start) == 0);
		assert(pthread_create(&thread1, NULL, thread_one, NULL) == 0);
		assert(pthread_create(&thread2, NULL, thread_two, NULL) == 0);
		assert(pthread_create(&thread3, NULL, thread_three, NULL) == 0);

		assert(pthread_join(thread1, NULL) == 0);
		assert(pthread_join(thread2, NULL) == 0);
		assert(pthread_join(thread3, NULL) == 0);
		assert(clock_gettime(CLOCK_REALTIME, &finish) == 0);

		timespecsub(&finish, &start);
		printf("%d.%09lu\n", finish.tv_sec, finish.tv_nsec);
	}

	return (0);
}
