| 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 |  | 
|---|
| 26 | typedef 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 |  | 
|---|
| 55 | TCL_DECLARE_MUTEX(joinMutex) | 
|---|
| 56 |  | 
|---|
| 57 | static 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 |  | 
|---|
| 80 | int | 
|---|
| 81 | TclJoinThread( | 
|---|
| 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 |  | 
|---|
| 229 | void | 
|---|
| 230 | TclRememberJoinableThread( | 
|---|
| 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 |  | 
|---|
| 268 | void | 
|---|
| 269 | TclSignalExitThread( | 
|---|
| 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 |  */ | 
|---|