598 lines
15 KiB
C
598 lines
15 KiB
C
/*
|
||
* 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:
|
||
*/
|