/* * tclUnixThrd.c -- * * This file implements the UNIX-specific thread support. * * Copyright (c) 1991-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: @(#) tclUnixThrd.c 1.18 98/02/19 14:24:12 */ #include "tclInt.h" #include "tclPort.h" #ifdef TCL_THREADS #include "pthread.h" typedef struct ThreadSpecificData { char nabuf[16]; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * masterLock is used to serialize creation of mutexes, condition * variables, and thread local storage. * This is the only place that can count on the ability to statically * initialize the mutex. */ static pthread_mutex_t masterLock = PTHREAD_MUTEX_INITIALIZER; /* * initLock is used to serialize initialization and finalization * of Tcl. It cannot use any dyamically allocated storage. */ static pthread_mutex_t initLock = PTHREAD_MUTEX_INITIALIZER; /* * allocLock is used by Tcl's version of malloc for synchronization. * For obvious reasons, cannot use any dyamically allocated storage. */ static pthread_mutex_t allocLock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t *allocLockPtr = &allocLock; /* * These are for the critical sections inside this file. */ #define MASTER_LOCK pthread_mutex_lock(&masterLock) #define MASTER_UNLOCK pthread_mutex_unlock(&masterLock) #endif /* TCL_THREADS */ /* *---------------------------------------------------------------------- * * TclpThreadCreate -- * * This procedure creates a new thread. * * Results: * TCL_OK if the thread could be created. The thread ID is * returned in a parameter. * * Side effects: * A new thread is created. * *---------------------------------------------------------------------- */ int TclpThreadCreate(idPtr, proc, clientData, stackSize, flags) Tcl_ThreadId *idPtr; /* Return, the ID of the thread */ Tcl_ThreadCreateProc proc; /* Main() function of the thread */ ClientData clientData; /* The one argument to Main() */ int stackSize; /* Size of stack for the new thread */ int flags; /* Flags controlling behaviour of * the new thread */ { #ifdef TCL_THREADS pthread_attr_t attr; pthread_t theThread; int result; pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); #ifdef HAVE_PTHREAD_ATTR_SETSTACKSIZE if (stackSize != TCL_THREAD_STACK_DEFAULT) { pthread_attr_setstacksize(&attr, (size_t) stackSize); #ifdef TCL_THREAD_STACK_MIN } else { /* * Certain systems define a thread stack size that by default is * too small for many operations. The user has the option of * defining TCL_THREAD_STACK_MIN to a value large enough to work * for their needs. This would look like (for 128K min stack): * make MEM_DEBUG_FLAGS=-DTCL_THREAD_STACK_MIN=131072L * * This solution is not optimal, as we should allow the user to * specify a size at runtime, but we don't want to slow this function * down, and that would still leave the main thread at the default. */ size_t size; result = pthread_attr_getstacksize(&attr, &size); if (!result && (size < TCL_THREAD_STACK_MIN)) { pthread_attr_setstacksize(&attr, (size_t) TCL_THREAD_STACK_MIN); } #endif } #endif if (! (flags & TCL_THREAD_JOINABLE)) { pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); } if (pthread_create(&theThread, &attr, (void * (*)())proc, (void *)clientData) && pthread_create(&theThread, NULL, (void * (*)())proc, (void *)clientData)) { result = TCL_ERROR; } else { *idPtr = (Tcl_ThreadId)theThread; result = TCL_OK; } pthread_attr_destroy(&attr); return result; #else return TCL_ERROR; #endif /* TCL_THREADS */ } /* *---------------------------------------------------------------------- * * Tcl_JoinThread -- * * This procedure waits upon the exit of the specified thread. * * Results: * TCL_OK if the wait was successful, TCL_ERROR else. * * Side effects: * The result area is set to the exit code of the thread we * waited upon. * *---------------------------------------------------------------------- */ int Tcl_JoinThread(threadId, state) Tcl_ThreadId threadId; /* Id of the thread to wait upon */ int* state; /* Reference to the storage the result * of the thread we wait upon will be * written into. */ { #ifdef TCL_THREADS int result; result = pthread_join ((pthread_t) threadId, (VOID**) state); return (result == 0) ? TCL_OK : TCL_ERROR; #else return TCL_ERROR; #endif } #ifdef TCL_THREADS /* *---------------------------------------------------------------------- * * TclpThreadExit -- * * This procedure terminates the current thread. * * Results: * None. * * Side effects: * This procedure terminates the current thread. * *---------------------------------------------------------------------- */ void TclpThreadExit(status) int status; { pthread_exit((VOID *)status); } #endif /* TCL_THREADS */ /* *---------------------------------------------------------------------- * * Tcl_GetCurrentThread -- * * This procedure returns the ID of the currently running thread. * * Results: * A thread ID. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_ThreadId Tcl_GetCurrentThread() { #ifdef TCL_THREADS return (Tcl_ThreadId) pthread_self(); #else return (Tcl_ThreadId) 0; #endif } /* *---------------------------------------------------------------------- * * TclpInitLock * * This procedure is used to grab a lock that serializes initialization * and finalization of Tcl. On some platforms this may also initialize * the mutex used to serialize creation of more mutexes and thread * local storage keys. * * Results: * None. * * Side effects: * Acquire the initialization mutex. * *---------------------------------------------------------------------- */ void TclpInitLock() { #ifdef TCL_THREADS pthread_mutex_lock(&initLock); #endif } /* *---------------------------------------------------------------------- * * TclpFinalizeLock * * This procedure is used to destroy all private resources used in * this file. * * Results: * None. * * Side effects: * Destroys everything private. TclpInitLock must be held * entering this function. * *---------------------------------------------------------------------- */ void TclFinalizeLock () { #ifdef TCL_THREADS /* * You do not need to destroy mutexes that were created with the * PTHREAD_MUTEX_INITIALIZER macro. These mutexes do not need * any destruction: masterLock, allocLock, and initLock. */ pthread_mutex_unlock(&initLock); #endif } /* *---------------------------------------------------------------------- * * TclpInitUnlock * * This procedure is used to release a lock that serializes initialization * and finalization of Tcl. * * Results: * None. * * Side effects: * Release the initialization mutex. * *---------------------------------------------------------------------- */ void TclpInitUnlock() { #ifdef TCL_THREADS pthread_mutex_unlock(&initLock); #endif } /* *---------------------------------------------------------------------- * * TclpMasterLock * * This procedure is used to grab a lock that serializes creation * and finalization of serialization objects. This interface is * only needed in finalization; it is hidden during * creation of the objects. * * This lock must be different than the initLock because the * initLock is held during creation of syncronization objects. * * Results: * None. * * Side effects: * Acquire the master mutex. * *---------------------------------------------------------------------- */ void TclpMasterLock() { #ifdef TCL_THREADS pthread_mutex_lock(&masterLock); #endif } /* *---------------------------------------------------------------------- * * TclpMasterUnlock * * This procedure is used to release a lock that serializes creation * and finalization of synchronization objects. * * Results: * None. * * Side effects: * Release the master mutex. * *---------------------------------------------------------------------- */ void TclpMasterUnlock() { #ifdef TCL_THREADS pthread_mutex_unlock(&masterLock); #endif } /* *---------------------------------------------------------------------- * * Tcl_GetAllocMutex * * This procedure returns a pointer to a statically initialized * mutex for use by the memory allocator. The alloctor must * use this lock, because all other locks are allocated... * * Results: * A pointer to a mutex that is suitable for passing to * Tcl_MutexLock and Tcl_MutexUnlock. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Mutex * Tcl_GetAllocMutex() { #ifdef TCL_THREADS return (Tcl_Mutex *)&allocLockPtr; #else return NULL; #endif } #ifdef TCL_THREADS /* *---------------------------------------------------------------------- * * Tcl_MutexLock -- * * This procedure is invoked to lock a mutex. This procedure * handles initializing the mutex, if necessary. The caller * can rely on the fact that Tcl_Mutex is an opaque pointer. * This routine will change that pointer from NULL after first use. * * Results: * None. * * Side effects: * May block the current thread. The mutex is aquired when * this returns. Will allocate memory for a pthread_mutex_t * and initialize this the first time this Tcl_Mutex is used. * *---------------------------------------------------------------------- */ void Tcl_MutexLock(mutexPtr) Tcl_Mutex *mutexPtr; /* Really (pthread_mutex_t **) */ { pthread_mutex_t *pmutexPtr; if (*mutexPtr == NULL) { MASTER_LOCK; if (*mutexPtr == NULL) { /* * Double inside master lock check to avoid a race condition. */ pmutexPtr = (pthread_mutex_t *)ckalloc(sizeof(pthread_mutex_t)); pthread_mutex_init(pmutexPtr, NULL); *mutexPtr = (Tcl_Mutex)pmutexPtr; TclRememberMutex(mutexPtr); } MASTER_UNLOCK; } pmutexPtr = *((pthread_mutex_t **)mutexPtr); pthread_mutex_lock(pmutexPtr); } /* *---------------------------------------------------------------------- * * Tcl_MutexUnlock -- * * This procedure is invoked to unlock a mutex. The mutex must * have been locked by Tcl_MutexLock. * * Results: * None. * * Side effects: * The mutex is released when this returns. * *---------------------------------------------------------------------- */ void Tcl_MutexUnlock(mutexPtr) Tcl_Mutex *mutexPtr; /* Really (pthread_mutex_t **) */ { pthread_mutex_t *pmutexPtr = *(pthread_mutex_t **)mutexPtr; pthread_mutex_unlock(pmutexPtr); } /* *---------------------------------------------------------------------- * * TclpFinalizeMutex -- * * This procedure is invoked to clean up one mutex. This is only * safe to call at the end of time. * * This assumes the Master Lock is held. * * Results: * None. * * Side effects: * The mutex list is deallocated. * *---------------------------------------------------------------------- */ void TclpFinalizeMutex(mutexPtr) Tcl_Mutex *mutexPtr; { pthread_mutex_t *pmutexPtr = *(pthread_mutex_t **)mutexPtr; if (pmutexPtr != NULL) { pthread_mutex_destroy(pmutexPtr); ckfree((char *)pmutexPtr); *mutexPtr = NULL; } } /* *---------------------------------------------------------------------- * * TclpThreadDataKeyInit -- * * This procedure initializes a thread specific data block key. * Each thread has table of pointers to thread specific data. * all threads agree on which table entry is used by each module. * this is remembered in a "data key", that is just an index into * this table. To allow self initialization, the interface * passes a pointer to this key and the first thread to use * the key fills in the pointer to the key. The key should be * a process-wide static. * * Results: * None. * * Side effects: * Will allocate memory the first time this process calls for * this key. In this case it modifies its argument * to hold the pointer to information about the key. * *---------------------------------------------------------------------- */ void TclpThreadDataKeyInit(keyPtr) Tcl_ThreadDataKey *keyPtr; /* Identifier for the data chunk, * really (pthread_key_t **) */ { pthread_key_t *pkeyPtr; MASTER_LOCK; if (*keyPtr == NULL) { pkeyPtr = (pthread_key_t *)ckalloc(sizeof(pthread_key_t)); pthread_key_create(pkeyPtr, NULL); *keyPtr = (Tcl_ThreadDataKey)pkeyPtr; TclRememberDataKey(keyPtr); } MASTER_UNLOCK; } /* *---------------------------------------------------------------------- * * 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(keyPtr) Tcl_ThreadDataKey *keyPtr; /* Identifier for the data chunk, * really (pthread_key_t **) */ { pthread_key_t *pkeyPtr = *(pthread_key_t **)keyPtr; if (pkeyPtr == NULL) { return NULL; } else { return (VOID *)pthread_getspecific(*pkeyPtr); } } /* *---------------------------------------------------------------------- * * 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(keyPtr, data) Tcl_ThreadDataKey *keyPtr; /* Identifier for the data chunk, * really (pthread_key_t **) */ VOID *data; /* Thread local storage */ { pthread_key_t *pkeyPtr = *(pthread_key_t **)keyPtr; pthread_setspecific(*pkeyPtr, data); } /* *---------------------------------------------------------------------- * * TclpFinalizeThreadData -- * * This procedure cleans up the thread-local storage. This is * called once for each thread. * * Results: * None. * * Side effects: * Frees up all thread local storage. * *---------------------------------------------------------------------- */ void TclpFinalizeThreadData(keyPtr) Tcl_ThreadDataKey *keyPtr; { VOID *result; pthread_key_t *pkeyPtr; if (*keyPtr != NULL) { pkeyPtr = *(pthread_key_t **)keyPtr; result = (VOID *)pthread_getspecific(*pkeyPtr); if (result != NULL) { ckfree((char *)result); pthread_setspecific(*pkeyPtr, (void *)NULL); } } } /* *---------------------------------------------------------------------- * * TclpFinalizeThreadDataKey -- * * This procedure is invoked to clean up one key. This is a * process-wide storage identifier. The thread finalization code * cleans up the thread local storage itself. * * This assumes the master lock is held. * * Results: * None. * * Side effects: * The key is deallocated. * *---------------------------------------------------------------------- */ void TclpFinalizeThreadDataKey(keyPtr) Tcl_ThreadDataKey *keyPtr; { pthread_key_t *pkeyPtr; if (*keyPtr != NULL) { pkeyPtr = *(pthread_key_t **)keyPtr; pthread_key_delete(*pkeyPtr); ckfree((char *)pkeyPtr); *keyPtr = NULL; } } /* *---------------------------------------------------------------------- * * Tcl_ConditionWait -- * * This procedure is invoked to wait on a condition variable. * The mutex is automically released as part of the wait, and * automatically grabbed when the condition is signaled. * * The mutex must be held when this procedure is called. * * Results: * None. * * Side effects: * May block the current thread. The mutex is aquired when * this returns. Will allocate memory for a pthread_mutex_t * and initialize this the first time this Tcl_Mutex is used. * *---------------------------------------------------------------------- */ void Tcl_ConditionWait(condPtr, mutexPtr, timePtr) Tcl_Condition *condPtr; /* Really (pthread_cond_t **) */ Tcl_Mutex *mutexPtr; /* Really (pthread_mutex_t **) */ Tcl_Time *timePtr; /* Timeout on waiting period */ { pthread_cond_t *pcondPtr; pthread_mutex_t *pmutexPtr; struct timespec ptime; if (*condPtr == NULL) { MASTER_LOCK; /* * Double check inside mutex to avoid race, * then initialize condition variable if necessary. */ if (*condPtr == NULL) { pcondPtr = (pthread_cond_t *)ckalloc(sizeof(pthread_cond_t)); pthread_cond_init(pcondPtr, NULL); *condPtr = (Tcl_Condition)pcondPtr; TclRememberCondition(condPtr); } MASTER_UNLOCK; } pmutexPtr = *((pthread_mutex_t **)mutexPtr); pcondPtr = *((pthread_cond_t **)condPtr); if (timePtr == NULL) { pthread_cond_wait(pcondPtr, pmutexPtr); } else { Tcl_Time now; /* * Make sure to take into account the microsecond component of the * current time, including possible overflow situations. [Bug #411603] */ Tcl_GetTime(&now); ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000; ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); pthread_cond_timedwait(pcondPtr, pmutexPtr, &ptime); } } /* *---------------------------------------------------------------------- * * Tcl_ConditionNotify -- * * This procedure is invoked to signal a condition variable. * * The mutex must be held during this call to avoid races, * but this interface does not enforce that. * * Results: * None. * * Side effects: * May unblock another thread. * *---------------------------------------------------------------------- */ void Tcl_ConditionNotify(condPtr) Tcl_Condition *condPtr; { pthread_cond_t *pcondPtr = *((pthread_cond_t **)condPtr); if (pcondPtr != NULL) { pthread_cond_broadcast(pcondPtr); } else { /* * Noone has used the condition variable, so there are no waiters. */ } } /* *---------------------------------------------------------------------- * * TclpFinalizeCondition -- * * This procedure is invoked to clean up a condition variable. * This is only safe to call at the end of time. * * This assumes the Master Lock is held. * * Results: * None. * * Side effects: * The condition variable is deallocated. * *---------------------------------------------------------------------- */ void TclpFinalizeCondition(condPtr) Tcl_Condition *condPtr; { pthread_cond_t *pcondPtr = *(pthread_cond_t **)condPtr; if (pcondPtr != NULL) { pthread_cond_destroy(pcondPtr); ckfree((char *)pcondPtr); *condPtr = NULL; } } #endif /* TCL_THREADS */ /* *---------------------------------------------------------------------- * * TclpReaddir, TclpLocaltime, TclpGmtime, TclpInetNtoa -- * * These procedures replace core C versions to be used in a * threaded environment. * * Results: * See documentation of C functions. * * Side effects: * See documentation of C functions. * * Notes: * TclpReaddir is no longer used by the core (see 1095909), * but it appears in the internal stubs table (see #589526). *---------------------------------------------------------------------- */ Tcl_DirEntry * TclpReaddir(DIR * dir) { return TclOSreaddir(dir); } char * TclpInetNtoa(struct in_addr addr) { #ifdef TCL_THREADS ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); union { unsigned long l; unsigned char b[4]; } u; u.l = (unsigned long) addr.s_addr; sprintf(tsdPtr->nabuf, "%u.%u.%u.%u", u.b[0], u.b[1], u.b[2], u.b[3]); return tsdPtr->nabuf; #else return inet_ntoa(addr); #endif } #if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC) && !defined(TCL_MEM_DEBUG) /* * Additions by AOL for specialized thread memory allocator. */ #ifdef USE_THREAD_ALLOC static volatile int initialized = 0; static pthread_key_t key; typedef struct allocMutex { Tcl_Mutex tlock; pthread_mutex_t plock; } allocMutex; Tcl_Mutex * TclpNewAllocMutex(void) { struct allocMutex *lockPtr; lockPtr = malloc(sizeof(struct allocMutex)); if (lockPtr == NULL) { panic("could not allocate lock"); } lockPtr->tlock = (Tcl_Mutex) &lockPtr->plock; pthread_mutex_init(&lockPtr->plock, NULL); return &lockPtr->tlock; } void TclpFreeAllocMutex(mutex) Tcl_Mutex *mutex; /* The alloc mutex to free. */ { allocMutex* lockPtr = (allocMutex*) mutex; if (!lockPtr) return; pthread_mutex_destroy(&lockPtr->plock); free(lockPtr); } void TclpFreeAllocCache(ptr) void *ptr; { if (ptr != NULL) { /* * Called by the pthread lib when a thread exits */ TclFreeAllocCache(ptr); } else if (initialized) { /* * Called by us in TclFinalizeThreadAlloc() during * the library finalization initiated from Tcl_Finalize() */ pthread_key_delete(key); initialized = 0; } } void * TclpGetAllocCache(void) { if (!initialized) { pthread_mutex_lock(allocLockPtr); if (!initialized) { pthread_key_create(&key, TclpFreeAllocCache); initialized = 1; } pthread_mutex_unlock(allocLockPtr); } return pthread_getspecific(key); } void TclpSetAllocCache(void *arg) { pthread_setspecific(key, arg); } #endif /* USE_THREAD_ALLOC */ #endif /* TCL_THREADS */