/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/* 
 * thread.c --
 *
 *	Routines for creating, exiting, and joining threads.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsthread/thread.c,v 1.11 2005/08/08 11:30:51 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;

#include "thread.h"

/*
 * Private flags for managing threads.
 */

#define FLAG_DETACHED	1
#define FLAG_HAVESTACK	2
#define FLAG_STACKDOWN	4

/*
 * The following structure is used as the startup arg for new threads.
 */

typedef struct ThreadArg {
    Ns_ThreadProc  *proc;	/* Thread startup routine. */ 
    void           *arg;	/* Argument to startup proc. */
    int		    flags;	/* Thread is detached. */
    char	    parent[NS_THREAD_NAMESIZE];
} ThreadArg;

/*
 * The following structure maintains all state for a thread
 * including thread local storage slots.
 */

typedef struct Thread {
    struct Thread  *nextPtr;	/* Next in list of all threads. */
    Ns_Time	    ctime;	/* Thread structure create time. */
    int		    flags;	/* Detached, joined, etc. */
    Ns_ThreadProc  *proc;	/* Thread startup routine. */ 
    void           *arg;	/* Argument to startup proc. */
    int		    tid;        /* Id set by thread for logging. */
    void	   *stackaddr;	/* Thread stack address. */
    size_t	    stacksize;	/* Thread stack size. */
    char	    name[NS_THREAD_NAMESIZE+1]; /* Thread name. */
    char	    parent[NS_THREAD_NAMESIZE+1]; /* Parent name. */
} Thread;

/*
 * Static functions defined in this file.
 */

static Thread *NewThread(ThreadArg *argPtr);
static Thread *GetThread(void);
static void CleanupThread(void *arg);

/*
 * The following pointer and lock maintain a linked list of all threads.
 */

static Thread *firstThreadPtr;
static Ns_Mutex threadlock;

/*
 * The following int and lock maintain the default stack size which can
 * be changed dynamically at runtime.
 */

static int stackdef;
static int stackmin;
static Ns_Mutex sizelock;

/*
 * The following maintains the tls key for the thread context.
 */

static Ns_Tls key;


/*
 *----------------------------------------------------------------------
 *
 * NsThreads_LibInit --
 *
 *	Initialize threads interface.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocates pthread_key_t for thread context.
 *
 *----------------------------------------------------------------------
 */

void
NsThreads_LibInit(void)
{
    static int once = 0;

    if (!once) {
	once = 1;
	NsInitThreads();
    	NsInitMaster();
    	NsInitReentrant();
	Ns_MutexSetName(&threadlock, "ns:threads");
	Ns_MutexSetName(&sizelock, "ns:stacksize");
    	Ns_TlsAlloc(&key, CleanupThread);
	stackdef = 64 * 1024;
	stackmin = 16 * 1024;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadCreate --
 *
 *	Create a new thread thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new thread is allocated and started.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadCreate(Ns_ThreadProc *proc, void *arg, long stacksize,
    	    	Ns_Thread *resultPtr)
{
    ThreadArg *argPtr;

    /*
     * Determine the stack size and add the guard.
     */

    if (stacksize <= 0) {
    	stacksize = Ns_ThreadStackSize(0);
    }
    if (stacksize < stackmin) {
	stacksize = stackmin;
    }

    /*
     * Create the thread.
     */

    argPtr = ns_malloc(sizeof(ThreadArg));
    argPtr->proc = proc;
    argPtr->arg = arg;
    argPtr->flags = resultPtr ? 0 : FLAG_DETACHED;
    strcpy(argPtr->parent, Ns_ThreadGetName());
    NsCreateThread(argPtr, stacksize, resultPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadStackSize --
 *
 *	Set default stack size.
 *
 * Results:
 *	Previous stack size.
 *
 * Side effects:
 *	New threads will use default size.
 *
 *----------------------------------------------------------------------
 */

long
Ns_ThreadStackSize(long stacksize)
{
    long prev;

    Ns_MutexLock(&sizelock);
    prev = stackdef;
    if (stacksize > 0) {
	stackdef = stacksize;
    }
    Ns_MutexUnlock(&sizelock);
    return prev;
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadMain --
 *
 *	Thread startup routine.  Sets the given pre-allocated thread
 *	structure and calls the user specified procedure.
 *
 * Results:
 *	None.  Will call Ns_ThreadExit if not called by the
 *	user code.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
NsThreadMain(void *arg)
{
    ThreadArg   *argPtr = arg;
    Thread      *thrPtr;

    thrPtr = NewThread(argPtr);
    ns_free(argPtr);
    (*thrPtr->proc) (thrPtr->arg);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadGetName --
 *
 *	Return a pointer to calling thread's string name.
 *
 * Results:
 *	Pointer to thread name string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
Ns_ThreadGetName(void)
{
    Thread *thrPtr = GetThread();

    return thrPtr->name;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadSetName --
 *
 *	Set the name of the calling thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	String is copied to thread data structure.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadSetName(char *name)
{
    Thread *thrPtr = GetThread();

    Ns_MutexLock(&threadlock);
    strncpy(thrPtr->name, name, NS_THREAD_NAMESIZE);
    Ns_MutexUnlock(&threadlock);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadGetParent --
 *
 *	Return a pointer to calling thread's parent name.
 *
 * Results:
 *	Pointer to thread parent name string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
Ns_ThreadGetParent(void)
{
    Thread *thrPtr = GetThread();

    return thrPtr->parent;
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadList --
 *
 *	Append info for each thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadList(Tcl_DString *dsPtr, Ns_ThreadArgProc *proc)
{
    Thread *thrPtr;
    char buf[100];

    Ns_MutexLock(&threadlock);
    thrPtr = firstThreadPtr;
    while (thrPtr != NULL) {
	Tcl_DStringStartSublist(dsPtr);
	Tcl_DStringAppendElement(dsPtr, thrPtr->name);
	Tcl_DStringAppendElement(dsPtr, thrPtr->parent);
	sprintf(buf, " %d %d %ld", thrPtr->tid,
		(thrPtr->flags & FLAG_DETACHED) ? NS_THREAD_DETACHED : 0,
		thrPtr->ctime.sec);
	Tcl_DStringAppend(dsPtr, buf, -1);
	if (proc != NULL) {
	    (*proc)(dsPtr, (void *) thrPtr->proc, thrPtr->arg);
	} else {
	    sprintf(buf, " %p %p", thrPtr->proc, thrPtr->arg);
	    Tcl_DStringAppend(dsPtr, buf, -1);
	}
	Tcl_DStringEndSublist(dsPtr);
	thrPtr = thrPtr->nextPtr;
    }
    Ns_MutexUnlock(&threadlock);
}


/*
 *----------------------------------------------------------------------
 *
 * NewThread --
 *
 *	Allocate a new thread data structure and add it to the list
 *	of all threads.  The new thread is suitable for a detached,
 *	unknown thread such as the initial thread but Ns_ThreadCreate
 *	will update as necessary before creating the new threads.
 *
 * Results:
 *	Pointer to new Thread.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Thread *
NewThread(ThreadArg *argPtr)
{
    Thread *thrPtr;
    int stack;

    thrPtr = ns_calloc(1, sizeof(Thread));
    Ns_GetTime(&thrPtr->ctime);
    thrPtr->tid = Ns_ThreadId();
    sprintf(thrPtr->name, "-thread%d-", thrPtr->tid);
    if (argPtr == NULL) {
    	thrPtr->flags = FLAG_DETACHED;
    } else {
	thrPtr->flags = argPtr->flags;
	thrPtr->proc = argPtr->proc;
	thrPtr->arg = argPtr->arg;
	strcpy(thrPtr->parent, argPtr->parent);
    }
    stack = NsGetStack(&thrPtr->stackaddr, &thrPtr->stacksize);
    if (stack) {
	thrPtr->flags |= FLAG_HAVESTACK;
	if (stack < 0) {
	    thrPtr->flags |= FLAG_STACKDOWN;
	}
    }
    Ns_TlsSet(&key, thrPtr);
    Ns_MutexLock(&threadlock);
    thrPtr->nextPtr = firstThreadPtr;
    firstThreadPtr = thrPtr;
    Ns_MutexUnlock(&threadlock);
    return thrPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * GetThread --
 *
 *	Return this thread's nsthread data structure, initializing
 *	it if necessary, normally for the first thread but also
 *	for threads created without Ns_ThreadCreate.
 *
 * Results:
 *	Pointer to per-thread data structure.
 *
 * Side effects:
 *	Key is allocated the first time.
 *
 *----------------------------------------------------------------------
 */

static Thread *
GetThread(void)
{
    Thread *thrPtr;

    thrPtr = Ns_TlsGet(&key);
    if (thrPtr == NULL) {
	thrPtr = NewThread(NULL);
    }
    return thrPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * CleanupThread --
 *
 *	TLS cleanup for the nsthread context. 
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
CleanupThread(void *arg)
{
    Thread **thrPtrPtr;
    Thread *thrPtr = arg;

    Ns_MutexLock(&threadlock);
    thrPtrPtr = &firstThreadPtr;
    while (*thrPtrPtr != thrPtr) {
	thrPtrPtr = &(*thrPtrPtr)->nextPtr;
    }
    *thrPtrPtr = thrPtr->nextPtr;
    thrPtr->nextPtr = NULL;
    Ns_MutexUnlock(&threadlock);
    ns_free(thrPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CheckStack --
 *
 *	Check a thread stack for overflow.
 *
 * Results:
 *	NS_OK:	   Stack appears ok.
 *	NS_BREAK:  Overflow appears likely.
 *	NS_ERROR:  Stack address/size unknown.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_CheckStack(void)
{
    Thread *thrPtr = GetThread();
    caddr_t limit;

    /*
     * Return error on no stack.
     */

    if (!(thrPtr->flags & FLAG_HAVESTACK)) {
	return NS_ERROR;
    }

    /*
     * Check if the stack has grown into or beyond the guard.
     */

    if (thrPtr->flags & FLAG_STACKDOWN) {
	limit = (caddr_t) thrPtr->stackaddr - thrPtr->stacksize;
	if ((caddr_t) &limit < limit) {
	    return NS_BREAK;
	}
    } else {
	limit = (caddr_t) thrPtr->stackaddr + thrPtr->stacksize;
	if ((caddr_t) &limit > limit) {
	    return NS_BREAK;
	}
    }
    return NS_OK;
}
