summaryrefslogtreecommitdiffstats
path: root/generic/tclThreadStorage.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclThreadStorage.c')
-rw-r--r--generic/tclThreadStorage.c597
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:
+ */