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