/*
* 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.
*
* 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.13.2.3 2008/05/03 19:31:28 das Exp $
*/
#include "tclInt.h"
/* Forward declaration */
struct ThreadSpecificData;
/*
* 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. */
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;
/*
*----------------------------------------------------------------------
*
* 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.
*
*----------------------------------------------------------------------
*/
void
TclFinalizeAsync(void)
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (tsdPtr->asyncMutex != NULL) {
Tcl_MutexFinalize(&tsdPtr->asyncMutex);
}
}
/*
*----------------------------------------------------------------------
*
* 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.
*
* Results:
* 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.
*
*----------------------------------------------------------------------
*/
Tcl_AsyncHandler
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->ready = 0;
asyncPtr->nextPtr = NULL;
asyncPtr->proc = proc;
asyncPtr->clientData = clientData;
asyncPtr->originTsd = tsdPtr;
asyncPtr->originThrdId = Tcl_GetCurrentThread();
Tcl_MutexLock(&tsdPtr->asyncMutex);
if (tsdPtr->firstHandler == NULL) {
tsdPtr->firstHandler = asyncPtr;
} else {
tsdPtr->lastHandler->nextPtr = asyncPtr;
}
tsdPtr->lastHandler = asyncPtr;
Tcl_MutexUnlock(&tsdPtr->asyncMutex);
return (Tcl_AsyncHandler) asyncPtr;
}
/*
*----------------------------------------------------------------------
*
* 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.
*
* Results:
* None.
*
* Side effects:
* The handler gets marked for invocation later.
*
*----------------------------------------------------------------------
*/
void
Tcl_AsyncMark(
Tcl_AsyncHandler async) /* Token for handler. */
{
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(&token->originTsd->asyncMutex);
}
/*
*----------------------------------------------------------------------
*
* Tcl_AsyncInvoke --
*
* 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.
*
* Side effects:
* Depends on the handlers that are active.
*
*----------------------------------------------------------------------
*/
int
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;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
Tcl_MutexLock(&tsdPtr->asyncMutex);
if (tsdPtr->asyncReady == 0) {
Tcl_MutexUnlock(&tsdPtr->asyncMutex);
return code;
}
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
* safe to continue down the list anyway.
*/
while (1) {
for (asyncPtr = tsdPtr->firstHandler; asyncPtr != NULL;
asyncPtr = asyncPtr->nextPtr) {
if (asyncPtr->ready) {
break;
}
}
if (asyncPtr == NULL) {
break;
}
asyncPtr->ready = 0;
Tcl_MutexUnlock(&tsdPtr->asyncMutex);
code = (*asyncPtr->proc)(asyncPtr->clientData, interp, code);
Tcl_MutexLock(&tsdPtr->asyncMutex);
}
tsdPtr->asyncActive = 0;
Tcl_MutexUnlock(&tsdPtr->asyncMutex);
return code;
}
/*
*----------------------------------------------------------------------
*
* Tcl_AsyncDelete --
*
* Frees up all the state for an asynchronous handler. The handler should
* never be used again.
*
* Results:
* None.
*
* 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(
Tcl_AsyncHandler async) /* Token for handler to delete. */
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
AsyncHandler *asyncPtr = (AsyncHandler *) async;
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(&tsdPtr->asyncMutex);
if (tsdPtr->firstHandler != NULL) {
prevPtr = thisPtr = tsdPtr->firstHandler;
while (thisPtr != NULL && thisPtr != asyncPtr) {
prevPtr = thisPtr;
thisPtr = thisPtr->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;
}
if (asyncPtr == tsdPtr->lastHandler) {
tsdPtr->lastHandler = prevPtr;
}
}
Tcl_MutexUnlock(&tsdPtr->asyncMutex);
ckfree((char *) asyncPtr);
}
/*
*----------------------------------------------------------------------
*
* 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
* thread-specific asyncReady variable.
*
* Results:
* The return value is 1 whenever a handler is ready and is 0 when no
* handlers are ready.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_AsyncReady(void)
{
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:
*/
|