diff options
Diffstat (limited to 'generic/tclAsync.c')
| -rw-r--r-- | generic/tclAsync.c | 329 | 
1 files changed, 202 insertions, 127 deletions
| diff --git a/generic/tclAsync.c b/generic/tclAsync.c index fc80385..14804e4 100644 --- a/generic/tclAsync.c +++ b/generic/tclAsync.c @@ -1,66 +1,94 @@ -/*  +/*   * tclAsync.c --   * - *	This file provides low-level support needed to invoke signal - *	handlers in a safe way.  The code here doesn't actually handle - *	signals, though.  This code is based on proposals made by - *	Mark Diekhans and Don Libes. + *	This file provides low-level support needed to invoke signal handlers + *	in a safe way. The code here doesn't actually handle signals, though. + *	This code is based on proposals made by Mark Diekhans and Don Libes.   *   * Copyright (c) 1993 The Regents of the University of California.   * Copyright (c) 1994 Sun Microsystems, Inc.   * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * RCS: @(#) $Id: tclAsync.c,v 1.4 1999/04/16 00:46:42 stanton Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES.   */  #include "tclInt.h" -#include "tclPort.h" + +/* Forward declaration */ +struct ThreadSpecificData;  /* - * One of the following structures exists for each asynchronous - * handler: + * One of the following structures exists for each asynchronous handler:   */  typedef struct AsyncHandler { -    int ready;				/* Non-zero means this handler should -					 * be invoked in the next call to -					 * Tcl_AsyncInvoke. */ -    struct AsyncHandler *nextPtr;	/* Next in list of all handlers for -					 * the process. */ -    Tcl_AsyncProc *proc;		/* Procedure to call when handler -					 * is invoked. */ -    ClientData clientData;		/* Value to pass to handler when it -					 * is invoked. */ +    int ready;			/* Non-zero means this handler should be +				 * invoked in the next call to +				 * Tcl_AsyncInvoke. */ +    struct AsyncHandler *nextPtr; +				/* Next in list of all handlers for the +				 * process. */ +    Tcl_AsyncProc *proc;	/* Procedure to call when handler is +				 * invoked. */ +    ClientData clientData;	/* Value to pass to handler when it is +				 * invoked. */ +    struct ThreadSpecificData *originTsd; +				/* Used in Tcl_AsyncMark to modify thread- +				 * specific data from outside the thread it is +				 * associated to. */ +    Tcl_ThreadId originThrdId;	/* Origin thread where this token was created +				 * and where it will be yielded. */  } AsyncHandler; +typedef struct ThreadSpecificData { +    /* +     * The variables below maintain a list of all existing handlers specific +     * to the calling thread. +     */ +    AsyncHandler *firstHandler;	/* First handler defined for process, or NULL +				 * if none. */ +    AsyncHandler *lastHandler;	/* Last handler or NULL. */ +    int asyncReady;		/* This is set to 1 whenever a handler becomes +				 * ready and it is cleared to zero whenever +				 * Tcl_AsyncInvoke is called. It can be +				 * checked elsewhere in the application by +				 * calling Tcl_AsyncReady to see if +				 * Tcl_AsyncInvoke should be invoked. */ +    int asyncActive;		/* Indicates whether Tcl_AsyncInvoke is +				 * currently working. If so then we won't set +				 * asyncReady again until Tcl_AsyncInvoke +				 * returns. */ +    Tcl_Mutex asyncMutex;	/* Thread-specific AsyncHandler linked-list +				 * lock */ +} ThreadSpecificData; +static Tcl_ThreadDataKey dataKey; +  /* - * The variables below maintain a list of all existing handlers. - */ - -static AsyncHandler *firstHandler;	/* First handler defined for process, -					 * or NULL if none. */ -static AsyncHandler *lastHandler;	/* Last handler or NULL. */ - -TCL_DECLARE_MUTEX(asyncMutex)           /* Process-wide async handler lock */ - -/* - * The variable below is set to 1 whenever a handler becomes ready and - * it is cleared to zero whenever Tcl_AsyncInvoke is called.  It can be - * checked elsewhere in the application by calling Tcl_AsyncReady to see - * if Tcl_AsyncInvoke should be invoked. + *---------------------------------------------------------------------- + * + * TclFinalizeAsync -- + * + *	Finalizes the mutex in the thread local data structure for the async + *	subsystem. + * + * Results: + *	None. + * + * Side effects: + *	Forgets knowledge of the mutex should it have been created. + * + *----------------------------------------------------------------------   */ -static int asyncReady = 0; - -/* - * The variable below indicates whether Tcl_AsyncInvoke is currently - * working.  If so then we won't set asyncReady again until - * Tcl_AsyncInvoke returns. - */ +void +TclFinalizeAsync(void) +{ +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); -static int asyncActive = 0; +    if (tsdPtr->asyncMutex != NULL) { +	Tcl_MutexFinalize(&tsdPtr->asyncMutex); +    } +}  /*   *---------------------------------------------------------------------- @@ -68,12 +96,12 @@ static int asyncActive = 0;   * Tcl_AsyncCreate --   *   *	This procedure creates the data structures for an asynchronous - *	handler, so that no memory has to be allocated when the handler - *	is activated. + *	handler, so that no memory has to be allocated when the handler is + *	activated.   *   * Results: - *	The return value is a token for the handler, which can be used - *	to activate it later on. + *	The return value is a token for the handler, which can be used to + *	activate it later on.   *   * Side effects:   *	Information about the handler is recorded. @@ -82,26 +110,30 @@ static int asyncActive = 0;   */  Tcl_AsyncHandler -Tcl_AsyncCreate(proc, clientData) -    Tcl_AsyncProc *proc;		/* Procedure to call when handler -					 * is invoked. */ -    ClientData clientData;		/* Argument to pass to handler. */ +Tcl_AsyncCreate( +    Tcl_AsyncProc *proc,	/* Procedure to call when handler is +				 * invoked. */ +    ClientData clientData)	/* Argument to pass to handler. */  {      AsyncHandler *asyncPtr; +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); -    asyncPtr = (AsyncHandler *) ckalloc(sizeof(AsyncHandler)); +    asyncPtr = ckalloc(sizeof(AsyncHandler));      asyncPtr->ready = 0;      asyncPtr->nextPtr = NULL;      asyncPtr->proc = proc;      asyncPtr->clientData = clientData; -    Tcl_MutexLock(&asyncMutex); -    if (firstHandler == NULL) { -	firstHandler = asyncPtr; +    asyncPtr->originTsd = tsdPtr; +    asyncPtr->originThrdId = Tcl_GetCurrentThread(); + +    Tcl_MutexLock(&tsdPtr->asyncMutex); +    if (tsdPtr->firstHandler == NULL) { +	tsdPtr->firstHandler = asyncPtr;      } else { -	lastHandler->nextPtr = asyncPtr; +	tsdPtr->lastHandler->nextPtr = asyncPtr;      } -    lastHandler = asyncPtr; -    Tcl_MutexUnlock(&asyncMutex); +    tsdPtr->lastHandler = asyncPtr; +    Tcl_MutexUnlock(&tsdPtr->asyncMutex);      return (Tcl_AsyncHandler) asyncPtr;  } @@ -110,10 +142,10 @@ Tcl_AsyncCreate(proc, clientData)   *   * Tcl_AsyncMark --   * - *	This procedure is called to request that an asynchronous handler - *	be invoked as soon as possible.  It's typically called from - *	an interrupt handler, where it isn't safe to do anything that - *	depends on or modifies application state. + *	This procedure is called to request that an asynchronous handler be + *	invoked as soon as possible. It's typically called from an interrupt + *	handler, where it isn't safe to do anything that depends on or + *	modifies application state.   *   * Results:   *	None. @@ -125,16 +157,18 @@ Tcl_AsyncCreate(proc, clientData)   */  void -Tcl_AsyncMark(async) -    Tcl_AsyncHandler async;		/* Token for handler. */ +Tcl_AsyncMark( +    Tcl_AsyncHandler async)		/* Token for handler. */  { -    Tcl_MutexLock(&asyncMutex); -    ((AsyncHandler *) async)->ready = 1; -    if (!asyncActive) { -	asyncReady = 1; -	TclpAsyncMark(async); +    AsyncHandler *token = (AsyncHandler *) async; + +    Tcl_MutexLock(&token->originTsd->asyncMutex); +    token->ready = 1; +    if (!token->originTsd->asyncActive) { +	token->originTsd->asyncReady = 1; +	Tcl_ThreadAlert(token->originThrdId);      } -    Tcl_MutexUnlock(&asyncMutex); +    Tcl_MutexUnlock(&token->originTsd->asyncMutex);  }  /* @@ -142,13 +176,12 @@ Tcl_AsyncMark(async)   *   * Tcl_AsyncInvoke --   * - *	This procedure is called at a "safe" time at background level - *	to invoke any active asynchronous handlers. + *	This procedure is called at a "safe" time at background level to + *	invoke any active asynchronous handlers.   *   * Results: - *	The return value is a normal Tcl result, which is intended to - *	replace the code argument as the current completion code for - *	interp. + *	The return value is a normal Tcl result, which is intended to replace + *	the code argument as the current completion code for interp.   *   * Side effects:   *	Depends on the handlers that are active. @@ -157,41 +190,41 @@ Tcl_AsyncMark(async)   */  int -Tcl_AsyncInvoke(interp, code) -    Tcl_Interp *interp;			/* If invoked from Tcl_Eval just after -					 * completing a command, points to -					 * interpreter.  Otherwise it is -					 * NULL. */ -    int code; 				/* If interp is non-NULL, this gives -					 * completion code from command that -					 * just completed. */ +Tcl_AsyncInvoke( +    Tcl_Interp *interp,		/* If invoked from Tcl_Eval just after +				 * completing a command, points to +				 * interpreter. Otherwise it is NULL. */ +    int code)			/* If interp is non-NULL, this gives +				 * completion code from command that just +				 * completed. */  {      AsyncHandler *asyncPtr; -    Tcl_MutexLock(&asyncMutex); +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); -    if (asyncReady == 0) { -	Tcl_MutexUnlock(&asyncMutex); +    Tcl_MutexLock(&tsdPtr->asyncMutex); + +    if (tsdPtr->asyncReady == 0) { +	Tcl_MutexUnlock(&tsdPtr->asyncMutex);  	return code;      } -    asyncReady = 0; -    asyncActive = 1; +    tsdPtr->asyncReady = 0; +    tsdPtr->asyncActive = 1;      if (interp == NULL) {  	code = 0;      }      /* -     * Make one or more passes over the list of handlers, invoking -     * at most one handler in each pass.  After invoking a handler, -     * go back to the start of the list again so that (a) if a new -     * higher-priority handler gets marked while executing a lower -     * priority handler, we execute the higher-priority handler -     * next, and (b) if a handler gets deleted during the execution -     * of a handler, then the list structure may change so it isn't +     * Make one or more passes over the list of handlers, invoking at most one +     * handler in each pass. After invoking a handler, go back to the start of +     * the list again so that (a) if a new higher-priority handler gets marked +     * while executing a lower priority handler, we execute the higher- +     * priority handler next, and (b) if a handler gets deleted during the +     * execution of a handler, then the list structure may change so it isn't       * safe to continue down the list anyway.       */      while (1) { -	for (asyncPtr = firstHandler; asyncPtr != NULL; +	for (asyncPtr = tsdPtr->firstHandler; asyncPtr != NULL;  		asyncPtr = asyncPtr->nextPtr) {  	    if (asyncPtr->ready) {  		break; @@ -201,12 +234,12 @@ Tcl_AsyncInvoke(interp, code)  	    break;  	}  	asyncPtr->ready = 0; -	Tcl_MutexUnlock(&asyncMutex); -	code = (*asyncPtr->proc)(asyncPtr->clientData, interp, code); -	Tcl_MutexLock(&asyncMutex); +	Tcl_MutexUnlock(&tsdPtr->asyncMutex); +	code = asyncPtr->proc(asyncPtr->clientData, interp, code); +	Tcl_MutexLock(&tsdPtr->asyncMutex);      } -    asyncActive = 0; -    Tcl_MutexUnlock(&asyncMutex); +    tsdPtr->asyncActive = 0; +    Tcl_MutexUnlock(&tsdPtr->asyncMutex);      return code;  } @@ -215,8 +248,8 @@ Tcl_AsyncInvoke(interp, code)   *   * Tcl_AsyncDelete --   * - *	Frees up all the state for an asynchronous handler.  The handler - *	should never be used again. + *	Frees up all the state for an asynchronous handler. The handler should + *	never be used again.   *   * Results:   *	None. @@ -224,34 +257,60 @@ Tcl_AsyncInvoke(interp, code)   * Side effects:   *	The state associated with the handler is deleted.   * + *	Failure to locate the handler in current thread private list + *	of async handlers will result in panic; exception: the list + *	is already empty (potential trouble?). + *	Consequently, threads should create and delete handlers + *	themselves.  I.e. a handler created by one should not be + *	deleted by some other thread. + *   *----------------------------------------------------------------------   */  void -Tcl_AsyncDelete(async) -    Tcl_AsyncHandler async;		/* Token for handler to delete. */ +Tcl_AsyncDelete( +    Tcl_AsyncHandler async)		/* Token for handler to delete. */  { +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);      AsyncHandler *asyncPtr = (AsyncHandler *) async; -    AsyncHandler *prevPtr; +    AsyncHandler *prevPtr, *thisPtr; + +    /* +     * Assure early handling of the constraint +     */ + +    if (asyncPtr->originThrdId != Tcl_GetCurrentThread()) { +	Tcl_Panic("Tcl_AsyncDelete: async handler deleted by the wrong thread"); +    } + +    /* +     * If we come to this point when TSD's for the current +     * thread have already been garbage-collected, we are +     * in the _serious_ trouble. OTOH, we tolerate calling +     * with already cleaned-up handler list (should we?). +     */ -    Tcl_MutexLock(&asyncMutex); -    if (firstHandler == asyncPtr) { -	firstHandler = asyncPtr->nextPtr; -	if (firstHandler == NULL) { -	    lastHandler = NULL; +    Tcl_MutexLock(&tsdPtr->asyncMutex); +    if (tsdPtr->firstHandler != NULL) { +	prevPtr = thisPtr = tsdPtr->firstHandler; +	while (thisPtr != NULL && thisPtr != asyncPtr) { +	    prevPtr = thisPtr; +	    thisPtr = thisPtr->nextPtr;  	} -    } else { -	prevPtr = firstHandler; -	while (prevPtr->nextPtr != asyncPtr) { -	    prevPtr = prevPtr->nextPtr; +	if (thisPtr == NULL) { +	    Tcl_Panic("Tcl_AsyncDelete: cannot find async handler"); +	} +	if (asyncPtr == tsdPtr->firstHandler) { +	    tsdPtr->firstHandler = asyncPtr->nextPtr; +	} else { +	    prevPtr->nextPtr = asyncPtr->nextPtr;  	} -	prevPtr->nextPtr = asyncPtr->nextPtr; -	if (lastHandler == asyncPtr) { -	    lastHandler = prevPtr; +	if (asyncPtr == tsdPtr->lastHandler) { +	    tsdPtr->lastHandler = prevPtr;  	}      } -    Tcl_MutexUnlock(&asyncMutex); -    ckfree((char *) asyncPtr); +    Tcl_MutexUnlock(&tsdPtr->asyncMutex); +    ckfree(asyncPtr);  }  /* @@ -259,13 +318,13 @@ Tcl_AsyncDelete(async)   *   * Tcl_AsyncReady --   * - *	This procedure can be used to tell whether Tcl_AsyncInvoke - *	needs to be called.  This procedure is the external interface - *	for checking the internal asyncReady variable. + *	This procedure can be used to tell whether Tcl_AsyncInvoke needs to be + *	called. This procedure is the external interface for checking the + *	thread-specific asyncReady variable.   *   * Results: - * 	The return value is 1 whenever a handler is ready and is 0 - *	when no handlers are ready. + *	The return value is 1 whenever a handler is ready and is 0 when no + *	handlers are ready.   *   * Side effects:   *	None. @@ -274,7 +333,23 @@ Tcl_AsyncDelete(async)   */  int -Tcl_AsyncReady() +Tcl_AsyncReady(void)  { -    return asyncReady; +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    return tsdPtr->asyncReady;  } + +int * +TclGetAsyncReadyPtr(void) +{ +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    return &(tsdPtr->asyncReady); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ | 
