diff options
Diffstat (limited to 'generic/tclThreadStorage.c')
| -rw-r--r-- | generic/tclThreadStorage.c | 373 | 
1 files changed, 373 insertions, 0 deletions
| diff --git a/generic/tclThreadStorage.c b/generic/tclThreadStorage.c new file mode 100644 index 0000000..755a461 --- /dev/null +++ b/generic/tclThreadStorage.c @@ -0,0 +1,373 @@ +/* + * 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 master collection of information about TSDs. This is shared across the + * whole process, and includes the mutex used to protect it. + */ + +static struct TSDMaster { +    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. */ +} tsdMaster = { NULL, 0, NULL }; + +/* + * The type of the data held per thread in a system TSD. + */ + +typedef struct TSDTable { +    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 TSDUnion { +    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 = TclpThreadGetMasterTSD(tsdMaster.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 = TclpThreadGetMasterTSD(tsdMaster.key); +    TSDUnion *keyPtr = (TSDUnion *) dataKeyPtr; + +    if (tsdTablePtr == NULL) { +	tsdTablePtr = TSDTableCreate(); +	TclpThreadSetMasterTSD(tsdMaster.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(&tsdMaster.mutex); +	if (keyPtr->offset == 0) { +	    /* +	     * The Tcl_ThreadDataKey hasn't been used yet. Make a new one. +	     */ + +	    keyPtr->offset = ++tsdMaster.counter; +	} +	Tcl_MutexUnlock(&tsdMaster.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 = TclpThreadGetMasterTSD(tsdMaster.key); + +    if (tsdTablePtr != NULL) { +	TSDTableDelete(tsdTablePtr); +	TclpThreadSetMasterTSD(tsdMaster.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) +{ +    tsdMaster.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(tsdMaster.key); +    tsdMaster.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: + */ | 
