#ifndef	lint
static char rcs_id[] = "$Header: threads.c,v 1.3 88/05/09 16:02:50 ecc Exp $";
#endif	not lint

/*
 * threads/threads.c
 *
 * $Source: /usr0/ecc/nectar/src/cab/threads/RCS/threads.c,v $
 *
 * Implementation of exported thread functions.
 */


#include <nectar_sys.h>
#include <threads.h>
#include "thread_internals.h"

/*
 * Thread status bits.
 */

#define	T_MAIN		0x1
#define	T_RETURNED	0x2
#define	T_DETACHED	0x4

EXPORT boolean_t Thread_debug = FALSE;

PRIVATE struct Queue threads = QUEUE_INITIALIZER;
PRIVATE struct Mutex thread_lock = MUTEX_INITIALIZER;
PRIVATE struct Condition thread_needed = CONDITION_INITIALIZER;
PRIVATE struct Condition thread_idle = CONDITION_INITIALIZER;
PRIVATE int n_procs = 0;
PRIVATE int n_threads = 0;
PRIVATE int max_procs = 0;

PRIVATE Thread_t free_threads = NO_THREAD;			/* free list */
PRIVATE struct Mutex free_lock = MUTEX_INITIALIZER;

PRIVATE struct Thread initial_thread = { 0 };


PRIVATE Thread_t
thread_Alloc(func, arg)
	Thread_fn_t func;
	pointer_t arg;
    BEGIN(thread_Alloc)
	register Thread_t t = NO_THREAD;

	if (free_threads != NO_THREAD) {
		/*
		 * Don't try for the lock unless
		 * the list is likely to be nonempty.
		 * We can't be sure, though, until we lock it.
		 */
		Mutex_Lock(&free_lock);
		t = free_threads;
		if (t != NO_THREAD)
			free_threads = t->next;
		Mutex_Unlock(&free_lock);
	}
	if (t == NO_THREAD) {
		/*
		 * The free list was empty.
		 * We may have only found this out after
		 * locking it, which is why this isn't an
		 * "else" branch of the previous statement.
		 */
		t = NEW(struct Thread);
	}
	*t = initial_thread;
	t->func = func;
	t->arg = arg;
	RETURN(t);
    END(thread_Alloc)


PRIVATE void
thread_Free(t)
	register Thread_t t;
    BEGIN(thread_Free)
	Mutex_Lock(&free_lock);
	t->next = free_threads;
	free_threads = t;
	Mutex_Unlock(&free_lock);
	RET;
    END(thread_Free)


EXPORT void
Thread_Init()
    BEGIN(Thread_Init)
	static boolean_t threads_started = FALSE;
	Thread_t t;

	if (threads_started)
		RET;
	thread_Proc_Init();
	n_procs = 1;
	t = thread_Alloc((Thread_fn_t) 0, (pointer_t) 0);
	n_threads = 1;
	t->state |= T_MAIN;
	Thread_Set_Name(t, "main");
	thread_Assoc(thread_Proc_Self(), t);
	threads_started = TRUE;
	RET;
    END(Thread_Init)


/*
 * thread_Body
 *
 *	Procedure invoked at the base of each thread.
 *	Called from thread_Proc_Start() with interrupts disabled.
 *
 * Parameters:
 *	self	Pointer to proc structure for this thread.
 */

PUBLIC void
thread_Body(self)
	proc_t self;
    BEGIN(thread_Body)
	Thread_t t;

	Intr_Enable();	/* wakeup point */
	ASSERT(thread_Proc_Self() == self);
	Mutex_Lock(&thread_lock);
	for (;;) {
		/*
		 * Dequeue a thread invocation request.
		 */
		QUEUE_DEQ(&threads, Thread_t, t);
		if (t != NO_THREAD) {
			/*
			 * We have a thread to execute.
			 */
			Mutex_Unlock(&thread_lock);
			thread_Assoc(self, t);		/* assume thread's identity */
			DEBUG(Thread_debug,
				(msg, "thread_Body: starting 0x%x(0x%x)\n", t->func, t->arg));
			if (_setjmp(t->catch) == 0) {	/* catch for Thread_Exit() */
				/*
				 * Execute the fork request.
				 */
				t->result = (*(t->func))(t->arg);
			}
			/*
			 * Return result from thread.
			 */
			DEBUG(Thread_debug,
				(msg, "thread_Body: finished 0x%x(0x%x)\n", t->func, t->arg));
			Mutex_Lock(&t->lock);
			if (t->state & T_DETACHED) {
				Mutex_Unlock(&t->lock);
				thread_Free(t);
			} else {
				t->state |= T_RETURNED;
				Mutex_Unlock(&t->lock);
				Condition_Signal(&t->done);
			}
			thread_Assoc(self, NO_THREAD);
			Mutex_Lock(&thread_lock);
			n_threads -= 1;
		} else {
			/*
			 * Queue is empty.
			 * Signal that we're idle in case the main thread
			 * is waiting to exit, then wait for reincarnation.
			 */
			Condition_Signal(&thread_idle);
			Condition_Wait(&thread_needed, &thread_lock);
		}
	}
    END(thread_Body)


EXPORT Thread_t
Thread_Fork(func, arg)
	Thread_fn_t func;
	pointer_t arg;
    BEGIN(Thread_Fork)
	Thread_t t;

	Mutex_Lock(&thread_lock);
	t = thread_Alloc(func, arg);
	QUEUE_ENQ(&threads, t);
	if (++n_threads > n_procs && (max_procs == 0 || n_procs < max_procs)) {
		n_procs += 1;
		thread_Proc_Create();
	}
	Mutex_Unlock(&thread_lock);
	Condition_Signal(&thread_needed);
	RETURN(t);
    END(Thread_Fork)


EXPORT void
Thread_Detach(t)
	Thread_t t;
    BEGIN(Thread_Detach)
	Mutex_Lock(&t->lock);
	if (t->state & T_RETURNED) {
		Mutex_Unlock(&t->lock);
		thread_Free(t);
	} else {
		t->state |= T_DETACHED;
		Mutex_Unlock(&t->lock);
	}
	RET;
    END(Thread_Detach)


EXPORT pointer_t
Thread_Join(t)
	Thread_t t;
    BEGIN(Thread_Join)
	pointer_t result;

	Mutex_Lock(&t->lock);
	ASSERT(! (t->state & T_DETACHED));
	while (! (t->state & T_RETURNED))
		Condition_Wait(&t->done, &t->lock);
	result = t->result;
	Mutex_Unlock(&t->lock);
	thread_Free(t);
	RETURN(result);
    END(Thread_Join)


EXPORT void
Thread_Exit(result)
	pointer_t result;
    BEGIN(Thread_Exit)
	Thread_t t = Thread_Self();
	IMPORT exit();

	t->result = result;
	if (t->state & T_MAIN) {
		Mutex_Lock(&thread_lock);
		while (n_threads > 1)
			Condition_Wait(&thread_idle, &thread_lock);
		Mutex_Unlock(&thread_lock);
		exit((int) result);
	} else {
		_longjmp(t->catch, TRUE);
	}
	/*
	 * This point is never reached.
	 */
	ASSERT(SHOULDNT_HAPPEN);
	RET;
    END(Thread_Exit)


EXPORT void
Thread_Set_Name(t, name)
	Thread_t t;
	string_t name;
    BEGIN(Thread_Set_Name)
	t->name = name;
	RET;
    END(Thread_Set_Name)

EXPORT string_t
Thread_Name(t)
	Thread_t t;
    BEGIN(Thread_Name)
	RETURN(t == NO_THREAD
		? "idle"
		: (t->name == NO_STRING
			? "?"
			: t->name));
    END(Thread_Name)

EXPORT void
Thread_Set_Limit(n)
	int n;
    BEGIN(Thread_Set_Limit)
	max_procs = n;
	RET;
    END(Thread_Set_Limit)

EXPORT int
Thread_Limit()
    BEGIN(Thread_Limit)
	RETURN(max_procs);
    END(Thread_Limit)

EXPORT int
Thread_Count()
    BEGIN(Thread_Count)
	RETURN(n_threads);
    END(Thread_Count)
