summaryrefslogtreecommitdiffstats
path: root/generic/tclThreadStorage.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclThreadStorage.c')
-rw-r--r--generic/tclThreadStorage.c1105
1 files changed, 183 insertions, 922 deletions
diff --git a/generic/tclThreadStorage.c b/generic/tclThreadStorage.c
index 7eb66be..f24e334 100644
--- a/generic/tclThreadStorage.c
+++ b/generic/tclThreadStorage.c
@@ -1,610 +1,183 @@
/*
* tclThreadStorage.c --
*
- * This file implements platform independent thread storage operations.
+ * 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.
- *
- * RCS: @(#) $Id: tclThreadStorage.c,v 1.4 2004/06/24 09:05:46 dkf Exp $
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
-#if defined(TCL_THREADS) && defined(USE_THREAD_STORAGE)
-
-/*
- * This is the thread storage cache array and it's accompanying mutex.
- * The elements are pairs of thread Id and an associated hash table
- * pointer; the hash table being pointed to contains the thread storage
- * for it's associated thread. The purpose of this cache is to minimize
- * the number of hash table lookups in the master thread storage hash
- * table.
- */
-
-static Tcl_Mutex threadStorageLock;
-
-/*
- * This is the struct used for a thread storage cache slot. It contains
- * the owning thread Id and the associated hash table pointer.
- */
-
-typedef struct ThreadStorage {
- Tcl_ThreadId id; /* the owning thread id */
- Tcl_HashTable *hashTablePtr; /* the hash table for the thread */
-} ThreadStorage;
-
-/*
- * These are the prototypes for the custom hash table allocation
- * functions used by the thread storage subsystem.
- */
-
-static Tcl_HashEntry * AllocThreadStorageEntry _ANSI_ARGS_((
- Tcl_HashTable *tablePtr, void *keyPtr));
-static void FreeThreadStorageEntry _ANSI_ARGS_((
- Tcl_HashEntry *hPtr));
-
-/*
- * This is the hash key type for thread storage. We MUST use this in
- * combination with the new hash key type flag TCL_HASH_KEY_SYSTEM_HASH
- * because these hash tables MAY be used by the threaded memory allocator.
- */
-Tcl_HashKeyType tclThreadStorageHashKeyType = {
- TCL_HASH_KEY_TYPE_VERSION, /* version */
- TCL_HASH_KEY_SYSTEM_HASH, /* flags */
- NULL, /* hashKeyProc */
- NULL, /* compareKeysProc */
- AllocThreadStorageEntry, /* allocEntryProc */
- FreeThreadStorageEntry /* freeEntryProc */
-};
-
-/*
- * This is an invalid thread value.
- */
-
-#define STORAGE_INVALID_THREAD (Tcl_ThreadId)0
-
-/*
- * This is the value for an invalid thread storage key.
- */
-
-#define STORAGE_INVALID_KEY 0
-
-/*
- * This is the first valid key for use by external callers.
- * All the values below this are RESERVED for future use.
- */
-
-#define STORAGE_FIRST_KEY 101
-
-/*
- * This is the default number of thread storage cache slots.
- * This define may need to be fine tuned for maximum performance.
- */
-
-#define STORAGE_CACHE_SLOTS 97
-
-/*
- * This is the master thread storage hash table. It is keyed on
- * thread Id and contains values that are hash tables for each thread.
- * The thread specific hash tables contain the actual thread storage.
- */
-
-static Tcl_HashTable *threadStorageHashTablePtr = NULL;
-
-/*
- * This is the next thread data key value to use. We increment this
- * everytime we "allocate" one. It is initially set to 1 in
- * TclThreadStorageInit.
- */
-
-static int nextThreadStorageKey = STORAGE_INVALID_KEY;
+#ifdef TCL_THREADS
+#include <signal.h>
/*
- * Have we initialized the thread storage mutex yet?
+ * 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.
*/
-static int initThreadStorage = 0;
-
/*
- * This is the master thread storage cache. Per kennykb's idea, this
- * prevents unnecessary lookups for threads that use a lot of thread
- * storage.
+ * The master collection of information about TSDs. This is shared across the
+ * whole process, and includes the mutex used to protect it.
*/
-static volatile ThreadStorage threadStorageCache[STORAGE_CACHE_SLOTS];
-
-/*
- *----------------------------------------------------------------------
- *
- * TclThreadStorageLockInit
- *
- * This procedure is used to initialize the lock that serializes
- * creation of thread storage.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The master lock is acquired and possibly initialized for the
- * first time.
- *
- *----------------------------------------------------------------------
- */
+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 };
-void
-TclThreadStorageLockInit()
-{
- if (!initThreadStorage) {
- /*
- * Mutexes in Tcl are self initializing, and we are taking
- * advantage of that fact since this file cannot contain
- * platform specific calls.
- */
- initThreadStorage = 1;
- }
-}
-
/*
- *----------------------------------------------------------------------
- *
- * TclThreadStorageLock
- *
- * This procedure is used to grab a lock that serializes creation
- * of thread storage.
- *
- * This lock must be different than the initLock because the
- * initLock is held during creation of syncronization objects.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Acquire the thread storage mutex.
- *
- *----------------------------------------------------------------------
+ * The type of the data held per thread in a system TSD.
*/
-void
-TclThreadStorageLock()
-{
- TclThreadStorageLockInit();
- Tcl_MutexLock(&threadStorageLock);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TclThreadStorageUnlock
- *
- * This procedure is used to release a lock that serializes creation
- * of thread storage.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Release the thread storage mutex.
- *
- *----------------------------------------------------------------------
- */
+typedef struct TSDTable {
+ ClientData *tablePtr; /* The table of Tcl TSDs. */
+ sig_atomic_t allocated; /* The size of the table in the current
+ * thread. */
+} TSDTable;
-void
-TclThreadStorageUnlock()
-{
- Tcl_MutexUnlock(&threadStorageLock);
-}
-
/*
- *----------------------------------------------------------------------
- *
- * AllocThreadStorageEntry --
- *
- * Allocate space for a Tcl_HashEntry using TclpSysAlloc (not
- * ckalloc). We do this because the threaded memory allocator MAY
- * use the thread storage hash tables.
- *
- * Results:
- * The return value is a pointer to the created entry.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
+ * The actual type of Tcl_ThreadDataKey.
*/
-static Tcl_HashEntry *
-AllocThreadStorageEntry(tablePtr, keyPtr)
- Tcl_HashTable *tablePtr; /* Hash table. */
- void *keyPtr; /* Key to store in the hash table entry. */
-{
- Tcl_HashEntry *hPtr;
-
- hPtr = (Tcl_HashEntry *)TclpSysAlloc(sizeof(Tcl_HashEntry), 0);
- hPtr->key.oneWordValue = (char *)keyPtr;
+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;
- return hPtr;
-}
-
/*
- *----------------------------------------------------------------------
- *
- * FreeThreadStorageEntry --
- *
- * Frees space for a Tcl_HashEntry using TclpSysFree (not ckfree).
- * We do this because the threaded memory allocator MAY use the
- * thread storage hash tables.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
+ * Forward declarations of functions in this file.
*/
-static void
-FreeThreadStorageEntry(hPtr)
- Tcl_HashEntry *hPtr; /* Hash entry to free. */
-{
- TclpSysFree((char *)hPtr);
-}
+static TSDTable * TSDTableCreate(void);
+static void TSDTableDelete(TSDTable *tsdTablePtr);
+static void TSDTableGrow(TSDTable *tsdTablePtr,
+ sig_atomic_t atLeast);
/*
- *----------------------------------------------------------------------
- *
- * TclThreadStoragePrint --
- *
- * This procedure prints out the contents of the master thread
- * storage hash table, the thread storage cache, and the next key
- * value to the specified file.
- *
- * This assumes that thread storage lock is held.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The thread storage lock is acquired and released.
- *
- *----------------------------------------------------------------------
+ * Allocator and deallocator for a TSDTable structure.
*/
-void
-TclThreadStoragePrint(outFile, flags)
- FILE *outFile; /* The file to print the information to. */
- int flags; /* Reserved for future use. */
+static TSDTable *
+TSDTableCreate(void)
{
- Tcl_HashEntry *hPtr;
- Tcl_HashSearch search;
- int header, index;
-
- if (threadStorageHashTablePtr != NULL) {
- hPtr = Tcl_FirstHashEntry(threadStorageHashTablePtr, &search);
+ TSDTable *tsdTablePtr;
+ sig_atomic_t i;
- if (hPtr != NULL) {
- fprintf(outFile, "master thread storage hash table:\n");
- for (; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- fprintf(outFile,
- "master entry ptr %p, thread %p, thread table ptr %p\n",
- hPtr, Tcl_GetHashKey(threadStorageHashTablePtr, hPtr),
- Tcl_GetHashValue(hPtr));
- }
- } else {
- fprintf(outFile,
- "master thread storage hash table has no entries\n");
- }
- } else {
- fprintf(outFile,
- "master thread storage hash table not initialized\n");
+ tsdTablePtr = TclpSysAlloc(sizeof(TSDTable), 0);
+ if (tsdTablePtr == NULL) {
+ Tcl_Panic("unable to allocate TSDTable");
}
- header = 0; /* we have not output the header yet. */
- for (index = 0; index < STORAGE_CACHE_SLOTS; index++) {
- if (threadStorageCache[index].id != STORAGE_INVALID_THREAD) {
- if (!header) {
- fprintf(outFile, "thread storage cache (%d total slots):\n",
- STORAGE_CACHE_SLOTS);
- header = 1;
- }
-
- fprintf(outFile, "slot %d, thread %p, thread table ptr %p\n",
- index, threadStorageCache[index].id,
- threadStorageCache[index].hashTablePtr);
-#ifdef VERBOSE_THREAD_STORAGE_DEBUGGING
- /*
- * Currently not enabled by default due to Tcl_HashStats
- * use of ckalloc and ckfree. Please note that this can
- * produce a LOT of output.
- */
- if (threadStorageCache[index].hashTablePtr != NULL) {
- CONST char *stats =
- Tcl_HashStats(threadStorageCache[index].hashTablePtr);
- if (stats != NULL) {
- fprintf(outFile, "%s\n", stats);
- ckfree((void *)stats);
- } else {
- fprintf(outFile,
- "could not get table statistics for slot %d\n",
- index);
- }
- }
-#endif
- } else {
- /* fprintf(outFile, "cache slot %d not used\n", index); */
- }
+ tsdTablePtr->allocated = 8;
+ tsdTablePtr->tablePtr =
+ TclpSysAlloc(sizeof(void *) * tsdTablePtr->allocated, 0);
+ if (tsdTablePtr->tablePtr == NULL) {
+ Tcl_Panic("unable to allocate TSDTable");
}
- if (!header) {
- fprintf(outFile, "thread storage cache is empty (%d total slots)\n",
- STORAGE_CACHE_SLOTS);
- header = 1;
+ for (i = 0; i < tsdTablePtr->allocated; ++i) {
+ tsdTablePtr->tablePtr[i] = NULL;
}
- /*
- * Show the next data key value.
- */
-
- fprintf(outFile, "next data key value is: %d\n", nextThreadStorageKey);
+ return tsdTablePtr;
}
-
-/*
- *----------------------------------------------------------------------
- *
- * TclThreadStorageGetHashTable --
- *
- * This procedure returns a hash table pointer to be used for thread
- * storage for the specified thread.
- *
- * This assumes that thread storage lock is held.
- *
- * Results:
- * A hash table pointer for the specified thread, or NULL
- * if the hash table has not been created yet.
- *
- * Side effects:
- * May change an entry in the master thread storage cache to point
- * to the specified thread and it's associated hash table.
- *
- *----------------------------------------------------------------------
- */
-Tcl_HashTable *
-TclThreadStorageGetHashTable(id)
- Tcl_ThreadId id; /* Id of thread to get hash table for */
+static void
+TSDTableDelete(
+ TSDTable *tsdTablePtr)
{
- int index = (unsigned int)id % STORAGE_CACHE_SLOTS;
- Tcl_HashEntry *hPtr;
- int new;
-
- /*
- * It's important that we pick up the hash table pointer BEFORE
- * comparing thread Id in case another thread is in the critical
- * region changing things out from under you.
- */
-
- Tcl_HashTable *hashTablePtr = threadStorageCache[index].hashTablePtr;
-
- if (threadStorageCache[index].id != id) {
- TclThreadStorageLock();
-
- /*
- * Make sure the master hash table is initialized.
- */
-
- TclThreadStorageInit(STORAGE_INVALID_THREAD, NULL);
-
- if (threadStorageHashTablePtr != NULL) {
- /*
- * It's not in the cache, so we look it up...
- */
-
- hPtr = Tcl_FindHashEntry(threadStorageHashTablePtr, (char *)id);
-
- if (hPtr != NULL) {
- /*
- * We found it, extract the hash table pointer.
- */
- hashTablePtr = Tcl_GetHashValue(hPtr);
- } else {
- /*
- * The thread specific hash table is not found.
- */
- hashTablePtr = NULL;
- }
-
- if (hashTablePtr == NULL) {
- hashTablePtr = (Tcl_HashTable *)
- TclpSysAlloc(sizeof(Tcl_HashTable), 0);
-
- if (hashTablePtr == NULL) {
- Tcl_Panic("could not allocate thread specific hash "
- "table, TclpSysAlloc failed from "
- "TclThreadStorageGetHashTable!");
- }
- Tcl_InitCustomHashTable(hashTablePtr, TCL_CUSTOM_TYPE_KEYS,
- &tclThreadStorageHashKeyType);
-
- /*
- * Add new thread storage hash table to the master
- * hash table.
- */
-
- hPtr = Tcl_CreateHashEntry(threadStorageHashTablePtr,
- (char *)id, &new);
-
- if (hPtr == NULL) {
- Tcl_Panic("Tcl_CreateHashEntry failed from "
- "TclThreadStorageInit!");
- }
- Tcl_SetHashValue(hPtr, hashTablePtr);
- }
+ sig_atomic_t i;
+ for (i=0 ; i<tsdTablePtr->allocated ; i++) {
+ if (tsdTablePtr->tablePtr[i] != NULL) {
/*
- * Now, we put it in the cache since it is highly likely
- * it will be needed again shortly.
+ * These values were allocated in Tcl_GetThreadData in tclThread.c
+ * and must now be deallocated or they will leak.
*/
- threadStorageCache[index].id = id;
- threadStorageCache[index].hashTablePtr = hashTablePtr;
- } else {
- /*
- * We cannot look it up, the master hash table has not
- * been initialized.
- */
- hashTablePtr = NULL;
+ ckfree(tsdTablePtr->tablePtr[i]);
}
- TclThreadStorageUnlock();
}
- return hashTablePtr;
+ TclpSysFree(tsdTablePtr->tablePtr);
+ TclpSysFree(tsdTablePtr);
}
/*
*----------------------------------------------------------------------
*
- * TclThreadStorageInit --
- *
- * This procedure initializes a thread specific hash table for the
- * current thread. It may also initialize the master hash table which
- * stores all the thread specific hash tables.
+ * TSDTableGrow --
*
- * This assumes that thread storage lock is held.
- *
- * Results:
- * A hash table pointer for the specified thread, or NULL if we are
- * be called to initialize the master hash table only.
- *
- * Side effects:
- * The thread specific hash table may be initialized and added to the
- * master hash table.
- *
- *----------------------------------------------------------------------
- */
-
-Tcl_HashTable *
-TclThreadStorageInit(id, reserved)
- Tcl_ThreadId id; /* Id of thread to get hash table for */
- void *reserved; /* reserved for future use */
-{
-#if 0 /* #ifdef TCL_THREAD_STORAGE_DEBUG */
- TclThreadStoragePrint(stderr, 0);
-#endif
-
- if (threadStorageHashTablePtr == NULL) {
- /*
- * Looks like we haven't created the outer hash table yet we
- * can just do that now.
- */
-
- threadStorageHashTablePtr = (Tcl_HashTable *)
- TclpSysAlloc(sizeof(Tcl_HashTable), 0);
- if (threadStorageHashTablePtr == NULL) {
- Tcl_Panic("could not allocate master thread storage hash table, "
- "TclpSysAlloc failed from TclThreadStorageInit!");
- }
- Tcl_InitCustomHashTable(threadStorageHashTablePtr,
- TCL_CUSTOM_TYPE_KEYS, &tclThreadStorageHashKeyType);
-
- /*
- * We also initialize the cache.
- */
-
- memset((ThreadStorage *)&threadStorageCache, 0,
- sizeof(ThreadStorage) * STORAGE_CACHE_SLOTS);
-
- /*
- * Now, we set the first value to be used for a thread data key.
- */
-
- nextThreadStorageKey = STORAGE_FIRST_KEY;
- }
-
- return NULL;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TclThreadStorageDataKeyInit --
- *
- * This procedure initializes a thread specific data block key.
- * Each thread has table of pointers to thread specific data.
- * all threads agree on which table entry is used by each module.
- * this is remembered in a "data key", that is just an index into
- * this table. To allow self initialization, the interface
- * passes a pointer to this key and the first thread to use
- * the key fills in the pointer to the key. The key should be
- * a process-wide static.
+ * This procedure makes the passed TSDTable grow to fit the atLeast
+ * value.
*
* Results:
* None.
*
* Side effects:
- * Will allocate memory the first time this process calls for
- * this key. In this case it modifies its argument
- * to hold the pointer to information about the key.
+ * The table is enlarged.
*
*----------------------------------------------------------------------
*/
-void
-TclThreadStorageDataKeyInit(keyPtr)
- Tcl_ThreadDataKey *keyPtr; /* Identifier for the data chunk,
- * really (int **) */
+static void
+TSDTableGrow(
+ TSDTable *tsdTablePtr,
+ sig_atomic_t atLeast)
{
- int *indexPtr;
- int newKey;
-
- if (*keyPtr == NULL) {
- indexPtr = (int *)TclpSysAlloc(sizeof(int), 0);
- if (indexPtr == NULL) {
- Tcl_Panic("TclpSysAlloc failed from TclThreadStorageDataKeyInit!");
- }
+ sig_atomic_t newAllocated = tsdTablePtr->allocated * 2;
+ ClientData *newTablePtr;
+ sig_atomic_t i;
- /*
- * We must call this now to make sure that
- * nextThreadStorageKey has a well defined value.
- */
-
- TclThreadStorageLock();
-
- /*
- * Make sure the master hash table is initialized.
- */
-
- TclThreadStorageInit(STORAGE_INVALID_THREAD, NULL);
-
- /*
- * These data key values are sequentially assigned and we must
- * use the storage lock to prevent serious problems here.
- * Also note that the caller should NOT make any assumptions
- * about the provided values. In particular, we may need to
- * reserve some values in the future.
- */
+ if (newAllocated <= atLeast) {
+ newAllocated = atLeast + 10;
+ }
- newKey = nextThreadStorageKey++;
- TclThreadStorageUnlock();
+ newTablePtr = TclpSysRealloc(tsdTablePtr->tablePtr,
+ sizeof(ClientData) * newAllocated);
+ if (newTablePtr == NULL) {
+ Tcl_Panic("unable to reallocate TSDTable");
+ }
- *indexPtr = newKey;
- *keyPtr = (Tcl_ThreadDataKey)indexPtr;
- TclRememberDataKey(keyPtr);
+ for (i = tsdTablePtr->allocated; i < newAllocated; ++i) {
+ newTablePtr[i] = NULL;
}
+
+ tsdTablePtr->allocated = newAllocated;
+ tsdTablePtr->tablePtr = newTablePtr;
}
/*
*----------------------------------------------------------------------
*
- * TclThreadStorageDataKeyGet --
+ * TclThreadStorageKeyGet --
*
- * This procedure returns a pointer to a block of thread local storage.
+ * This procedure gets the value associated with the passed key.
*
* Results:
- * A thread-specific pointer to the data structure, or NULL
- * if the memory has not been assigned to this key for this thread.
+ * A pointer value associated with the Tcl_ThreadDataKey or NULL.
*
* Side effects:
* None.
@@ -613,500 +186,188 @@ TclThreadStorageDataKeyInit(keyPtr)
*/
void *
-TclThreadStorageDataKeyGet(keyPtr)
- Tcl_ThreadDataKey *keyPtr; /* Identifier for the data chunk,
- * really (int **) */
+TclThreadStorageKeyGet(
+ Tcl_ThreadDataKey *dataKeyPtr)
{
- int *indexPtr = *(int **)keyPtr;
-
- if (indexPtr == NULL) {
- return NULL;
- } else {
- Tcl_HashTable *hashTablePtr =
- TclThreadStorageGetHashTable(Tcl_GetCurrentThread());
- Tcl_HashEntry *hPtr;
-
- if (hashTablePtr == NULL) {
- Tcl_Panic("TclThreadStorageGetHashTable failed from "
- "TclThreadStorageDataKeyGet!");
- }
-
- hPtr = Tcl_FindHashEntry(hashTablePtr, (char *)*indexPtr);
-
- if (hPtr == NULL) {
- return NULL;
- }
- return (void *)Tcl_GetHashValue(hPtr);
+ 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;
}
/*
*----------------------------------------------------------------------
*
- * TclThreadStorageDataKeySet --
- *
- * This procedure sets the pointer to a block of thread local storage.
+ * TclThreadStorageKeySet --
*
+ * This procedure set an association of value with the key passed. The
+ * associated value may be retrieved with TclThreadDataKeyGet().
+ *
* Results:
* None.
*
* Side effects:
- * Sets up the thread so future calls to TclThreadStorageDataKeyGet
- * with this key will return the data pointer.
+ * The thread-specific table may be created or reallocated.
*
*----------------------------------------------------------------------
*/
void
-TclThreadStorageDataKeySet(keyPtr, data)
- Tcl_ThreadDataKey *keyPtr; /* Identifier for the data chunk,
- * really (pthread_key_t **) */
- void *data; /* Thread local storage */
+TclThreadStorageKeySet(
+ Tcl_ThreadDataKey *dataKeyPtr,
+ void *value)
{
- int *indexPtr = *(int **)keyPtr;
- Tcl_HashTable *hashTablePtr;
- Tcl_HashEntry *hPtr;
+ TSDTable *tsdTablePtr = TclpThreadGetMasterTSD(tsdMaster.key);
+ TSDUnion *keyPtr = (TSDUnion *) dataKeyPtr;
- hashTablePtr = TclThreadStorageGetHashTable(Tcl_GetCurrentThread());
- if (hashTablePtr == NULL) {
- Tcl_Panic("TclThreadStorageGetHashTable failed from "
- "TclThreadStorageDataKeySet!");
+ if (tsdTablePtr == NULL) {
+ tsdTablePtr = TSDTableCreate();
+ TclpThreadSetMasterTSD(tsdMaster.key, tsdTablePtr);
}
- hPtr = Tcl_FindHashEntry(hashTablePtr, (char *)*indexPtr);
-
/*
- * Does the item need to be created?
+ * 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 (hPtr == NULL) {
- int new;
- hPtr = Tcl_CreateHashEntry(hashTablePtr, (char *)*indexPtr, &new);
- if (hPtr == NULL) {
- Tcl_Panic("could not create hash entry value from "
- "TclThreadStorageDataKeySet");
- }
- }
-
- Tcl_SetHashValue(hPtr, data);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TclFinalizeThreadStorageThread --
- *
- * This procedure cleans up the thread storage hash table for the
- * specified thread.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-void
-TclFinalizeThreadStorageThread(id)
- Tcl_ThreadId id; /* Id of the thread to finalize */
-{
- int index = (unsigned int)id % STORAGE_CACHE_SLOTS;
- Tcl_HashTable *hashTablePtr; /* Hash table for current thread */
- Tcl_HashEntry *hPtr; /* Hash entry for current thread in master
- * table */
-
- TclThreadStorageLock();
-
- if (threadStorageHashTablePtr != NULL) {
- hPtr = Tcl_FindHashEntry(threadStorageHashTablePtr, (char *)id);
-
- if (hPtr != NULL) {
+ if (keyPtr->offset == 0) {
+ Tcl_MutexLock(&tsdMaster.mutex);
+ if (keyPtr->offset == 0) {
/*
- * We found it, extract the hash table pointer.
+ * The Tcl_ThreadDataKey hasn't been used yet. Make a new one.
*/
- hashTablePtr = Tcl_GetHashValue(hPtr);
-
- if (hashTablePtr != NULL) {
- /*
- * Delete thread specific hash table and free the
- * struct.
- */
-
- Tcl_DeleteHashTable(hashTablePtr);
- TclpSysFree((char *)hashTablePtr);
- }
-
- /*
- * Delete thread specific entry from master hash table.
- */
-
- Tcl_DeleteHashEntry(hPtr);
+ keyPtr->offset = ++tsdMaster.counter;
}
+ Tcl_MutexUnlock(&tsdMaster.mutex);
}
/*
- * Make sure cache entry for this thread is NULL.
+ * 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 (threadStorageCache[index].id == id) {
- /*
- * We do not step on another thread's cache entry. This is
- * especially important if we are creating and exiting a lot
- * of threads.
- */
-
- threadStorageCache[index].id = STORAGE_INVALID_THREAD;
- threadStorageCache[index].hashTablePtr = NULL;
+ if (keyPtr->offset >= tsdTablePtr->allocated) {
+ TSDTableGrow(tsdTablePtr, keyPtr->offset);
}
- TclThreadStorageUnlock();
+ /*
+ * Set the value in the Tcl thread-local variable.
+ */
+
+ tsdTablePtr->tablePtr[keyPtr->offset] = value;
}
/*
*----------------------------------------------------------------------
*
- * TclFinalizeThreadStorage --
+ * TclFinalizeThreadDataThread --
*
- * This procedure cleans up the master thread storage hash table,
- * all thread specific hash tables, and the thread storage cache.
+ * This procedure finalizes the data for a single thread.
*
* Results:
* None.
*
* Side effects:
- * The master thread storage hash table and thread storage cache are
- * reset to their initial (empty) state.
+ * The TSDTable is deleted/freed.
*
*----------------------------------------------------------------------
*/
-void
-TclFinalizeThreadStorage()
+void
+TclFinalizeThreadDataThread(void)
{
- TclThreadStorageLock();
-
- if (threadStorageHashTablePtr != NULL) {
- Tcl_HashSearch search; /* We need to hit every thread with
- * this search. */
- Tcl_HashEntry *hPtr; /* Hash entry for current thread in
- * master table. */
-
- /*
- * We are going to delete the hash table for every thread now.
- * This hash table should be empty at this point, except for
- * one entry for the current thread.
- */
-
- for (hPtr = Tcl_FirstHashEntry(threadStorageHashTablePtr, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- Tcl_HashTable *hashTablePtr = Tcl_GetHashValue(hPtr);
-
- if (hashTablePtr != NULL) {
- /*
- * Delete thread specific hash table for the thread in
- * question and free the struct.
- */
-
- Tcl_DeleteHashTable(hashTablePtr);
- TclpSysFree((char *)hashTablePtr);
- }
-
- /*
- * Delete thread specific entry from master hash table.
- */
-
- Tcl_SetHashValue(hPtr, NULL);
- }
-
- Tcl_DeleteHashTable(threadStorageHashTablePtr);
- TclpSysFree((char *)threadStorageHashTablePtr);
-
- /*
- * Reset this so that next time around we know it's not valid.
- */
+ TSDTable *tsdTablePtr = TclpThreadGetMasterTSD(tsdMaster.key);
- threadStorageHashTablePtr = NULL;
+ if (tsdTablePtr != NULL) {
+ TSDTableDelete(tsdTablePtr);
+ TclpThreadSetMasterTSD(tsdMaster.key, NULL);
}
-
- /*
- * Clear out the thread storage cache as well.
- */
-
- memset((ThreadStorage *)&threadStorageCache, 0,
- sizeof(ThreadStorage) * STORAGE_CACHE_SLOTS);
-
- /*
- * Reset this to zero, it will be set to STORAGE_FIRST_KEY if the
- * thread storage subsystem gets reinitialized
- */
-
- nextThreadStorageKey = STORAGE_INVALID_KEY;
-
- TclThreadStorageUnlock();
}
/*
*----------------------------------------------------------------------
*
- * TclFinalizeThreadStorageData --
+ * TclInitializeThreadStorage --
*
- * This procedure cleans up the thread-local storage. This is
- * called once for each thread.
+ * This procedure initializes the TSD subsystem with per-platform code.
+ * This should be called before any Tcl threads are created.
*
* Results:
* None.
*
* Side effects:
- * Frees up the memory.
+ * Allocates a system TSD.
*
*----------------------------------------------------------------------
*/
void
-TclFinalizeThreadStorageData(keyPtr)
- Tcl_ThreadDataKey *keyPtr;
+TclInitThreadStorage(void)
{
- if (*keyPtr != NULL) {
- Tcl_ThreadId id = Tcl_GetCurrentThread();
- Tcl_HashTable *hashTablePtr; /* Hash table for current thread */
- Tcl_HashEntry *hPtr; /* Hash entry for data key in current
- * thread. */
- int *indexPtr = *(int **)keyPtr;
-
- hashTablePtr = TclThreadStorageGetHashTable(id);
- if (hashTablePtr == NULL) {
- Tcl_Panic("TclThreadStorageGetHashTable failed from "
- "TclFinalizeThreadStorageData!");
- }
-
- hPtr = Tcl_FindHashEntry(hashTablePtr, (char *)*indexPtr);
- if (hPtr != NULL) {
- void *result = Tcl_GetHashValue(hPtr);
-
- if (result != NULL) {
- /*
- * This must be ckfree because tclThread.c allocates
- * these using ckalloc.
- */
- ckfree((char *)result);
- }
-
- Tcl_SetHashValue(hPtr, NULL);
- }
- }
+ tsdMaster.key = TclpThreadCreateKey();
}
/*
*----------------------------------------------------------------------
*
- * TclFinalizeThreadStorageDataKey --
- *
- * This procedure is invoked to clean up one key. This is a
- * process-wide storage identifier. The thread finalization code
- * cleans up the thread local storage itself.
+ * TclFinalizeThreadStorage --
*
- * This assumes the master lock is held.
+ * 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:
- * The key is deallocated.
+ * Releases the thread data key.
*
*----------------------------------------------------------------------
*/
void
-TclFinalizeThreadStorageDataKey(keyPtr)
- Tcl_ThreadDataKey *keyPtr;
+TclFinalizeThreadStorage(void)
{
- int *indexPtr;
- Tcl_HashTable *hashTablePtr;/* Hash table for current thread */
- Tcl_HashSearch search; /* Need to hit every thread with this search */
- Tcl_HashEntry *hPtr; /* Hash entry for current thread in master
- * table. */
- Tcl_HashEntry *hDataPtr; /* Hash entry for data key in current thread */
-
- if (*keyPtr != NULL) {
- indexPtr = *(int **)keyPtr;
-
- TclThreadStorageLock();
-
- if (threadStorageHashTablePtr != NULL) {
- /*
- * We are going to delete the specified data key entry
- * from every thread.
- */
-
- for (hPtr = Tcl_FirstHashEntry(threadStorageHashTablePtr, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
-
- /*
- * Get the hash table corresponding to the thread in question.
- */
- hashTablePtr = Tcl_GetHashValue(hPtr);
-
- if (hashTablePtr != NULL) {
- /*
- * Now find the entry for the specified data key.
- */
- hDataPtr = Tcl_FindHashEntry(hashTablePtr,
- (char *)*indexPtr);
-
- if (hDataPtr != NULL) {
- /*
- * Delete the data key for this thread.
- */
- Tcl_DeleteHashEntry(hDataPtr);
- }
- }
- }
- }
-
- TclThreadStorageUnlock();
-
- TclpSysFree((char *)indexPtr);
- *keyPtr = NULL;
- }
-}
-
-#else /* !defined(TCL_THREADS) || !defined(USE_THREAD_STORAGE) */
-
-static void ThreadStoragePanic _ANSI_ARGS_((CONST char *message));
-
-/*
- *----------------------------------------------------------------------
- *
- * ThreadStoragePanic --
- *
- * Panic if Tcl was compiled without TCL_THREADS or without
- * USE_THREAD_STORAGE and a thread storage function has been
- * called.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-static void ThreadStoragePanic(message)
- CONST char *message; /* currently ignored */
-{
-#ifdef TCL_THREADS
-# ifdef USE_THREAD_STORAGE
- /*
- * Do nothing, everything is OK. However, this should never happen
- * because this function only gets called by the dummy thread
- * storage functions (used when one or both of these DEFINES are
- * not present).
- */
-# else
- Tcl_Panic("Tcl was not compiled with thread storage enabled.");
-# endif /* USE_THREAD_STORAGE */
-#else
- Tcl_Panic("Tcl was not compiled with threads enabled.");
-#endif /* TCL_THREADS */
+ TclpThreadDeleteKey(tsdMaster.key);
+ tsdMaster.key = NULL;
}
+#else /* !TCL_THREADS */
/*
- * Stub functions that just call ThreadStoragePanic.
+ * Stub functions for non-threaded builds
*/
void
-TclThreadStorageLockInit()
-{
- ThreadStoragePanic(NULL);
-}
-
-void
-TclThreadStorageLock()
-{
- ThreadStoragePanic(NULL);
-}
-
-void
-TclThreadStorageUnlock()
-{
- ThreadStoragePanic(NULL);
-}
-
-void
-TclThreadStoragePrint(outFile, flags)
- FILE *outFile;
- int flags;
-{
- ThreadStoragePanic(NULL);
-}
-
-Tcl_HashTable *
-TclThreadStorageGetHashTable(id)
- Tcl_ThreadId id;
-{
- ThreadStoragePanic(NULL);
- return NULL;
-}
-
-Tcl_HashTable *
-TclThreadStorageInit(id, reserved)
- Tcl_ThreadId id;
- void *reserved;
-{
- ThreadStoragePanic(NULL);
- return NULL;
-}
-
-void
-TclThreadStorageDataKeyInit(keyPtr)
- Tcl_ThreadDataKey *keyPtr;
-{
- ThreadStoragePanic(NULL);
-}
-
-void *
-TclThreadStorageDataKeyGet(keyPtr)
- Tcl_ThreadDataKey *keyPtr;
-{
- ThreadStoragePanic(NULL);
- return NULL;
-}
-
-void
-TclThreadStorageDataKeySet(keyPtr, data)
- Tcl_ThreadDataKey *keyPtr;
- void *data;
-{
- ThreadStoragePanic(NULL);
-}
-
-void
-TclFinalizeThreadStorageThread(id)
- Tcl_ThreadId id;
-{
- ThreadStoragePanic(NULL);
-}
-
-void
-TclFinalizeThreadStorage()
+TclInitThreadStorage(void)
{
- ThreadStoragePanic(NULL);
}
void
-TclFinalizeThreadStorageData(keyPtr)
- Tcl_ThreadDataKey *keyPtr;
+TclFinalizeThreadDataThread(void)
{
- ThreadStoragePanic(NULL);
}
void
-TclFinalizeThreadStorageDataKey(keyPtr)
- Tcl_ThreadDataKey *keyPtr;
+TclFinalizeThreadStorage(void)
{
- ThreadStoragePanic(NULL);
}
-
-#endif /* defined(TCL_THREADS) && defined(USE_THREAD_STORAGE) */
+#endif /* TCL_THREADS */
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */