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