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