/*
 * tclThreadStorage.c --
 *
 *	This file implements platform independent thread storage operations to
 *	work around system limits on the number of thread-specific variables.
 *
 * Copyright (c) 2003-2004 by Joe Mistachkin
 * Copyright (c) 2008 by George Peter Staplin
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tclInt.h"

#ifdef TCL_THREADS
#include <signal.h>

/*
 * IMPLEMENTATION NOTES:
 *
 * The primary idea is that we create one platform-specific TSD slot, and use
 * it for storing a table pointer. Each Tcl_ThreadDataKey has an offset into
 * the table of TSD values. We don't use more than 1 platform-specific TSD
 * slot, because there is a hard limit on the number of TSD slots. Valid key
 * offsets are greater than 0; 0 is for the initialized Tcl_ThreadDataKey.
 */

/*
 * The global collection of information about TSDs. This is shared across the
 * whole process, and includes the mutex used to protect it.
 */

static struct {
    void *key;			/* Key into the system TSD structure. The
				 * collection of Tcl TSD values for a
				 * particular thread will hang off the
				 * back-end of this. */
    sig_atomic_t counter;	/* The number of different Tcl TSDs used
				 * across *all* threads. This is a strictly
				 * increasing value. */
    Tcl_Mutex mutex;		/* Protection for the rest of this structure,
				 * which holds per-process data. */
} tsdGlobal = { NULL, 0, NULL };

/*
 * The type of the data held per thread in a system TSD.
 */

typedef struct {
    ClientData *tablePtr;	/* The table of Tcl TSDs. */
    sig_atomic_t allocated;	/* The size of the table in the current
				 * thread. */
} TSDTable;

/*
 * The actual type of Tcl_ThreadDataKey.
 */

typedef union {
    volatile sig_atomic_t offset;
				/* The type is really an offset into the
				 * thread-local table of TSDs, which is this
				 * field. */
    void *ptr;			/* For alignment purposes only. Not actually
				 * accessed through this. */
} TSDUnion;

/*
 * Forward declarations of functions in this file.
 */

static TSDTable *	TSDTableCreate(void);
static void		TSDTableDelete(TSDTable *tsdTablePtr);
static void		TSDTableGrow(TSDTable *tsdTablePtr,
			    sig_atomic_t atLeast);

/*
 * Allocator and deallocator for a TSDTable structure.
 */

static TSDTable *
TSDTableCreate(void)
{
    TSDTable *tsdTablePtr;
    sig_atomic_t i;

    tsdTablePtr = TclpSysAlloc(sizeof(TSDTable), 0);
    if (tsdTablePtr == NULL) {
	Tcl_Panic("unable to allocate TSDTable");
    }

    tsdTablePtr->allocated = 8;
    tsdTablePtr->tablePtr =
	    TclpSysAlloc(sizeof(void *) * tsdTablePtr->allocated, 0);
    if (tsdTablePtr->tablePtr == NULL) {
	Tcl_Panic("unable to allocate TSDTable");
    }

    for (i = 0; i < tsdTablePtr->allocated; ++i) {
	tsdTablePtr->tablePtr[i] = NULL;
    }

    return tsdTablePtr;
}

static void
TSDTableDelete(
    TSDTable *tsdTablePtr)
{
    sig_atomic_t i;

    for (i=0 ; i<tsdTablePtr->allocated ; i++) {
	if (tsdTablePtr->tablePtr[i] != NULL) {
	    /*
	     * These values were allocated in Tcl_GetThreadData in tclThread.c
	     * and must now be deallocated or they will leak.
	     */

	    ckfree(tsdTablePtr->tablePtr[i]);
	}
    }

    TclpSysFree(tsdTablePtr->tablePtr);
    TclpSysFree(tsdTablePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TSDTableGrow --
 *
 *	This procedure makes the passed TSDTable grow to fit the atLeast
 *	value.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The table is enlarged.
 *
 *----------------------------------------------------------------------
 */

static void
TSDTableGrow(
    TSDTable *tsdTablePtr,
    sig_atomic_t atLeast)
{
    sig_atomic_t newAllocated = tsdTablePtr->allocated * 2;
    ClientData *newTablePtr;
    sig_atomic_t i;

    if (newAllocated <= atLeast) {
	newAllocated = atLeast + 10;
    }

    newTablePtr = TclpSysRealloc(tsdTablePtr->tablePtr,
	    sizeof(ClientData) * newAllocated);
    if (newTablePtr == NULL) {
	Tcl_Panic("unable to reallocate TSDTable");
    }

    for (i = tsdTablePtr->allocated; i < newAllocated; ++i) {
	newTablePtr[i] = NULL;
    }

    tsdTablePtr->allocated = newAllocated;
    tsdTablePtr->tablePtr = newTablePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TclThreadStorageKeyGet --
 *
 *    This procedure gets the value associated with the passed key.
 *
 * Results:
 *	A pointer value associated with the Tcl_ThreadDataKey or NULL.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void *
TclThreadStorageKeyGet(
    Tcl_ThreadDataKey *dataKeyPtr)
{
    TSDTable *tsdTablePtr = TclpThreadGetGlobalTSD(tsdGlobal.key);
    ClientData resultPtr = NULL;
    TSDUnion *keyPtr = (TSDUnion *) dataKeyPtr;
    sig_atomic_t offset = keyPtr->offset;

    if ((tsdTablePtr != NULL) && (offset > 0)
	    && (offset < tsdTablePtr->allocated)) {
	resultPtr = tsdTablePtr->tablePtr[offset];
    }
    return resultPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TclThreadStorageKeySet --
 *
 *	This procedure set an association of value with the key passed. The
 *	associated value may be retrieved with TclThreadDataKeyGet().
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The thread-specific table may be created or reallocated.
 *
 *----------------------------------------------------------------------
 */

void
TclThreadStorageKeySet(
    Tcl_ThreadDataKey *dataKeyPtr,
    void *value)
{
    TSDTable *tsdTablePtr = TclpThreadGetGlobalTSD(tsdGlobal.key);
    TSDUnion *keyPtr = (TSDUnion *) dataKeyPtr;

    if (tsdTablePtr == NULL) {
	tsdTablePtr = TSDTableCreate();
	TclpThreadSetGlobalTSD(tsdGlobal.key, tsdTablePtr);
    }

    /*
     * Get the lock while we check if this TSD is new or not. Note that this
     * is the only place where Tcl_ThreadDataKey values are set. We use a
     * double-checked lock to try to avoid having to grab this lock a lot,
     * since it is on quite a few critical paths and will only get set once in
     * each location.
     */

    if (keyPtr->offset == 0) {
	Tcl_MutexLock(&tsdGlobal.mutex);
	if (keyPtr->offset == 0) {
	    /*
	     * The Tcl_ThreadDataKey hasn't been used yet. Make a new one.
	     */

	    keyPtr->offset = ++tsdGlobal.counter;
	}
	Tcl_MutexUnlock(&tsdGlobal.mutex);
    }

    /*
     * Check if this is the first time this Tcl_ThreadDataKey has been used
     * with the current thread. Note that we don't need to hold a lock when
     * doing this, as we are *definitely* the only point accessing this
     * tsdTablePtr right now; it's thread-local.
     */

    if (keyPtr->offset >= tsdTablePtr->allocated) {
	TSDTableGrow(tsdTablePtr, keyPtr->offset);
    }

    /*
     * Set the value in the Tcl thread-local variable.
     */

    tsdTablePtr->tablePtr[keyPtr->offset] = value;
}

/*
 *----------------------------------------------------------------------
 *
 * TclFinalizeThreadDataThread --
 *
 *	This procedure finalizes the data for a single thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The TSDTable is deleted/freed.
 *
 *----------------------------------------------------------------------
 */

void
TclFinalizeThreadDataThread(void)
{
    TSDTable *tsdTablePtr = TclpThreadGetGlobalTSD(tsdGlobal.key);

    if (tsdTablePtr != NULL) {
	TSDTableDelete(tsdTablePtr);
	TclpThreadSetGlobalTSD(tsdGlobal.key, NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TclInitializeThreadStorage --
 *
 *	This procedure initializes the TSD subsystem with per-platform code.
 *	This should be called before any Tcl threads are created.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocates a system TSD.
 *
 *----------------------------------------------------------------------
 */

void
TclInitThreadStorage(void)
{
    tsdGlobal.key = TclpThreadCreateKey();
}

/*
 *----------------------------------------------------------------------
 *
 * TclFinalizeThreadStorage --
 *
 *	This procedure cleans up the thread storage data key for all threads.
 *	IMPORTANT: All Tcl threads must be finalized before calling this!
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Releases the thread data key.
 *
 *----------------------------------------------------------------------
 */

void
TclFinalizeThreadStorage(void)
{
    TclpThreadDeleteKey(tsdGlobal.key);
    tsdGlobal.key = NULL;
}

#else /* !TCL_THREADS */
/*
 * Stub functions for non-threaded builds
 */

void
TclInitThreadStorage(void)
{
}

void
TclFinalizeThreadDataThread(void)
{
}

void
TclFinalizeThreadStorage(void)
{
}
#endif /* TCL_THREADS */

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
