1/* 2 * tclThreadJoin.c -- 3 * 4 * This file implements a platform independent emulation layer for 5 * the handling of joinable threads. The Mac and Windows platforms 6 * use this code to provide the functionality of joining threads. 7 * This code is currently not necessary on Unix. 8 * 9 * Copyright (c) 2000 by Scriptics Corporation 10 * 11 * See the file "license.terms" for information on usage and redistribution 12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id: tclThreadJoin.c,v 1.4 2002/04/24 20:35:40 hobbs Exp $ 15 */ 16 17#include "tclInt.h" 18 19#if defined(WIN32) || defined(MAC_TCL) 20 21/* The information about each joinable thread is remembered in a 22 * structure as defined below. 23 */ 24 25typedef struct JoinableThread { 26 Tcl_ThreadId id; /* The id of the joinable thread */ 27 int result; /* A place for the result after the 28 * demise of the thread */ 29 int done; /* Boolean flag. Initialized to 0 30 * and set to 1 after the exit of 31 * the thread. This allows a thread 32 * requesting a join to detect when 33 * waiting is not necessary. */ 34 int waitedUpon; /* Boolean flag. Initialized to 0 35 * and set to 1 by the thread waiting 36 * for this one via Tcl_JoinThread. 37 * Used to lock any other thread 38 * trying to wait on this one. 39 */ 40 Tcl_Mutex threadMutex; /* The mutex used to serialize access 41 * to this structure. */ 42 Tcl_Condition cond; /* This is the condition a thread has 43 * to wait upon to get notified of the 44 * end of the described thread. It is 45 * signaled indirectly by 46 * Tcl_ExitThread. */ 47 struct JoinableThread* nextThreadPtr; /* Reference to the next thread in the 48 * list of joinable threads */ 49} JoinableThread; 50 51/* The following variable is used to maintain the global list of all 52 * joinable threads. Usage by a thread is allowed only if the 53 * thread acquired the 'joinMutex'. 54 */ 55 56TCL_DECLARE_MUTEX(joinMutex) 57 58static JoinableThread* firstThreadPtr; 59 60 61 62/* 63 *---------------------------------------------------------------------- 64 * 65 * TclJoinThread -- 66 * 67 * This procedure waits for the exit of the thread with the specified 68 * id and returns its result. 69 * 70 * Results: 71 * A standard tcl result signaling the overall success/failure of the 72 * operation and an integer result delivered by the thread which was 73 * waited upon. 74 * 75 * Side effects: 76 * Deallocates the memory allocated by TclRememberJoinableThread. 77 * Removes the data associated to the thread waited upon from the 78 * list of joinable threads. 79 * 80 *---------------------------------------------------------------------- 81 */ 82 83int 84TclJoinThread(id, result) 85 Tcl_ThreadId id; /* The id of the thread to wait upon. */ 86 int* result; /* Reference to a location for the result 87 * of the thread we are waiting upon. */ 88{ 89 /* Steps done here: 90 * i. Acquire the joinMutex and search for the thread. 91 * ii. Error out if it could not be found. 92 * iii. If found, switch from exclusive access to the list to exclusive 93 * access to the thread structure. 94 * iv. Error out if some other is already waiting. 95 * v. Skip the waiting part of the thread is already done. 96 * vi. Wait for the thread to exit, mark it as waited upon too. 97 * vii. Get the result form the structure, 98 * viii. switch to exclusive access of the list, 99 * ix. remove the structure from the list, 100 * x. then switch back to exclusive access to the structure 101 * xi. and delete it. 102 */ 103 104 JoinableThread* threadPtr; 105 106 Tcl_MutexLock (&joinMutex); 107 108 for (threadPtr = firstThreadPtr; 109 (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id); 110 threadPtr = threadPtr->nextThreadPtr) 111 /* empty body */ 112 ; 113 114 if (threadPtr == (JoinableThread*) NULL) { 115 /* Thread not found. Either not joinable, or already waited 116 * upon and exited. Whatever, an error is in order. 117 */ 118 119 Tcl_MutexUnlock (&joinMutex); 120 return TCL_ERROR; 121 } 122 123 /* [1] If we don't lock the structure before giving up exclusive access 124 * to the list some other thread just completing its wait on the same 125 * thread can delete the structure from under us, leaving us with a 126 * dangling pointer. 127 */ 128 129 Tcl_MutexLock (&threadPtr->threadMutex); 130 Tcl_MutexUnlock (&joinMutex); 131 132 /* [2] Now that we have the structure mutex any other thread that just 133 * tries to delete structure will wait at location [3] until we are 134 * done with the structure. And in that case we are done with it 135 * rather quickly as 'waitedUpon' will be set and we will have to 136 * error out. 137 */ 138 139 if (threadPtr->waitedUpon) { 140 Tcl_MutexUnlock (&threadPtr->threadMutex); 141 return TCL_ERROR; 142 } 143 144 /* We are waiting now, let other threads recognize this 145 */ 146 147 threadPtr->waitedUpon = 1; 148 149 while (!threadPtr->done) { 150 Tcl_ConditionWait (&threadPtr->cond, &threadPtr->threadMutex, NULL); 151 } 152 153 /* We have to release the structure before trying to access the list 154 * again or we can run into deadlock with a thread at [1] (see above) 155 * because of us holding the structure and the other holding the list. 156 * There is no problem with dangling pointers here as 'waitedUpon == 1' 157 * is still valid and any other thread will error out and not come to 158 * this place. IOW, the fact that we are here also means that no other 159 * thread came here before us and is able to delete the structure. 160 */ 161 162 Tcl_MutexUnlock (&threadPtr->threadMutex); 163 Tcl_MutexLock (&joinMutex); 164 165 /* We have to search the list again as its structure may (may, almost 166 * certainly) have changed while we were waiting. Especially now is the 167 * time to compute the predecessor in the list. Any earlier result can 168 * be dangling by now. 169 */ 170 171 if (firstThreadPtr == threadPtr) { 172 firstThreadPtr = threadPtr->nextThreadPtr; 173 } else { 174 JoinableThread* prevThreadPtr; 175 176 for (prevThreadPtr = firstThreadPtr; 177 prevThreadPtr->nextThreadPtr != threadPtr; 178 prevThreadPtr = prevThreadPtr->nextThreadPtr) 179 /* empty body */ 180 ; 181 182 prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr; 183 } 184 185 Tcl_MutexUnlock (&joinMutex); 186 187 /* [3] Now that the structure is not part of the list anymore no other 188 * thread can acquire its mutex from now on. But it is possible that 189 * another thread is still holding the mutex though, see location [2]. 190 * So we have to acquire the mutex one more time to wait for that thread 191 * to finish. We can (and have to) release the mutex immediately. 192 */ 193 194 Tcl_MutexLock (&threadPtr->threadMutex); 195 Tcl_MutexUnlock (&threadPtr->threadMutex); 196 197 /* Copy the result to us, finalize the synchronisation objects, then 198 * free the structure and return. 199 */ 200 201 *result = threadPtr->result; 202 203 Tcl_ConditionFinalize (&threadPtr->cond); 204 Tcl_MutexFinalize (&threadPtr->threadMutex); 205 ckfree ((VOID*) threadPtr); 206 207 return TCL_OK; 208} 209 210/* 211 *---------------------------------------------------------------------- 212 * 213 * TclRememberJoinableThread -- 214 * 215 * This procedure remebers a thread as joinable. Only a call to 216 * TclJoinThread will remove the structre created (and initialized) 217 * here. IOW, not waiting upon a joinable thread will cause memory 218 * leaks. 219 * 220 * Results: 221 * None. 222 * 223 * Side effects: 224 * Allocates memory, adds it to the global list of all joinable 225 * threads. 226 * 227 *---------------------------------------------------------------------- 228 */ 229 230VOID 231TclRememberJoinableThread(id) 232 Tcl_ThreadId id; /* The thread to remember as joinable */ 233{ 234 JoinableThread* threadPtr; 235 236 threadPtr = (JoinableThread*) ckalloc (sizeof (JoinableThread)); 237 threadPtr->id = id; 238 threadPtr->done = 0; 239 threadPtr->waitedUpon = 0; 240 threadPtr->threadMutex = (Tcl_Mutex) NULL; 241 threadPtr->cond = (Tcl_Condition) NULL; 242 243 Tcl_MutexLock (&joinMutex); 244 245 threadPtr->nextThreadPtr = firstThreadPtr; 246 firstThreadPtr = threadPtr; 247 248 Tcl_MutexUnlock (&joinMutex); 249} 250 251/* 252 *---------------------------------------------------------------------- 253 * 254 * TclSignalExitThread -- 255 * 256 * This procedure signals that the specified thread is done with 257 * its work. If the thread is joinable this signal is propagated 258 * to the thread waiting upon it. 259 * 260 * Results: 261 * None. 262 * 263 * Side effects: 264 * Modifies the associated structure to hold the result. 265 * 266 *---------------------------------------------------------------------- 267 */ 268 269VOID 270TclSignalExitThread(id,result) 271 Tcl_ThreadId id; /* Id of the thread signaling its exit */ 272 int result; /* The result from the thread */ 273{ 274 JoinableThread* threadPtr; 275 276 Tcl_MutexLock (&joinMutex); 277 278 for (threadPtr = firstThreadPtr; 279 (threadPtr != (JoinableThread*) NULL) && (threadPtr->id != id); 280 threadPtr = threadPtr->nextThreadPtr) 281 /* empty body */ 282 ; 283 284 if (threadPtr == (JoinableThread*) NULL) { 285 /* Thread not found. Not joinable. No problem, nothing to do. 286 */ 287 288 Tcl_MutexUnlock (&joinMutex); 289 return; 290 } 291 292 /* Switch over the exclusive access from the list to the structure, 293 * then store the result, set the flag and notify the waiting thread, 294 * provided that it exists. The order of lock/unlock ensures that a 295 * thread entering 'TclJoinThread' will not interfere with us. 296 */ 297 298 Tcl_MutexLock (&threadPtr->threadMutex); 299 Tcl_MutexUnlock (&joinMutex); 300 301 threadPtr->done = 1; 302 threadPtr->result = result; 303 304 if (threadPtr->waitedUpon) { 305 Tcl_ConditionNotify (&threadPtr->cond); 306 } 307 308 Tcl_MutexUnlock (&threadPtr->threadMutex); 309} 310 311#endif /* WIN32 || MAC_TCL */ 312