1/*
2 * tclAsync.c --
3 *
4 *	This file provides low-level support needed to invoke signal
5 *	handlers in a safe way.  The code here doesn't actually handle
6 *	signals, though.  This code is based on proposals made by
7 *	Mark Diekhans and Don Libes.
8 *
9 * Copyright (c) 1993 The Regents of the University of California.
10 * Copyright (c) 1994 Sun Microsystems, Inc.
11 *
12 * See the file "license.terms" for information on usage and redistribution
13 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * RCS: @(#) $Id: tclAsync.c,v 1.6.12.1 2006/07/11 13:18:10 vasiljevic Exp $
16 */
17
18#include "tclInt.h"
19#include "tclPort.h"
20
21/* Forward declaration */
22struct ThreadSpecificData;
23
24/*
25 * One of the following structures exists for each asynchronous
26 * handler:
27 */
28
29typedef struct AsyncHandler {
30    int ready;				/* Non-zero means this handler should
31					 * be invoked in the next call to
32					 * Tcl_AsyncInvoke. */
33    struct AsyncHandler *nextPtr;	/* Next in list of all handlers for
34					 * the process. */
35    Tcl_AsyncProc *proc;		/* Procedure to call when handler
36					 * is invoked. */
37    ClientData clientData;		/* Value to pass to handler when it
38					 * is invoked. */
39    struct ThreadSpecificData *originTsd;
40					/* Used in Tcl_AsyncMark to modify thread-
41					 * specific data from outside the thread
42					 * it is associated to. */
43    Tcl_ThreadId originThrdId;		/* Origin thread where this token was
44					 * created and where it will be
45					 * yielded. */
46} AsyncHandler;
47
48
49typedef struct ThreadSpecificData {
50    /*
51     * The variables below maintain a list of all existing handlers
52     * specific to the calling thread.
53     */
54    AsyncHandler *firstHandler;	    /* First handler defined for process,
55				     * or NULL if none. */
56    AsyncHandler *lastHandler;	    /* Last handler or NULL. */
57
58    /*
59     * The variable below is set to 1 whenever a handler becomes ready and
60     * it is cleared to zero whenever Tcl_AsyncInvoke is called.  It can be
61     * checked elsewhere in the application by calling Tcl_AsyncReady to see
62     * if Tcl_AsyncInvoke should be invoked.
63     */
64
65    int asyncReady;
66
67    /*
68     * The variable below indicates whether Tcl_AsyncInvoke is currently
69     * working.  If so then we won't set asyncReady again until
70     * Tcl_AsyncInvoke returns.
71     */
72
73    int asyncActive;
74
75    Tcl_Mutex asyncMutex;   /* Thread-specific AsyncHandler linked-list lock */
76
77} ThreadSpecificData;
78static Tcl_ThreadDataKey dataKey;
79
80
81/*
82 *----------------------------------------------------------------------
83 *
84 * TclFinalizeAsync --
85 *
86 *	Finalizes the mutex in the thread local data structure for the
87 *	async subsystem.
88 *
89 * Results:
90 *	None.
91 *
92 * Side effects:
93 *	Forgets knowledge of the mutex should it have been created.
94 *
95 *----------------------------------------------------------------------
96 */
97
98void
99TclFinalizeAsync()
100{
101    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
102
103    if (tsdPtr->asyncMutex != NULL) {
104	Tcl_MutexFinalize(&tsdPtr->asyncMutex);
105    }
106}
107
108/*
109 *----------------------------------------------------------------------
110 *
111 * Tcl_AsyncCreate --
112 *
113 *	This procedure creates the data structures for an asynchronous
114 *	handler, so that no memory has to be allocated when the handler
115 *	is activated.
116 *
117 * Results:
118 *	The return value is a token for the handler, which can be used
119 *	to activate it later on.
120 *
121 * Side effects:
122 *	Information about the handler is recorded.
123 *
124 *----------------------------------------------------------------------
125 */
126
127Tcl_AsyncHandler
128Tcl_AsyncCreate(proc, clientData)
129    Tcl_AsyncProc *proc;		/* Procedure to call when handler
130					 * is invoked. */
131    ClientData clientData;		/* Argument to pass to handler. */
132{
133    AsyncHandler *asyncPtr;
134    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
135
136    asyncPtr = (AsyncHandler *) ckalloc(sizeof(AsyncHandler));
137    asyncPtr->ready = 0;
138    asyncPtr->nextPtr = NULL;
139    asyncPtr->proc = proc;
140    asyncPtr->clientData = clientData;
141    asyncPtr->originTsd = tsdPtr;
142    asyncPtr->originThrdId = Tcl_GetCurrentThread();
143
144    Tcl_MutexLock(&tsdPtr->asyncMutex);
145    if (tsdPtr->firstHandler == NULL) {
146	tsdPtr->firstHandler = asyncPtr;
147    } else {
148	tsdPtr->lastHandler->nextPtr = asyncPtr;
149    }
150    tsdPtr->lastHandler = asyncPtr;
151    Tcl_MutexUnlock(&tsdPtr->asyncMutex);
152    return (Tcl_AsyncHandler) asyncPtr;
153}
154
155/*
156 *----------------------------------------------------------------------
157 *
158 * Tcl_AsyncMark --
159 *
160 *	This procedure is called to request that an asynchronous handler
161 *	be invoked as soon as possible.  It's typically called from
162 *	an interrupt handler, where it isn't safe to do anything that
163 *	depends on or modifies application state.
164 *
165 * Results:
166 *	None.
167 *
168 * Side effects:
169 *	The handler gets marked for invocation later.
170 *
171 *----------------------------------------------------------------------
172 */
173
174void
175Tcl_AsyncMark(async)
176    Tcl_AsyncHandler async;		/* Token for handler. */
177{
178    AsyncHandler *token = (AsyncHandler *) async;
179
180    Tcl_MutexLock(&token->originTsd->asyncMutex);
181    token->ready = 1;
182    if (!token->originTsd->asyncActive) {
183	token->originTsd->asyncReady = 1;
184	Tcl_ThreadAlert(token->originThrdId);
185    }
186    Tcl_MutexUnlock(&token->originTsd->asyncMutex);
187}
188
189/*
190 *----------------------------------------------------------------------
191 *
192 * Tcl_AsyncInvoke --
193 *
194 *	This procedure is called at a "safe" time at background level
195 *	to invoke any active asynchronous handlers.
196 *
197 * Results:
198 *	The return value is a normal Tcl result, which is intended to
199 *	replace the code argument as the current completion code for
200 *	interp.
201 *
202 * Side effects:
203 *	Depends on the handlers that are active.
204 *
205 *----------------------------------------------------------------------
206 */
207
208int
209Tcl_AsyncInvoke(interp, code)
210    Tcl_Interp *interp;			/* If invoked from Tcl_Eval just after
211					 * completing a command, points to
212					 * interpreter.  Otherwise it is
213					 * NULL. */
214    int code; 				/* If interp is non-NULL, this gives
215					 * completion code from command that
216					 * just completed. */
217{
218    AsyncHandler *asyncPtr;
219    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
220
221    Tcl_MutexLock(&tsdPtr->asyncMutex);
222
223    if (tsdPtr->asyncReady == 0) {
224	Tcl_MutexUnlock(&tsdPtr->asyncMutex);
225	return code;
226    }
227    tsdPtr->asyncReady = 0;
228    tsdPtr->asyncActive = 1;
229    if (interp == NULL) {
230	code = 0;
231    }
232
233    /*
234     * Make one or more passes over the list of handlers, invoking
235     * at most one handler in each pass.  After invoking a handler,
236     * go back to the start of the list again so that (a) if a new
237     * higher-priority handler gets marked while executing a lower
238     * priority handler, we execute the higher-priority handler
239     * next, and (b) if a handler gets deleted during the execution
240     * of a handler, then the list structure may change so it isn't
241     * safe to continue down the list anyway.
242     */
243
244    while (1) {
245	for (asyncPtr = tsdPtr->firstHandler; asyncPtr != NULL;
246		asyncPtr = asyncPtr->nextPtr) {
247	    if (asyncPtr->ready) {
248		break;
249	    }
250	}
251	if (asyncPtr == NULL) {
252	    break;
253	}
254	asyncPtr->ready = 0;
255	Tcl_MutexUnlock(&tsdPtr->asyncMutex);
256	code = (*asyncPtr->proc)(asyncPtr->clientData, interp, code);
257	Tcl_MutexLock(&tsdPtr->asyncMutex);
258    }
259    tsdPtr->asyncActive = 0;
260    Tcl_MutexUnlock(&tsdPtr->asyncMutex);
261    return code;
262}
263
264/*
265 *----------------------------------------------------------------------
266 *
267 * Tcl_AsyncDelete --
268 *
269 *	Frees up all the state for an asynchronous handler.  The handler
270 *	should never be used again.
271 *
272 * Results:
273 *	None.
274 *
275 * Side effects:
276 *	The state associated with the handler is deleted.
277 *
278 *	Failure to locate the handler in current thread private list
279 *	of async handlers will result in panic; exception: the list
280 *	is already empty (potential trouble?).
281 *	Consequently, threads should create and delete handlers
282 *	themselves.  I.e. a handler created by one should not be
283 *	deleted by some other thread.
284 *
285 *----------------------------------------------------------------------
286 */
287
288void
289Tcl_AsyncDelete(async)
290    Tcl_AsyncHandler async;		/* Token for handler to delete. */
291{
292    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
293    AsyncHandler *asyncPtr = (AsyncHandler *) async;
294    AsyncHandler *prevPtr, *thisPtr;
295
296    /*
297     * Assure early handling of the constraint
298     */
299
300    if (asyncPtr->originThrdId != Tcl_GetCurrentThread()) {
301	panic("Tcl_AsyncDelete: async handler deleted by the wrong thread");
302    }
303
304    /*
305     * If we come to this point when TSD's for the current
306     * thread have already been garbage-collected, we are
307     * in the _serious_ trouble. OTOH, we tolerate calling
308     * with already cleaned-up handler list (should we?).
309     */
310
311    Tcl_MutexLock(&tsdPtr->asyncMutex);
312    if (tsdPtr->firstHandler != NULL) {
313	prevPtr = thisPtr = tsdPtr->firstHandler;
314	while (thisPtr != NULL && thisPtr != asyncPtr) {
315	    prevPtr = thisPtr;
316	    thisPtr = thisPtr->nextPtr;
317	}
318	if (thisPtr == NULL) {
319	    panic("Tcl_AsyncDelete: cannot find async handler");
320	}
321	if (asyncPtr == tsdPtr->firstHandler) {
322	    tsdPtr->firstHandler = asyncPtr->nextPtr;
323	} else {
324	    prevPtr->nextPtr = asyncPtr->nextPtr;
325	}
326	if (asyncPtr == tsdPtr->lastHandler) {
327	    tsdPtr->lastHandler = prevPtr;
328	}
329    }
330    Tcl_MutexUnlock(&tsdPtr->asyncMutex);
331    ckfree((char *) asyncPtr);
332}
333
334/*
335 *----------------------------------------------------------------------
336 *
337 * Tcl_AsyncReady --
338 *
339 *	This procedure can be used to tell whether Tcl_AsyncInvoke
340 *	needs to be called.  This procedure is the external interface
341 *	for checking the thread-specific asyncReady variable.
342 *
343 * Results:
344 * 	The return value is 1 whenever a handler is ready and is 0
345 *	when no handlers are ready.
346 *
347 * Side effects:
348 *	None.
349 *
350 *----------------------------------------------------------------------
351 */
352
353int
354Tcl_AsyncReady()
355{
356    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
357    return tsdPtr->asyncReady;
358}
359