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  * SCCS: @(#) tclAsync.c 1.6 96/02/15 11:46:15
16  */
17 
18 #include "tclInt.h"
19 
20 /*
21  * One of the following structures exists for each asynchronous
22  * handler:
23  */
24 
25 typedef struct AsyncHandler {
26     int ready;				/* Non-zero means this handler should
27 					 * be invoked in the next call to
28 					 * Tcl_AsyncInvoke. */
29     struct AsyncHandler *nextPtr;	/* Next in list of all handlers for
30 					 * the process. */
31     Tcl_AsyncProc *proc;		/* Procedure to call when handler
32 					 * is invoked. */
33     ClientData clientData;		/* Value to pass to handler when it
34 					 * is invoked. */
35 } AsyncHandler;
36 
37 /*
38  * The variables below maintain a list of all existing handlers.
39  */
40 
41 static AsyncHandler *firstHandler;	/* First handler defined for process,
42 					 * or NULL if none. */
43 static AsyncHandler *lastHandler;	/* Last handler or NULL. */
44 
45 /*
46  * The variable below is set to 1 whenever a handler becomes ready and
47  * it is cleared to zero whenever Tcl_AsyncInvoke is called.  It can be
48  * checked elsewhere in the application by calling Tcl_AsyncReady to see
49  * if Tcl_AsyncInvoke should be invoked.
50  */
51 
52 static int asyncReady = 0;
53 
54 /*
55  * The variable below indicates whether Tcl_AsyncInvoke is currently
56  * working.  If so then we won't set asyncReady again until
57  * Tcl_AsyncInvoke returns.
58  */
59 
60 static int asyncActive = 0;
61 
62 /*
63  *----------------------------------------------------------------------
64  *
65  * Tcl_AsyncCreate --
66  *
67  *	This procedure creates the data structures for an asynchronous
68  *	handler, so that no memory has to be allocated when the handler
69  *	is activated.
70  *
71  * Results:
72  *	The return value is a token for the handler, which can be used
73  *	to activate it later on.
74  *
75  * Side effects:
76  *	Information about the handler is recorded.
77  *
78  *----------------------------------------------------------------------
79  */
80 
81 Tcl_AsyncHandler
Tcl_AsyncCreate(proc,clientData)82 Tcl_AsyncCreate(proc, clientData)
83     Tcl_AsyncProc *proc;		/* Procedure to call when handler
84 					 * is invoked. */
85     ClientData clientData;		/* Argument to pass to handler. */
86 {
87     AsyncHandler *asyncPtr;
88 
89     asyncPtr = (AsyncHandler *) ckalloc(sizeof(AsyncHandler));
90     asyncPtr->ready = 0;
91     asyncPtr->nextPtr = NULL;
92     asyncPtr->proc = proc;
93     asyncPtr->clientData = clientData;
94     if (firstHandler == NULL) {
95 	firstHandler = asyncPtr;
96     } else {
97 	lastHandler->nextPtr = asyncPtr;
98     }
99     lastHandler = asyncPtr;
100     return (Tcl_AsyncHandler) asyncPtr;
101 }
102 
103 /*
104  *----------------------------------------------------------------------
105  *
106  * Tcl_AsyncMark --
107  *
108  *	This procedure is called to request that an asynchronous handler
109  *	be invoked as soon as possible.  It's typically called from
110  *	an interrupt handler, where it isn't safe to do anything that
111  *	depends on or modifies application state.
112  *
113  * Results:
114  *	None.
115  *
116  * Side effects:
117  *	The handler gets marked for invocation later.
118  *
119  *----------------------------------------------------------------------
120  */
121 
122 void
Tcl_AsyncMark(async)123 Tcl_AsyncMark(async)
124     Tcl_AsyncHandler async;		/* Token for handler. */
125 {
126     ((AsyncHandler *) async)->ready = 1;
127     if (!asyncActive) {
128 	asyncReady = 1;
129     }
130 }
131 
132 /*
133  *----------------------------------------------------------------------
134  *
135  * Tcl_AsyncInvoke --
136  *
137  *	This procedure is called at a "safe" time at background level
138  *	to invoke any active asynchronous handlers.
139  *
140  * Results:
141  *	The return value is a normal Tcl result, which is intended to
142  *	replace the code argument as the current completion code for
143  *	interp.
144  *
145  * Side effects:
146  *	Depends on the handlers that are active.
147  *
148  *----------------------------------------------------------------------
149  */
150 
151 int
Tcl_AsyncInvoke(interp,code)152 Tcl_AsyncInvoke(interp, code)
153     Tcl_Interp *interp;			/* If invoked from Tcl_Eval just after
154 					 * completing a command, points to
155 					 * interpreter.  Otherwise it is
156 					 * NULL. */
157     int code; 				/* If interp is non-NULL, this gives
158 					 * completion code from command that
159 					 * just completed. */
160 {
161     AsyncHandler *asyncPtr;
162 
163     if (asyncReady == 0) {
164 	return code;
165     }
166     asyncReady = 0;
167     asyncActive = 1;
168     if (interp == NULL) {
169 	code = 0;
170     }
171 
172     /*
173      * Make one or more passes over the list of handlers, invoking
174      * at most one handler in each pass.  After invoking a handler,
175      * go back to the start of the list again so that (a) if a new
176      * higher-priority handler gets marked while executing a lower
177      * priority handler, we execute the higher-priority handler
178      * next, and (b) if a handler gets deleted during the execution
179      * of a handler, then the list structure may change so it isn't
180      * safe to continue down the list anyway.
181      */
182 
183     while (1) {
184 	for (asyncPtr = firstHandler; asyncPtr != NULL;
185 		asyncPtr = asyncPtr->nextPtr) {
186 	    if (asyncPtr->ready) {
187 		break;
188 	    }
189 	}
190 	if (asyncPtr == NULL) {
191 	    break;
192 	}
193 	asyncPtr->ready = 0;
194 	code = (*asyncPtr->proc)(asyncPtr->clientData, interp, code);
195     }
196     asyncActive = 0;
197     return code;
198 }
199 
200 /*
201  *----------------------------------------------------------------------
202  *
203  * Tcl_AsyncDelete --
204  *
205  *	Frees up all the state for an asynchronous handler.  The handler
206  *	should never be used again.
207  *
208  * Results:
209  *	None.
210  *
211  * Side effects:
212  *	The state associated with the handler is deleted.
213  *
214  *----------------------------------------------------------------------
215  */
216 
217 void
Tcl_AsyncDelete(async)218 Tcl_AsyncDelete(async)
219     Tcl_AsyncHandler async;		/* Token for handler to delete. */
220 {
221     AsyncHandler *asyncPtr = (AsyncHandler *) async;
222     AsyncHandler *prevPtr;
223 
224     if (firstHandler == asyncPtr) {
225 	firstHandler = asyncPtr->nextPtr;
226 	if (firstHandler == NULL) {
227 	    lastHandler = NULL;
228 	}
229     } else {
230 	prevPtr = firstHandler;
231 	while (prevPtr->nextPtr != asyncPtr) {
232 	    prevPtr = prevPtr->nextPtr;
233 	}
234 	prevPtr->nextPtr = asyncPtr->nextPtr;
235 	if (lastHandler == asyncPtr) {
236 	    lastHandler = prevPtr;
237 	}
238     }
239     ckfree((char *) asyncPtr);
240 }
241 
242 /*
243  *----------------------------------------------------------------------
244  *
245  * Tcl_AsyncReady --
246  *
247  *	This procedure can be used to tell whether Tcl_AsyncInvoke
248  *	needs to be called.  This procedure is the external interface
249  *	for checking the internal asyncReady variable.
250  *
251  * Results:
252  * 	The return value is 1 whenever a handler is ready and is 0
253  *	when no handlers are ready.
254  *
255  * Side effects:
256  *	None.
257  *
258  *----------------------------------------------------------------------
259  */
260 
261 int
Tcl_AsyncReady()262 Tcl_AsyncReady()
263 {
264     return asyncReady;
265 }
266