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.
9 * Copyright (c) 2000 by Scriptics Corporation
11 * See the file "license.terms" for information on usage and redistribution of
12 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
20 * The information about each joinable thread is remembered in a structure as
24 typedef struct JoinableThread {
25 Tcl_ThreadId id; /* The id of the joinable thread. */
26 int result; /* A place for the result after the demise of
28 int done; /* Boolean flag. Initialized to 0 and set to 1
29 * after the exit of the thread. This allows a
30 * thread requesting a join to detect when
31 * waiting is not necessary. */
32 int waitedUpon; /* Boolean flag. Initialized to 0 and set to 1
33 * by the thread waiting for this one via
34 * Tcl_JoinThread. Used to lock any other
35 * thread trying to wait on this one. */
36 Tcl_Mutex threadMutex; /* The mutex used to serialize access to this
38 Tcl_Condition cond; /* This is the condition a thread has to wait
39 * upon to get notified of the end of the
40 * described thread. It is signaled indirectly
41 * by Tcl_ExitThread. */
42 struct JoinableThread *nextThreadPtr;
43 /* Reference to the next thread in the list of
44 * joinable threads. */
48 * The following variable is used to maintain the global list of all joinable
49 * threads. Usage by a thread is allowed only if the thread acquired the
53 TCL_DECLARE_MUTEX(joinMutex)
55 static JoinableThread *firstThreadPtr;
58 *----------------------------------------------------------------------
62 * This procedure waits for the exit of the thread with the specified id
63 * and returns its result.
66 * A standard tcl result signaling the overall success/failure of the
67 * operation and an integer result delivered by the thread which was
71 * Deallocates the memory allocated by TclRememberJoinableThread.
72 * Removes the data associated to the thread waited upon from the list of
75 *----------------------------------------------------------------------
80 Tcl_ThreadId id, /* The id of the thread to wait upon. */
81 int *result) /* Reference to a location for the result of
82 * the thread we are waiting upon. */
84 JoinableThread *threadPtr;
88 * i. Acquire the joinMutex and search for the thread.
89 * ii. Error out if it could not be found.
90 * iii. If found, switch from exclusive access to the list to exclusive
91 * access to the thread structure.
92 * iv. Error out if some other is already waiting.
93 * v. Skip the waiting part of the thread is already done.
94 * vi. Wait for the thread to exit, mark it as waited upon too.
95 * vii. Get the result form the structure,
96 * viii. switch to exclusive access of the list,
97 * ix. remove the structure from the list,
98 * x. then switch back to exclusive access to the structure
102 Tcl_MutexLock(&joinMutex);
104 threadPtr = firstThreadPtr;
105 while (threadPtr!=NULL && threadPtr->id!=id) {
106 threadPtr = threadPtr->nextThreadPtr;
109 if (threadPtr == NULL) {
111 * Thread not found. Either not joinable, or already waited upon and
112 * exited. Whatever, an error is in order.
115 Tcl_MutexUnlock(&joinMutex);
120 * [1] If we don't lock the structure before giving up exclusive access to
121 * the list some other thread just completing its wait on the same thread
122 * can delete the structure from under us, leaving us with a dangling
126 Tcl_MutexLock(&threadPtr->threadMutex);
127 Tcl_MutexUnlock(&joinMutex);
130 * [2] Now that we have the structure mutex any other thread that just
131 * tries to delete structure will wait at location [3] until we are done
132 * with the structure. And in that case we are done with it rather quickly
133 * as 'waitedUpon' will be set and we will have to error out.
136 if (threadPtr->waitedUpon) {
137 Tcl_MutexUnlock(&threadPtr->threadMutex);
142 * We are waiting now, let other threads recognize this.
145 threadPtr->waitedUpon = 1;
147 while (!threadPtr->done) {
148 Tcl_ConditionWait(&threadPtr->cond, &threadPtr->threadMutex, NULL);
152 * We have to release the structure before trying to access the list again
153 * or we can run into deadlock with a thread at [1] (see above) because of
154 * us holding the structure and the other holding the list. There is no
155 * problem with dangling pointers here as 'waitedUpon == 1' is still valid
156 * and any other thread will error out and not come to this place. IOW,
157 * the fact that we are here also means that no other thread came here
158 * before us and is able to delete the structure.
161 Tcl_MutexUnlock(&threadPtr->threadMutex);
162 Tcl_MutexLock(&joinMutex);
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 be
171 if (firstThreadPtr == threadPtr) {
172 firstThreadPtr = threadPtr->nextThreadPtr;
174 JoinableThread *prevThreadPtr = firstThreadPtr;
176 while (prevThreadPtr->nextThreadPtr != threadPtr) {
177 prevThreadPtr = prevThreadPtr->nextThreadPtr;
179 prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr;
182 Tcl_MutexUnlock(&joinMutex);
185 * [3] Now that the structure is not part of the list anymore no other
186 * thread can acquire its mutex from now on. But it is possible that
187 * another thread is still holding the mutex though, see location [2]. So
188 * we have to acquire the mutex one more time to wait for that thread to
189 * finish. We can (and have to) release the mutex immediately.
192 Tcl_MutexLock(&threadPtr->threadMutex);
193 Tcl_MutexUnlock(&threadPtr->threadMutex);
196 * Copy the result to us, finalize the synchronisation objects, then free
197 * the structure and return.
200 *result = threadPtr->result;
202 Tcl_ConditionFinalize(&threadPtr->cond);
203 Tcl_MutexFinalize(&threadPtr->threadMutex);
210 *----------------------------------------------------------------------
212 * TclRememberJoinableThread --
214 * This procedure remebers a thread as joinable. Only a call to
215 * TclJoinThread will remove the structre created (and initialized) here.
216 * IOW, not waiting upon a joinable thread will cause memory leaks.
222 * Allocates memory, adds it to the global list of all joinable threads.
224 *----------------------------------------------------------------------
228 TclRememberJoinableThread(
229 Tcl_ThreadId id) /* The thread to remember as joinable */
231 JoinableThread *threadPtr;
233 threadPtr = ckalloc(sizeof(JoinableThread));
236 threadPtr->waitedUpon = 0;
237 threadPtr->threadMutex = (Tcl_Mutex) NULL;
238 threadPtr->cond = (Tcl_Condition) NULL;
240 Tcl_MutexLock(&joinMutex);
242 threadPtr->nextThreadPtr = firstThreadPtr;
243 firstThreadPtr = threadPtr;
245 Tcl_MutexUnlock(&joinMutex);
249 *----------------------------------------------------------------------
251 * TclSignalExitThread --
253 * This procedure signals that the specified thread is done with its
254 * work. If the thread is joinable this signal is propagated to the
255 * thread waiting upon it.
261 * Modifies the associated structure to hold the result.
263 *----------------------------------------------------------------------
268 Tcl_ThreadId id, /* Id of the thread signaling its exit. */
269 int result) /* The result from the thread. */
271 JoinableThread *threadPtr;
273 Tcl_MutexLock(&joinMutex);
275 threadPtr = firstThreadPtr;
276 while ((threadPtr != NULL) && (threadPtr->id != id)) {
277 threadPtr = threadPtr->nextThreadPtr;
280 if (threadPtr == NULL) {
282 * Thread not found. Not joinable. No problem, nothing to do.
285 Tcl_MutexUnlock(&joinMutex);
290 * Switch over the exclusive access from the list to the structure, then
291 * store the result, set the flag and notify the waiting thread, provided
292 * that it exists. The order of lock/unlock ensures that a thread entering
293 * 'TclJoinThread' will not interfere with us.
296 Tcl_MutexLock(&threadPtr->threadMutex);
297 Tcl_MutexUnlock(&joinMutex);
300 threadPtr->result = result;
302 if (threadPtr->waitedUpon) {
303 Tcl_ConditionNotify(&threadPtr->cond);
306 Tcl_MutexUnlock(&threadPtr->threadMutex);