/* a queue of requests that gets dispatched without having to use
 * sched_yield() which may cause problems.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

struct request_tag_t {
	struct request_tag_t *next;
	int operation;
};

struct dispatcher_tag_t {
	struct request_tag_t *first;
	struct request_tag_t *last;
	int running;
	pthread_mutex_t mutex;
	pthread_cond_t request;
	pthread_mutex_t resched;
};

struct dispatcher_tag_t dispatcher = {
	.first = NULL,
	.last = NULL,
	.running = 0,
	.mutex = PTHREAD_MUTEX_INITIALIZER,
	.request = PTHREAD_COND_INITIALIZER,
	.resched = PTHREAD_MUTEX_INITIALIZER,
};

int rand_num(int range)
{
	return (int)((range)*1.0*rand()/(RAND_MAX+1.0));
}

void *dispatcher_routine(void *arg)
{
	struct request_tag_t *request;

	printf("dispatcher starting\n");

	while (1)
	{
		/* take the dispatcher queue mutex */
		pthread_mutex_lock(&dispatcher.mutex);

		/* if nothing to do, wait on the dispatcher queue
		 * condition variable until there is */
		while (dispatcher.first == NULL)
			pthread_cond_wait(&dispatcher.request, &dispatcher.mutex);

		/* take a request from the queue */
		request = dispatcher.first;
		dispatcher.first = request->next;
		if (dispatcher.first == NULL)
			dispatcher.last = NULL;

		/* handle request, maybe by handling it in a new thread. */
		printf("I'm dispatching request %d\n", request->operation);

		/* free request we have just finished with */
		free(request);

		/* release queue lock */
		pthread_mutex_unlock(&dispatcher.mutex);
#ifdef RESCHED
		/* release the resched lock to signal we've seen the queue */
		pthread_mutex_unlock(&dispatcher.resched);
#endif
	}
}

int tries = 1;

void request(int operation)
{
	struct request_tag_t *request;

	/* take the dispatcher queue mutex */
	pthread_mutex_lock(&dispatcher.mutex);

	/* start the thread if it isn't */
	if (!dispatcher.running)
	{
		pthread_t thread;
		printf("dispatcher not running, starting\n");
		dispatcher.running = 1;
		pthread_create(&thread, NULL, dispatcher_routine, NULL);
	}

	/* allocate a new request */
	request = malloc(sizeof(struct request_tag_t));
	if (request == NULL)
		exit(1);
	request->next = NULL;
	request->operation = operation;

	/* add to the queue, maintaing first and last */
	if (dispatcher.first == NULL) {
		dispatcher.first = request;
		dispatcher.last = request;
	} else {
		(dispatcher.last)->next = request;
		dispatcher.last = request;
	}

	/* signal something to do */
	pthread_cond_signal(&dispatcher.request);

	/* free the queue lock */
	pthread_mutex_unlock(&dispatcher.mutex);

#ifdef RESCHED
	/* try the resched lock.  if we can't have it, that means
	 * we've already got it and the dispatch thread hasn't had a
	 * chance to run and unlock it.  We queue up to this 10 times
	 * before allowing the dispatcher to run by sleeping on the
	 * mutex
	 */
	if (pthread_mutex_trylock(&dispatcher.resched) == EBUSY) {
		if (tries++ == 10) {
			pthread_mutex_lock(&dispatcher.resched);
			tries = 1;
		}
	} else 
		tries = 1;
#endif
}

int main(void) {
	while(1)
	{
		int n = rand_num(3);
		printf("requesting %d\n", n);
		request(n);

		/* if you uncomment this, everything should play
		nicely with one dispatch per request, because other
		threads get a chance to run. */
		/*usleep(500000);*/
	}
}

