Import Tcl 8.5.15 (as of svn r89086)
This commit is contained in:
597
generic/tclThreadStorage.c
Normal file
597
generic/tclThreadStorage.c
Normal file
@@ -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:
|
||||
*/
|
||||
Reference in New Issue
Block a user