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 © 2000 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
15 #include "tclInt.h"
16
17 #ifdef _WIN32
18
19 /*
20 * The information about each joinable thread is remembered in a structure as
21 * defined below.
22 */
23
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
27 * the thread. */
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
37 * structure. */
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. */
45 } JoinableThread;
46
47 /*
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
50 * 'joinMutex'.
51 */
52
TCL_DECLARE_MUTEX(joinMutex)53 TCL_DECLARE_MUTEX(joinMutex)
54
55 static JoinableThread *firstThreadPtr;
56
57 /*
58 *----------------------------------------------------------------------
59 *
60 * TclJoinThread --
61 *
62 * This procedure waits for the exit of the thread with the specified id
63 * and returns its result.
64 *
65 * Results:
66 * A standard tcl result signaling the overall success/failure of the
67 * operation and an integer result delivered by the thread which was
68 * waited upon.
69 *
70 * Side effects:
71 * Deallocates the memory allocated by TclRememberJoinableThread.
72 * Removes the data associated to the thread waited upon from the list of
73 * joinable threads.
74 *
75 *----------------------------------------------------------------------
76 */
77
78 int
79 TclJoinThread(
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. */
83 {
84 JoinableThread *threadPtr;
85
86 /*
87 * Steps done here:
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
99 * xi. and delete it.
100 */
101
102 Tcl_MutexLock(&joinMutex);
103
104 threadPtr = firstThreadPtr;
105 while (threadPtr!=NULL && threadPtr->id!=id) {
106 threadPtr = threadPtr->nextThreadPtr;
107 }
108
109 if (threadPtr == NULL) {
110 /*
111 * Thread not found. Either not joinable, or already waited upon and
112 * exited. Whatever, an error is in order.
113 */
114
115 Tcl_MutexUnlock(&joinMutex);
116 return TCL_ERROR;
117 }
118
119 /*
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
123 * pointer.
124 */
125
126 Tcl_MutexLock(&threadPtr->threadMutex);
127 Tcl_MutexUnlock(&joinMutex);
128
129 /*
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.
134 */
135
136 if (threadPtr->waitedUpon) {
137 Tcl_MutexUnlock(&threadPtr->threadMutex);
138 return TCL_ERROR;
139 }
140
141 /*
142 * We are waiting now, let other threads recognize this.
143 */
144
145 threadPtr->waitedUpon = 1;
146
147 while (!threadPtr->done) {
148 Tcl_ConditionWait(&threadPtr->cond, &threadPtr->threadMutex, NULL);
149 }
150
151 /*
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.
159 */
160
161 Tcl_MutexUnlock(&threadPtr->threadMutex);
162 Tcl_MutexLock(&joinMutex);
163
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 be
168 * dangling by now.
169 */
170
171 if (firstThreadPtr == threadPtr) {
172 firstThreadPtr = threadPtr->nextThreadPtr;
173 } else {
174 JoinableThread *prevThreadPtr = firstThreadPtr;
175
176 while (prevThreadPtr->nextThreadPtr != threadPtr) {
177 prevThreadPtr = prevThreadPtr->nextThreadPtr;
178 }
179 prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr;
180 }
181
182 Tcl_MutexUnlock(&joinMutex);
183
184 /*
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.
190 */
191
192 Tcl_MutexLock(&threadPtr->threadMutex);
193 Tcl_MutexUnlock(&threadPtr->threadMutex);
194
195 /*
196 * Copy the result to us, finalize the synchronisation objects, then free
197 * the structure and return.
198 */
199
200 *result = threadPtr->result;
201
202 Tcl_ConditionFinalize(&threadPtr->cond);
203 Tcl_MutexFinalize(&threadPtr->threadMutex);
204 ckfree(threadPtr);
205
206 return TCL_OK;
207 }
208
209 /*
210 *----------------------------------------------------------------------
211 *
212 * TclRememberJoinableThread --
213 *
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.
217 *
218 * Results:
219 * None.
220 *
221 * Side effects:
222 * Allocates memory, adds it to the global list of all joinable threads.
223 *
224 *----------------------------------------------------------------------
225 */
226
227 void
TclRememberJoinableThread(Tcl_ThreadId id)228 TclRememberJoinableThread(
229 Tcl_ThreadId id) /* The thread to remember as joinable */
230 {
231 JoinableThread *threadPtr;
232
233 threadPtr = (JoinableThread *)ckalloc(sizeof(JoinableThread));
234 threadPtr->id = id;
235 threadPtr->done = 0;
236 threadPtr->waitedUpon = 0;
237 threadPtr->threadMutex = (Tcl_Mutex) NULL;
238 threadPtr->cond = (Tcl_Condition) NULL;
239
240 Tcl_MutexLock(&joinMutex);
241
242 threadPtr->nextThreadPtr = firstThreadPtr;
243 firstThreadPtr = threadPtr;
244
245 Tcl_MutexUnlock(&joinMutex);
246 }
247
248 /*
249 *----------------------------------------------------------------------
250 *
251 * TclSignalExitThread --
252 *
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.
256 *
257 * Results:
258 * None.
259 *
260 * Side effects:
261 * Modifies the associated structure to hold the result.
262 *
263 *----------------------------------------------------------------------
264 */
265
266 void
TclSignalExitThread(Tcl_ThreadId id,int result)267 TclSignalExitThread(
268 Tcl_ThreadId id, /* Id of the thread signaling its exit. */
269 int result) /* The result from the thread. */
270 {
271 JoinableThread *threadPtr;
272
273 Tcl_MutexLock(&joinMutex);
274
275 threadPtr = firstThreadPtr;
276 while ((threadPtr != NULL) && (threadPtr->id != id)) {
277 threadPtr = threadPtr->nextThreadPtr;
278 }
279
280 if (threadPtr == NULL) {
281 /*
282 * Thread not found. Not joinable. No problem, nothing to do.
283 */
284
285 Tcl_MutexUnlock(&joinMutex);
286 return;
287 }
288
289 /*
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.
294 */
295
296 Tcl_MutexLock(&threadPtr->threadMutex);
297 Tcl_MutexUnlock(&joinMutex);
298
299 threadPtr->done = 1;
300 threadPtr->result = result;
301
302 if (threadPtr->waitedUpon) {
303 Tcl_ConditionNotify(&threadPtr->cond);
304 }
305
306 Tcl_MutexUnlock(&threadPtr->threadMutex);
307 }
308 #else
309 TCL_MAC_EMPTY_FILE(generic_tclThreadJoin_c)
310 #endif /* _WIN32 */
311
312 /*
313 * Local Variables:
314 * mode: c
315 * c-basic-offset: 4
316 * fill-column: 78
317 * End:
318 */
319