mirror of
https://github.com/tcltk/tcl.git
synced 2026-05-29 00:27:49 +08:00
Some checks failed
Linux / plan (push) Has been cancelled
macOS / plan (push) Has been cancelled
macOS / xcode (push) Has been cancelled
Build Binaries / Linux (push) Has been cancelled
Build Binaries / macOS (push) Has been cancelled
Build Binaries / Windows (push) Has been cancelled
Windows / plan (push) Has been cancelled
Linux / gcc (push) Has been cancelled
macOS / clang (push) Has been cancelled
Build Binaries / Combine Artifacts (prototype) (push) Has been cancelled
Windows / msvc (push) Has been cancelled
Windows / gcc (push) Has been cancelled
451 lines
12 KiB
C
451 lines
12 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 © 1993 The Regents of the University of California.
|
||
* Copyright © 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 {
|
||
signed char ready; /* Non-zero means this handler should be
|
||
* invoked in the next call to
|
||
* Tcl_AsyncInvoke. */
|
||
struct AsyncHandler *nextPtr, *prevPtr;
|
||
/* Next, previous in list of all handlers
|
||
* for the process. */
|
||
Tcl_AsyncProc *proc; /* Procedure to call when handler is
|
||
* invoked. */
|
||
void *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. */
|
||
void *notifierData; /* Platform notifier data or NULL. */
|
||
} AsyncHandler;
|
||
|
||
typedef struct ThreadSpecificData {
|
||
bool 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. */
|
||
bool asyncActive; /* Indicates whether Tcl_AsyncInvoke is
|
||
* currently working. If so then we won't set
|
||
* asyncReady again until Tcl_AsyncInvoke
|
||
* returns. */
|
||
} ThreadSpecificData;
|
||
static Tcl_ThreadDataKey dataKey;
|
||
|
||
/* Mutex to protect linked-list of AsyncHandlers in the process. */
|
||
TCL_DECLARE_MUTEX(asyncMutex)
|
||
|
||
/* List of all existing handlers of the process. */
|
||
static AsyncHandler *firstHandler = NULL;
|
||
static AsyncHandler *lastHandler = NULL;
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclFinalizeAsync --
|
||
*
|
||
* Finalizes the thread local data structure for the async
|
||
* subsystem.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Cleans up left-over async handlers for the calling thread.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclFinalizeAsync(void)
|
||
{
|
||
AsyncHandler *token, *toDelete = NULL;
|
||
Tcl_ThreadId self = Tcl_GetCurrentThread();
|
||
|
||
Tcl_MutexLock(&asyncMutex);
|
||
for (token = firstHandler; token != NULL;) {
|
||
AsyncHandler *nextToken = token->nextPtr;
|
||
|
||
if (token->originThrdId == self) {
|
||
if (token->prevPtr == NULL) {
|
||
firstHandler = token->nextPtr;
|
||
if (firstHandler == NULL) {
|
||
lastHandler = NULL;
|
||
break;
|
||
}
|
||
} else {
|
||
token->prevPtr->nextPtr = token->nextPtr;
|
||
if (token == lastHandler) {
|
||
lastHandler = token->prevPtr;
|
||
}
|
||
}
|
||
if (token->nextPtr != NULL) {
|
||
token->nextPtr->prevPtr = token->prevPtr;
|
||
}
|
||
token->nextPtr = toDelete;
|
||
token->prevPtr = NULL;
|
||
toDelete = token;
|
||
}
|
||
token = nextToken;
|
||
}
|
||
Tcl_MutexUnlock(&asyncMutex);
|
||
while (toDelete != NULL) {
|
||
token = toDelete;
|
||
toDelete = toDelete->nextPtr;
|
||
Tcl_Free(token);
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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. */
|
||
void *clientData) /* Argument to pass to handler. */
|
||
{
|
||
AsyncHandler *asyncPtr;
|
||
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
|
||
|
||
asyncPtr = (AsyncHandler*)Tcl_Alloc(sizeof(AsyncHandler));
|
||
asyncPtr->ready = 0;
|
||
asyncPtr->nextPtr = NULL;
|
||
asyncPtr->prevPtr = NULL;
|
||
asyncPtr->proc = proc;
|
||
asyncPtr->clientData = clientData;
|
||
asyncPtr->originTsd = tsdPtr;
|
||
asyncPtr->originThrdId = Tcl_GetCurrentThread();
|
||
asyncPtr->notifierData = TclpNotifierData();
|
||
|
||
Tcl_MutexLock(&asyncMutex);
|
||
if (firstHandler == NULL) {
|
||
firstHandler = asyncPtr;
|
||
} else {
|
||
asyncPtr->prevPtr = lastHandler;
|
||
lastHandler->nextPtr = asyncPtr;
|
||
}
|
||
lastHandler = asyncPtr;
|
||
Tcl_MutexUnlock(&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(&asyncMutex);
|
||
token->ready = 1;
|
||
if (!token->originTsd->asyncActive) {
|
||
token->originTsd->asyncReady = true;
|
||
Tcl_ThreadAlert(token->originThrdId);
|
||
}
|
||
Tcl_MutexUnlock(&asyncMutex);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_AsyncMarkFromSignal --
|
||
*
|
||
* This procedure is similar to Tcl_AsyncMark but must be used
|
||
* in POSIX signal contexts. In addition to Tcl_AsyncMark the
|
||
* signal number is passed.
|
||
*
|
||
* Results:
|
||
* True, when the handler will be marked, false otherwise.
|
||
*
|
||
* Side effects:
|
||
* The handler gets marked for invocation later.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
Tcl_AsyncMarkFromSignal(
|
||
Tcl_AsyncHandler async, /* Token for handler. */
|
||
int sigNumber) /* Signal number. */
|
||
{
|
||
#if TCL_THREADS
|
||
AsyncHandler *token = (AsyncHandler *) async;
|
||
|
||
return TclAsyncNotifier(sigNumber, token->originThrdId,
|
||
token->notifierData, &token->ready, -1) ? 1 : 0;
|
||
#else
|
||
(void)sigNumber;
|
||
|
||
Tcl_AsyncMark(async);
|
||
return 1;
|
||
#endif
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclAsyncMarkFromNotifier --
|
||
*
|
||
* This procedure is called from the notifier thread and
|
||
* invokes Tcl_AsyncMark for specifically marked handlers.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Handlers get marked for invocation later.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclAsyncMarkFromNotifier(void)
|
||
{
|
||
AsyncHandler *token;
|
||
|
||
Tcl_MutexLock(&asyncMutex);
|
||
for (token = firstHandler; token != NULL;
|
||
token = token->nextPtr) {
|
||
if (token->ready == -1) {
|
||
token->ready = 1;
|
||
if (!token->originTsd->asyncActive) {
|
||
token->originTsd->asyncReady = true;
|
||
Tcl_ThreadAlert(token->originThrdId);
|
||
}
|
||
}
|
||
}
|
||
Tcl_MutexUnlock(&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_ThreadId self = Tcl_GetCurrentThread();
|
||
|
||
Tcl_MutexLock(&asyncMutex);
|
||
|
||
if (!tsdPtr->asyncReady) {
|
||
Tcl_MutexUnlock(&asyncMutex);
|
||
return code;
|
||
}
|
||
tsdPtr->asyncReady = false;
|
||
tsdPtr->asyncActive = true;
|
||
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 = firstHandler; asyncPtr != NULL;
|
||
asyncPtr = asyncPtr->nextPtr) {
|
||
if (asyncPtr->originThrdId != self) {
|
||
continue;
|
||
}
|
||
if (asyncPtr->ready) {
|
||
break;
|
||
}
|
||
}
|
||
if (asyncPtr == NULL) {
|
||
break;
|
||
}
|
||
asyncPtr->ready = 0;
|
||
Tcl_MutexUnlock(&asyncMutex);
|
||
code = asyncPtr->proc(asyncPtr->clientData, interp, code);
|
||
Tcl_MutexLock(&asyncMutex);
|
||
}
|
||
tsdPtr->asyncActive = false;
|
||
Tcl_MutexUnlock(&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. */
|
||
{
|
||
AsyncHandler *asyncPtr = (AsyncHandler *) async;
|
||
|
||
/*
|
||
* Assure early handling of the constraint
|
||
*/
|
||
|
||
if (asyncPtr->originThrdId != Tcl_GetCurrentThread()) {
|
||
Tcl_Panic("Tcl_AsyncDelete: async handler deleted by the wrong thread");
|
||
}
|
||
|
||
Tcl_MutexLock(&asyncMutex);
|
||
if (asyncPtr->prevPtr == NULL) {
|
||
firstHandler = asyncPtr->nextPtr;
|
||
if (firstHandler == NULL) {
|
||
lastHandler = NULL;
|
||
}
|
||
} else {
|
||
asyncPtr->prevPtr->nextPtr = asyncPtr->nextPtr;
|
||
if (asyncPtr == lastHandler) {
|
||
lastHandler = asyncPtr->prevPtr;
|
||
}
|
||
}
|
||
if (asyncPtr->nextPtr != NULL) {
|
||
asyncPtr->nextPtr->prevPtr = asyncPtr->prevPtr;
|
||
}
|
||
Tcl_MutexUnlock(&asyncMutex);
|
||
Tcl_Free(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 ? 1 : 0;
|
||
}
|
||
|
||
bool *
|
||
TclGetAsyncReadyPtr(void)
|
||
{
|
||
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
|
||
return &tsdPtr->asyncReady;
|
||
}
|
||
|
||
/*
|
||
* Local Variables:
|
||
* mode: c
|
||
* c-basic-offset: 4
|
||
* fill-column: 78
|
||
* End:
|
||
*/
|