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