diff options
Diffstat (limited to 'generic/tclThreadStorage.c')
-rw-r--r-- | generic/tclThreadStorage.c | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/generic/tclThreadStorage.c b/generic/tclThreadStorage.c new file mode 100644 index 0000000..f1df888 --- /dev/null +++ b/generic/tclThreadStorage.c @@ -0,0 +1,597 @@ +/* + * tclThreadStorage.c -- + * + * This file implements platform independent thread storage operations. + * + * Copyright (c) 2003-2004 by Joe Mistachkin + * + * 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) + +/* + * 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(Tcl_HashTable *tablePtr, + void *keyPtr); +static void FreeThreadStorageEntry(Tcl_HashEntry *hPtr); +static Tcl_HashTable * ThreadStorageGetHashTable(Tcl_ThreadId id); + +/* + * 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. + */ + +static Tcl_HashKeyType tclThreadStorageHashKeyType = { + TCL_HASH_KEY_TYPE_VERSION, /* version */ + TCL_HASH_KEY_SYSTEM_HASH | TCL_HASH_KEY_RANDOMIZE_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 1 + +/* + * 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 threadStorageHashTable; + +/* + * This is the next thread data key value to use. We increment this everytime + * we "allocate" one. It is initially set to 1 in TclInitThreadStorage. + */ + +static int nextThreadStorageKey = STORAGE_INVALID_KEY; + +/* + * This is the master thread storage cache. Per Kevin Kenny's idea, this + * prevents unnecessary lookups for threads that use a lot of thread storage. + */ + +static volatile ThreadStorage threadStorageCache[STORAGE_CACHE_SLOTS]; + +/* + *---------------------------------------------------------------------- + * + * 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. + * + *---------------------------------------------------------------------- + */ + +static Tcl_HashEntry * +AllocThreadStorageEntry( + 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 = keyPtr; + hPtr->clientData = NULL; + + 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. + * + *---------------------------------------------------------------------- + */ + +static void +FreeThreadStorageEntry( + Tcl_HashEntry *hPtr) /* Hash entry to free. */ +{ + TclpSysFree((char *) hPtr); +} + +/* + *---------------------------------------------------------------------- + * + * ThreadStorageGetHashTable -- + * + * This procedure returns a hash table pointer to be used for thread + * storage for the specified thread. + * + * 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. + * + * Thread safety: + * This function assumes that integer operations are safe (atomic) + * on all (currently) supported Tcl platforms. Hence there are + * places where shared integer arithmetic is done w/o protective locks. + * + *---------------------------------------------------------------------- + */ + +static Tcl_HashTable * +ThreadStorageGetHashTable( + Tcl_ThreadId id) /* Id of thread to get hash table for */ +{ + int index = PTR2UINT(id) % STORAGE_CACHE_SLOTS; + Tcl_HashEntry *hPtr; + int isNew; + Tcl_HashTable *hashTablePtr; + + /* + * 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. + * + * Thread safety: threadStorageCache is accessed w/o locks in order to + * avoid serialization of all threads at this hot-spot. It is safe to + * do this here because (threadStorageCache[index].id != id) test below + * should be atomic on all (currently) supported platforms and there + * are no devastatig side effects of the test. + * + * Note Valgrind users: this place will show up as a race-condition in + * helgrind-tool output. To silence this warnings, define VALGRIND + * symbol at compilation time. + */ + +#if !defined(VALGRIND) + hashTablePtr = threadStorageCache[index].hashTablePtr; + if (threadStorageCache[index].id != id) { + Tcl_MutexLock(&threadStorageLock); +#else + Tcl_MutexLock(&threadStorageLock); + hashTablePtr = threadStorageCache[index].hashTablePtr; + if (threadStorageCache[index].id != id) { +#endif + + /* + * It's not in the cache, so we look it up... + */ + + hPtr = Tcl_FindHashEntry(&threadStorageHashTable, (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 ThreadStorageGetHashTable!"); + } + Tcl_InitCustomHashTable(hashTablePtr, TCL_CUSTOM_TYPE_KEYS, + &tclThreadStorageHashKeyType); + + /* + * Add new thread storage hash table to the master hash table. + */ + + hPtr = Tcl_CreateHashEntry(&threadStorageHashTable, (char *) id, + &isNew); + + if (hPtr == NULL) { + Tcl_Panic("Tcl_CreateHashEntry failed from " + "ThreadStorageGetHashTable!"); + } + Tcl_SetHashValue(hPtr, hashTablePtr); + } + + /* + * Now, we put it in the cache since it is highly likely it will be + * needed again shortly. + */ + + threadStorageCache[index].id = id; + threadStorageCache[index].hashTablePtr = hashTablePtr; +#if !defined(VALGRIND) + Tcl_MutexUnlock(&threadStorageLock); + } +#else + } + Tcl_MutexUnlock(&threadStorageLock); +#endif + + return hashTablePtr; +} + +/* + *---------------------------------------------------------------------- + * + * TclInitThreadStorage -- + * + * Initializes the thread storage allocator. + * + * Results: + * None. + * + * Side effects: + * This procedure initializes the master hash table that maps thread ID + * onto the individual index tables that map thread data key to thread + * data. It also creates a cache that enables fast lookup of the thread + * data block array for a recently executing thread without using + * spinlocks. + * + * This procedure is called from an extremely early point in Tcl's + * initialization. In particular, it may not use ckalloc/ckfree because they + * may depend on thread-local storage (it uses TclpSysAlloc and TclpSysFree + * instead). It may not depend on synchronization primitives - but no threads + * other than the master thread have yet been launched. + * + *---------------------------------------------------------------------- + */ + +void +TclInitThreadStorage(void) +{ + Tcl_InitCustomHashTable(&threadStorageHashTable, TCL_CUSTOM_TYPE_KEYS, + &tclThreadStorageHashKeyType); + + /* + * We also initialize the cache. + */ + + memset((void*) &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; +} + +/* + *---------------------------------------------------------------------- + * + * TclpThreadDataKeyGet -- + * + * This procedure returns a pointer to a block of thread local storage. + * + * Results: + * A thread-specific pointer to the data structure, or NULL if the memory + * has not been assigned to this key for this thread. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void * +TclpThreadDataKeyGet( + Tcl_ThreadDataKey *keyPtr) /* Identifier for the data chunk, really + * (int**) */ +{ + Tcl_HashTable *hashTablePtr = + ThreadStorageGetHashTable(Tcl_GetCurrentThread()); + Tcl_HashEntry *hPtr = Tcl_FindHashEntry(hashTablePtr, (char *) keyPtr); + + if (hPtr == NULL) { + return NULL; + } + return Tcl_GetHashValue(hPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TclpThreadDataKeySet -- + * + * This procedure sets the pointer to a block of thread local storage. + * + * Results: + * None. + * + * Side effects: + * Sets up the thread so future calls to TclpThreadDataKeyGet with this + * key will return the data pointer. + * + *---------------------------------------------------------------------- + */ + +void +TclpThreadDataKeySet( + Tcl_ThreadDataKey *keyPtr, /* Identifier for the data chunk, really + * (pthread_key_t **) */ + void *data) /* Thread local storage */ +{ + Tcl_HashTable *hashTablePtr; + Tcl_HashEntry *hPtr; + int dummy; + + hashTablePtr = ThreadStorageGetHashTable(Tcl_GetCurrentThread()); + hPtr = Tcl_CreateHashEntry(hashTablePtr, (char *)keyPtr, &dummy); + + Tcl_SetHashValue(hPtr, data); +} + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizeThreadDataThread -- + * + * This procedure cleans up the thread storage hash table for the + * current thread. + * + * Results: + * None. + * + * Side effects: + * Frees all associated thread storage, all hash table entries for + * the thread's thread storage, and the hash table itself. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizeThreadDataThread(void) +{ + Tcl_ThreadId id = Tcl_GetCurrentThread(); + /* Id of the thread to finalize. */ + int index = PTR2UINT(id) % STORAGE_CACHE_SLOTS; + Tcl_HashEntry *hPtr; /* Hash entry for current thread in master + * table. */ + Tcl_HashTable* hashTablePtr;/* Pointer to the hash table holding TSD + * blocks for the current thread*/ + Tcl_HashSearch search; /* Search object to walk the TSD blocks in the + * designated thread */ + Tcl_HashEntry *hPtr2; /* Hash entry for a TSD block in the + * designated thread. */ + + Tcl_MutexLock(&threadStorageLock); + hPtr = Tcl_FindHashEntry(&threadStorageHashTable, (char*)id); + if (hPtr == NULL) { + hashTablePtr = NULL; + } else { + /* + * We found it, extract the hash table pointer. + */ + + hashTablePtr = Tcl_GetHashValue(hPtr); + Tcl_DeleteHashEntry(hPtr); + + /* + * Make sure cache entry for this thread is NULL. + */ + + 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; + } + } + Tcl_MutexUnlock(&threadStorageLock); + + /* + * The thread's hash table has been extracted and removed from the master + * hash table. Now clean up the thread. + */ + + if (hashTablePtr != NULL) { + /* + * Free all TSD + */ + + for (hPtr2 = Tcl_FirstHashEntry(hashTablePtr, &search); hPtr2 != NULL; + hPtr2 = Tcl_NextHashEntry(&search)) { + void *blockPtr = Tcl_GetHashValue(hPtr2); + + if (blockPtr != NULL) { + /* + * The block itself was allocated in Tcl_GetThreadData using + * ckalloc; use ckfree to dispose of it. + */ + + ckfree(blockPtr); + } + } + + /* + * Delete thread specific hash table and free the struct. + */ + + Tcl_DeleteHashTable(hashTablePtr); + TclpSysFree((char *) hashTablePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclFinalizeThreadStorage -- + * + * This procedure cleans up the master thread storage hash table, all + * thread specific hash tables, and the thread storage cache. + * + * Results: + * None. + * + * Side effects: + * The master thread storage hash table and thread storage cache are + * reset to their initial (empty) state. + * + *---------------------------------------------------------------------- + */ + +void +TclFinalizeThreadStorage(void) +{ + Tcl_HashSearch search; /* We need to hit every thread with this + * search. */ + Tcl_HashEntry *hPtr; /* Hash entry for current thread in master + * table. */ + Tcl_MutexLock(&threadStorageLock); + + /* + * 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(&threadStorageHashTable, &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(&threadStorageHashTable); + + /* + * Clear out the thread storage cache as well. + */ + + memset((void*) &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; + + Tcl_MutexUnlock(&threadStorageLock); +} + +#else /* !defined(TCL_THREADS) */ + +/* + * Stub functions for non-threaded builds + */ + +void +TclInitThreadStorage(void) +{ +} + +void +TclpFinalizeThreadDataThread(void) +{ +} + +void +TclFinalizeThreadStorage(void) +{ +} + +#endif /* defined(TCL_THREADS) && defined(USE_THREAD_STORAGE) */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |