374 lines
8.6 KiB
C
374 lines
8.6 KiB
C
|
/*
|
|||
|
* 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:
|
|||
|
*/
|