sl@0: /* sl@0: * tclThreadJoin.c -- sl@0: * sl@0: * This file implements a platform independent emulation layer for sl@0: * the handling of joinable threads. The Mac and Windows platforms sl@0: * use this code to provide the functionality of joining threads. sl@0: * This code is currently not necessary on Unix. sl@0: * sl@0: * Copyright (c) 2000 by Scriptics Corporation sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclThreadJoin.c,v 1.4 2002/04/24 20:35:40 hobbs Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: sl@0: #if defined(WIN32) || defined(MAC_TCL) sl@0: sl@0: /* The information about each joinable thread is remembered in a sl@0: * structure as defined below. sl@0: */ sl@0: sl@0: typedef struct JoinableThread { sl@0: Tcl_ThreadId id; /* The id of the joinable thread */ sl@0: int result; /* A place for the result after the sl@0: * demise of the thread */ sl@0: int done; /* Boolean flag. Initialized to 0 sl@0: * and set to 1 after the exit of sl@0: * the thread. This allows a thread sl@0: * requesting a join to detect when sl@0: * waiting is not necessary. */ sl@0: int waitedUpon; /* Boolean flag. Initialized to 0 sl@0: * and set to 1 by the thread waiting sl@0: * for this one via Tcl_JoinThread. sl@0: * Used to lock any other thread sl@0: * trying to wait on this one. sl@0: */ sl@0: Tcl_Mutex threadMutex; /* The mutex used to serialize access sl@0: * to this structure. */ sl@0: Tcl_Condition cond; /* This is the condition a thread has sl@0: * to wait upon to get notified of the sl@0: * end of the described thread. It is sl@0: * signaled indirectly by sl@0: * Tcl_ExitThread. */ sl@0: struct JoinableThread* nextThreadPtr; /* Reference to the next thread in the sl@0: * list of joinable threads */ sl@0: } JoinableThread; sl@0: sl@0: /* The following variable is used to maintain the global list of all sl@0: * joinable threads. Usage by a thread is allowed only if the sl@0: * thread acquired the 'joinMutex'. sl@0: */ sl@0: sl@0: TCL_DECLARE_MUTEX(joinMutex) sl@0: sl@0: static JoinableThread* firstThreadPtr; sl@0: sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclJoinThread -- sl@0: * sl@0: * This procedure waits for the exit of the thread with the specified sl@0: * id and returns its result. sl@0: * sl@0: * Results: sl@0: * A standard tcl result signaling the overall success/failure of the sl@0: * operation and an integer result delivered by the thread which was sl@0: * waited upon. sl@0: * sl@0: * Side effects: sl@0: * Deallocates the memory allocated by TclRememberJoinableThread. sl@0: * Removes the data associated to the thread waited upon from the sl@0: * list of joinable threads. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclJoinThread(id, result) sl@0: Tcl_ThreadId id; /* The id of the thread to wait upon. */ sl@0: int* result; /* Reference to a location for the result sl@0: * of the thread we are waiting upon. */ sl@0: { sl@0: /* Steps done here: sl@0: * i. Acquire the joinMutex and search for the thread. sl@0: * ii. Error out if it could not be found. sl@0: * iii. If found, switch from exclusive access to the list to exclusive sl@0: * access to the thread structure. sl@0: * iv. Error out if some other is already waiting. sl@0: * v. Skip the waiting part of the thread is already done. sl@0: * vi. Wait for the thread to exit, mark it as waited upon too. sl@0: * vii. Get the result form the structure, sl@0: * viii. switch to exclusive access of the list, sl@0: * ix. remove the structure from the list, sl@0: * x. then switch back to exclusive access to the structure sl@0: * xi. and delete it. sl@0: */ sl@0: sl@0: JoinableThread* threadPtr; sl@0: sl@0: Tcl_MutexLock (&joinMutex); sl@0: sl@0: for (threadPtr = firstThreadPtr; sl@0: (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id); sl@0: threadPtr = threadPtr->nextThreadPtr) sl@0: /* empty body */ sl@0: ; sl@0: sl@0: if (threadPtr == (JoinableThread*) NULL) { sl@0: /* Thread not found. Either not joinable, or already waited sl@0: * upon and exited. Whatever, an error is in order. sl@0: */ sl@0: sl@0: Tcl_MutexUnlock (&joinMutex); sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* [1] If we don't lock the structure before giving up exclusive access sl@0: * to the list some other thread just completing its wait on the same sl@0: * thread can delete the structure from under us, leaving us with a sl@0: * dangling pointer. sl@0: */ sl@0: sl@0: Tcl_MutexLock (&threadPtr->threadMutex); sl@0: Tcl_MutexUnlock (&joinMutex); sl@0: sl@0: /* [2] Now that we have the structure mutex any other thread that just sl@0: * tries to delete structure will wait at location [3] until we are sl@0: * done with the structure. And in that case we are done with it sl@0: * rather quickly as 'waitedUpon' will be set and we will have to sl@0: * error out. sl@0: */ sl@0: sl@0: if (threadPtr->waitedUpon) { sl@0: Tcl_MutexUnlock (&threadPtr->threadMutex); sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* We are waiting now, let other threads recognize this sl@0: */ sl@0: sl@0: threadPtr->waitedUpon = 1; sl@0: sl@0: while (!threadPtr->done) { sl@0: Tcl_ConditionWait (&threadPtr->cond, &threadPtr->threadMutex, NULL); sl@0: } sl@0: sl@0: /* We have to release the structure before trying to access the list sl@0: * again or we can run into deadlock with a thread at [1] (see above) sl@0: * because of us holding the structure and the other holding the list. sl@0: * There is no problem with dangling pointers here as 'waitedUpon == 1' sl@0: * is still valid and any other thread will error out and not come to sl@0: * this place. IOW, the fact that we are here also means that no other sl@0: * thread came here before us and is able to delete the structure. sl@0: */ sl@0: sl@0: Tcl_MutexUnlock (&threadPtr->threadMutex); sl@0: Tcl_MutexLock (&joinMutex); sl@0: sl@0: /* We have to search the list again as its structure may (may, almost sl@0: * certainly) have changed while we were waiting. Especially now is the sl@0: * time to compute the predecessor in the list. Any earlier result can sl@0: * be dangling by now. sl@0: */ sl@0: sl@0: if (firstThreadPtr == threadPtr) { sl@0: firstThreadPtr = threadPtr->nextThreadPtr; sl@0: } else { sl@0: JoinableThread* prevThreadPtr; sl@0: sl@0: for (prevThreadPtr = firstThreadPtr; sl@0: prevThreadPtr->nextThreadPtr != threadPtr; sl@0: prevThreadPtr = prevThreadPtr->nextThreadPtr) sl@0: /* empty body */ sl@0: ; sl@0: sl@0: prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr; sl@0: } sl@0: sl@0: Tcl_MutexUnlock (&joinMutex); sl@0: sl@0: /* [3] Now that the structure is not part of the list anymore no other sl@0: * thread can acquire its mutex from now on. But it is possible that sl@0: * another thread is still holding the mutex though, see location [2]. sl@0: * So we have to acquire the mutex one more time to wait for that thread sl@0: * to finish. We can (and have to) release the mutex immediately. sl@0: */ sl@0: sl@0: Tcl_MutexLock (&threadPtr->threadMutex); sl@0: Tcl_MutexUnlock (&threadPtr->threadMutex); sl@0: sl@0: /* Copy the result to us, finalize the synchronisation objects, then sl@0: * free the structure and return. sl@0: */ sl@0: sl@0: *result = threadPtr->result; sl@0: sl@0: Tcl_ConditionFinalize (&threadPtr->cond); sl@0: Tcl_MutexFinalize (&threadPtr->threadMutex); sl@0: ckfree ((VOID*) threadPtr); sl@0: sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclRememberJoinableThread -- sl@0: * sl@0: * This procedure remebers a thread as joinable. Only a call to sl@0: * TclJoinThread will remove the structre created (and initialized) sl@0: * here. IOW, not waiting upon a joinable thread will cause memory sl@0: * leaks. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Allocates memory, adds it to the global list of all joinable sl@0: * threads. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: VOID sl@0: TclRememberJoinableThread(id) sl@0: Tcl_ThreadId id; /* The thread to remember as joinable */ sl@0: { sl@0: JoinableThread* threadPtr; sl@0: sl@0: threadPtr = (JoinableThread*) ckalloc (sizeof (JoinableThread)); sl@0: threadPtr->id = id; sl@0: threadPtr->done = 0; sl@0: threadPtr->waitedUpon = 0; sl@0: threadPtr->threadMutex = (Tcl_Mutex) NULL; sl@0: threadPtr->cond = (Tcl_Condition) NULL; sl@0: sl@0: Tcl_MutexLock (&joinMutex); sl@0: sl@0: threadPtr->nextThreadPtr = firstThreadPtr; sl@0: firstThreadPtr = threadPtr; sl@0: sl@0: Tcl_MutexUnlock (&joinMutex); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclSignalExitThread -- sl@0: * sl@0: * This procedure signals that the specified thread is done with sl@0: * its work. If the thread is joinable this signal is propagated sl@0: * to the thread waiting upon it. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Modifies the associated structure to hold the result. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: VOID sl@0: TclSignalExitThread(id,result) sl@0: Tcl_ThreadId id; /* Id of the thread signaling its exit */ sl@0: int result; /* The result from the thread */ sl@0: { sl@0: JoinableThread* threadPtr; sl@0: sl@0: Tcl_MutexLock (&joinMutex); sl@0: sl@0: for (threadPtr = firstThreadPtr; sl@0: (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id); sl@0: threadPtr = threadPtr->nextThreadPtr) sl@0: /* empty body */ sl@0: ; sl@0: sl@0: if (threadPtr == (JoinableThread*) NULL) { sl@0: /* Thread not found. Not joinable. No problem, nothing to do. sl@0: */ sl@0: sl@0: Tcl_MutexUnlock (&joinMutex); sl@0: return; sl@0: } sl@0: sl@0: /* Switch over the exclusive access from the list to the structure, sl@0: * then store the result, set the flag and notify the waiting thread, sl@0: * provided that it exists. The order of lock/unlock ensures that a sl@0: * thread entering 'TclJoinThread' will not interfere with us. sl@0: */ sl@0: sl@0: Tcl_MutexLock (&threadPtr->threadMutex); sl@0: Tcl_MutexUnlock (&joinMutex); sl@0: sl@0: threadPtr->done = 1; sl@0: threadPtr->result = result; sl@0: sl@0: if (threadPtr->waitedUpon) { sl@0: Tcl_ConditionNotify (&threadPtr->cond); sl@0: } sl@0: sl@0: Tcl_MutexUnlock (&threadPtr->threadMutex); sl@0: } sl@0: sl@0: #endif /* WIN32 || MAC_TCL */