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