356 lines
9.8 KiB
C
356 lines
9.8 KiB
C
|
/*
|
|||
|
* 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.
|
|||
|
*/
|
|||
|
|
|||
|
#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(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:
|
|||
|
*/
|