/*
* 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.
*
* RCS: @(#) $Id: tclThreadStorage.c,v 1.15.2.2 2010/04/03 09:38:47 vasiljevic Exp $
*/
#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:
*/
|