Index: doc/Async.3 ================================================================== --- doc/Async.3 +++ doc/Async.3 @@ -7,23 +7,28 @@ '\" .TH Tcl_AsyncCreate 3 7.0 Tcl "Tcl Library Procedures" .so man.macros .BS .SH NAME -Tcl_AsyncCreate, Tcl_AsyncMark, Tcl_AsyncInvoke, Tcl_AsyncDelete, Tcl_AsyncReady \- handle asynchronous events +Tcl_AsyncCreate, Tcl_AsyncMark, Tcl_AsyncMarkFromSignal, Tcl_AsyncInvoke, Tcl_AsyncDelete, Tcl_AsyncReady \- handle asynchronous events .SH SYNOPSIS .nf \fB#include \fR .sp Tcl_AsyncHandler \fBTcl_AsyncCreate\fR(\fIproc, clientData\fR) .sp +void \fBTcl_AsyncMark\fR(\fIasync\fR) .sp +int +\fBTcl_AsyncMarkFromSignal\fR(\fIasync\fR, \fIsigNumber\fR) +.sp int \fBTcl_AsyncInvoke\fR(\fIinterp, code\fR) .sp +void \fBTcl_AsyncDelete\fR(\fIasync\fR) .sp int \fBTcl_AsyncReady\fR() .SH ARGUMENTS @@ -32,10 +37,12 @@ Procedure to invoke to handle an asynchronous event. .AP ClientData clientData in One-word value to pass to \fIproc\fR. .AP Tcl_AsyncHandler async in Token for asynchronous event handler. +.AP int sigNumber in +POSIX signal number, when used in a signal context. .AP Tcl_Interp *interp in Tcl interpreter in which command was being evaluated when handler was invoked, or NULL if handler was invoked when there was no interpreter active. .AP int code in @@ -58,29 +65,33 @@ occurred, then handle the event later when the world has returned to a clean state, such as after the current Tcl command completes. .PP \fBTcl_AsyncCreate\fR, \fBTcl_AsyncDelete\fR, and \fBTcl_AsyncReady\fR are thread sensitive. They access and/or set a thread-specific data -structure in the event of a core built with \fI\-\-enable\-threads\fR. The token -created by \fBTcl_AsyncCreate\fR contains the needed thread information it -was called from so that calling \fBTcl_AsyncMark\fR(\fItoken\fR) will only yield -the origin thread into the asynchronous handler. +structure in the event of a core built with \fI\-\-enable\-threads\fR. +The token created by \fBTcl_AsyncCreate\fR contains the needed thread +information it was called from so that calling \fBTcl_AsyncMarkFromSignal\fR +or \fBTcl_AsyncMark\fR with this token will only yield the origin +thread into the asynchronous handler. .PP \fBTcl_AsyncCreate\fR creates an asynchronous handler and returns a token for it. The asynchronous handler must be created before any occurrences of the asynchronous event that it is intended to handle (it is not safe to create a handler at the time of an event). When an asynchronous event occurs the code that detects the event -(such as a signal handler) should call \fBTcl_AsyncMark\fR with the -token for the handler. -\fBTcl_AsyncMark\fR will mark the handler as ready to execute, but it -will not invoke the handler immediately. -Tcl will call the \fIproc\fR associated with the handler later, when -the world is in a safe state, and \fIproc\fR can then carry out -the actions associated with the asynchronous event. +(such as a POSIX signal handler) should call \fBTcl_AsyncMarkFromSignal\fR +with the token for the handler and the POSIX signal number. The +return value of this function is true, when the handler will be +marked, false otherwise. +For non-signal contexts, \fBTcl_AsyncMark\fR serves the same purpose. +\fBTcl_AsyncMarkFromSignal\fR and \fBTcl_AsyncMark\fR will mark +the handler as ready to execute, but will not invoke the handler +immediately. Tcl will call the \fIproc\fR associated with the +handler later, when the world is in a safe state, and \fIproc\fR +can then carry out the actions associated with the asynchronous event. \fIProc\fR should have arguments and result that match the type \fBTcl_AsyncProc\fR: .PP .CS typedef int \fBTcl_AsyncProc\fR( Index: generic/tcl.decls ================================================================== --- generic/tcl.decls +++ generic/tcl.decls @@ -2424,10 +2424,15 @@ const char *Tcl_UtfPrev(const char *src, const char *start) } declare 657 { int Tcl_UniCharIsUnicode(int ch) } + +# TIP #511 +declare 660 { + int Tcl_AsyncMarkFromSignal(Tcl_AsyncHandler async, int sigNumber) +} # ----- BASELINE -- FOR -- 8.7.0 ----- # ############################################################################## Index: generic/tclAsync.c ================================================================== --- generic/tclAsync.c +++ generic/tclAsync.c @@ -23,13 +23,13 @@ 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. */ + struct AsyncHandler *nextPtr, *prevPtr; + /* Next, previous 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; @@ -36,20 +36,14 @@ /* 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. */ + ClientData notifierData; /* Platform notifier data or NULL. */ } 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 @@ -56,39 +50,74 @@ * 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; + +/* 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 mutex in the thread local data structure for the async + * Finalizes the thread local data structure for the async * subsystem. * * Results: * None. * * Side effects: - * Forgets knowledge of the mutex should it have been created. + * Cleans up left-over async handlers for the calling thread. * *---------------------------------------------------------------------- */ void TclFinalizeAsync(void) { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + AsyncHandler *token, *toDelete = NULL; + Tcl_ThreadId self = Tcl_GetCurrentThread(); + + Tcl_MutexLock(&asyncMutex); + for (token = firstHandler; token != NULL;) { + AsyncHandler *nextToken = token->nextPtr; - if (tsdPtr->asyncMutex != NULL) { - Tcl_MutexFinalize(&tsdPtr->asyncMutex); + 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; + ckfree(token); } } /* *---------------------------------------------------------------------- @@ -119,23 +148,26 @@ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); asyncPtr = (AsyncHandler*)ckalloc(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(&tsdPtr->asyncMutex); - if (tsdPtr->firstHandler == NULL) { - tsdPtr->firstHandler = asyncPtr; + Tcl_MutexLock(&asyncMutex); + if (firstHandler == NULL) { + firstHandler = asyncPtr; } else { - tsdPtr->lastHandler->nextPtr = asyncPtr; + asyncPtr->prevPtr = lastHandler; + lastHandler->nextPtr = asyncPtr; } - tsdPtr->lastHandler = asyncPtr; - Tcl_MutexUnlock(&tsdPtr->asyncMutex); + lastHandler = asyncPtr; + Tcl_MutexUnlock(&asyncMutex); return (Tcl_AsyncHandler) asyncPtr; } /* *---------------------------------------------------------------------- @@ -160,17 +192,88 @@ Tcl_AsyncMark( Tcl_AsyncHandler async) /* Token for handler. */ { AsyncHandler *token = (AsyncHandler *) async; - Tcl_MutexLock(&token->originTsd->asyncMutex); + Tcl_MutexLock(&asyncMutex); token->ready = 1; if (!token->originTsd->asyncActive) { token->originTsd->asyncReady = 1; Tcl_ThreadAlert(token->originThrdId); } - Tcl_MutexUnlock(&token->originTsd->asyncMutex); + 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); +#else + 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 = 1; + Tcl_ThreadAlert(token->originThrdId); + } + } + } + Tcl_MutexUnlock(&asyncMutex); } /* *---------------------------------------------------------------------- * @@ -198,15 +301,16 @@ * completion code from command that just * completed. */ { AsyncHandler *asyncPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + Tcl_ThreadId self = Tcl_GetCurrentThread(); - Tcl_MutexLock(&tsdPtr->asyncMutex); + Tcl_MutexLock(&asyncMutex); if (tsdPtr->asyncReady == 0) { - Tcl_MutexUnlock(&tsdPtr->asyncMutex); + Tcl_MutexUnlock(&asyncMutex); return code; } tsdPtr->asyncReady = 0; tsdPtr->asyncActive = 1; if (interp == NULL) { @@ -222,26 +326,29 @@ * 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; + 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(&tsdPtr->asyncMutex); + Tcl_MutexUnlock(&asyncMutex); code = asyncPtr->proc(asyncPtr->clientData, interp, code); - Tcl_MutexLock(&tsdPtr->asyncMutex); + Tcl_MutexLock(&asyncMutex); } tsdPtr->asyncActive = 0; - Tcl_MutexUnlock(&tsdPtr->asyncMutex); + Tcl_MutexUnlock(&asyncMutex); return code; } /* *---------------------------------------------------------------------- @@ -269,49 +376,36 @@ 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); + 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); ckfree(asyncPtr); } /* *---------------------------------------------------------------------- Index: generic/tclDecls.h ================================================================== --- generic/tclDecls.h +++ generic/tclDecls.h @@ -1937,10 +1937,15 @@ EXTERN const char * Tcl_UtfNext(const char *src); /* 656 */ EXTERN const char * Tcl_UtfPrev(const char *src, const char *start); /* 657 */ EXTERN int Tcl_UniCharIsUnicode(int ch); +/* Slot 658 is reserved */ +/* Slot 659 is reserved */ +/* 660 */ +EXTERN int Tcl_AsyncMarkFromSignal(Tcl_AsyncHandler async, + int sigNumber); typedef struct { const struct TclPlatStubs *tclPlatStubs; const struct TclIntStubs *tclIntStubs; const struct TclIntPlatStubs *tclIntPlatStubs; @@ -2630,10 +2635,13 @@ unsigned char * (*tclGetByteArrayFromObj) (Tcl_Obj *objPtr, size_t *lengthPtr); /* 653 */ int (*tcl_UtfCharComplete) (const char *src, int length); /* 654 */ const char * (*tcl_UtfNext) (const char *src); /* 655 */ const char * (*tcl_UtfPrev) (const char *src, const char *start); /* 656 */ int (*tcl_UniCharIsUnicode) (int ch); /* 657 */ + void (*reserved658)(void); + void (*reserved659)(void); + int (*tcl_AsyncMarkFromSignal) (Tcl_AsyncHandler async, int sigNumber); /* 660 */ } TclStubs; extern const TclStubs *tclStubsPtr; #ifdef __cplusplus @@ -3974,10 +3982,14 @@ (tclStubsPtr->tcl_UtfNext) /* 655 */ #define Tcl_UtfPrev \ (tclStubsPtr->tcl_UtfPrev) /* 656 */ #define Tcl_UniCharIsUnicode \ (tclStubsPtr->tcl_UniCharIsUnicode) /* 657 */ +/* Slot 658 is reserved */ +/* Slot 659 is reserved */ +#define Tcl_AsyncMarkFromSignal \ + (tclStubsPtr->tcl_AsyncMarkFromSignal) /* 660 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ Index: generic/tclInt.h ================================================================== --- generic/tclInt.h +++ generic/tclInt.h @@ -2924,10 +2924,13 @@ void *codePtr, CmdFrame *cfPtr, int cmd, int pc); MODULE_SCOPE void TclArgumentBCRelease(Tcl_Interp *interp, CmdFrame *cfPtr); MODULE_SCOPE void TclArgumentGet(Tcl_Interp *interp, Tcl_Obj *obj, CmdFrame **cfPtrPtr, int *wordPtr); +MODULE_SCOPE int TclAsyncNotifier(int sigNumber, Tcl_ThreadId threadId, + ClientData clientData, int *flagPtr, int value); +MODULE_SCOPE void TclAsyncMarkFromNotifier(void); MODULE_SCOPE double TclBignumToDouble(const void *bignum); MODULE_SCOPE int TclByteArrayMatch(const unsigned char *string, int strLen, const unsigned char *pattern, int ptnLen, int flags); MODULE_SCOPE double TclCeil(const void *a); @@ -3139,10 +3142,11 @@ MODULE_SCOPE Tcl_Obj * TclpTempFileNameForLibrary(Tcl_Interp *interp, Tcl_Obj* pathPtr); MODULE_SCOPE Tcl_Obj * TclNewFSPathObj(Tcl_Obj *dirPtr, const char *addStrRep, int len); MODULE_SCOPE void TclpAlertNotifier(ClientData clientData); +MODULE_SCOPE ClientData TclpNotifierData(void); MODULE_SCOPE void TclpServiceModeHook(int mode); MODULE_SCOPE void TclpSetTimer(const Tcl_Time *timePtr); MODULE_SCOPE int TclpWaitForEvent(const Tcl_Time *timePtr); MODULE_SCOPE void TclpCreateFileHandler(int fd, int mask, Tcl_FileProc *proc, ClientData clientData); Index: generic/tclStubInit.c ================================================================== --- generic/tclStubInit.c +++ generic/tclStubInit.c @@ -1940,8 +1940,11 @@ TclGetByteArrayFromObj, /* 653 */ Tcl_UtfCharComplete, /* 654 */ Tcl_UtfNext, /* 655 */ Tcl_UtfPrev, /* 656 */ Tcl_UniCharIsUnicode, /* 657 */ + 0, /* 658 */ + 0, /* 659 */ + Tcl_AsyncMarkFromSignal, /* 660 */ }; /* !END!: Do not edit above this line. */ Index: macosx/tclMacOSXNotify.c ================================================================== --- macosx/tclMacOSXNotify.c +++ macosx/tclMacOSXNotify.c @@ -455,10 +455,24 @@ * You must hold the notifierInitLock before accessing this variable. */ static int notifierThreadRunning; +/* + * The following static flag indicates that async handlers are pending. + */ + +#if TCL_THREADS +static int asyncPending = 0; +#endif + +/* + * Signal mask information for notifier thread. + */ +static sigset_t notifierSigMask; +static sigset_t allSigMask; + /* * This is the thread ID of the notifier thread that does select. Only valid * when notifierThreadRunning is non-zero. * * You must hold the notifierInitLock before accessing this variable. @@ -801,10 +815,19 @@ } if (!notifierThreadRunning) { int result; pthread_attr_t attr; + /* + * Arrange for the notifier thread to start with all + * signals blocked. In its mainloop it unblocks the + * signals at safe points. + */ + + sigfillset(&allSigMask); + pthread_sigmask(SIG_BLOCK, &allSigMask, ¬ifierSigMask); + pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_attr_setstacksize(&attr, 60 * 1024); result = pthread_create(¬ifierThread, &attr, @@ -812,10 +835,16 @@ pthread_attr_destroy(&attr); if (result) { Tcl_Panic("StartNotifierThread: unable to start notifier thread"); } notifierThreadRunning = 1; + + /* + * Restore original signal mask. + */ + + pthread_sigmask(SIG_SETMASK, ¬ifierSigMask, NULL); } UNLOCK_NOTIFIER_INIT; } @@ -874,10 +903,18 @@ if (result) { Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier " "thread"); } notifierThreadRunning = 0; + + /* + * If async marks are outstanding, perform actions now. + */ + if (asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } } close(receivePipe); triggerPipe = -1; } @@ -1277,10 +1314,33 @@ filePtr->proc(filePtr->clientData, mask); } } return 1; } + +/* + *---------------------------------------------------------------------- + * + * TclpNotifierData -- + * + * This function returns a ClientData pointer to be associated + * with a Tcl_AsyncHandler. + * + * Results: + * On MacOSX, returns always NULL. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +ClientData +TclpNotifierData(void) +{ + return NULL; +} /* *---------------------------------------------------------------------- * * TclpWaitForEvent -- @@ -1827,10 +1887,65 @@ } /* *---------------------------------------------------------------------- * + * TclAsyncNotifier -- + * + * This procedure sets the async mark of an async handler to a + * given value, if it is called from the notifier thread. + * + * Result: + * True, when the handler will be marked, false otherwise. + * + * Side effetcs: + * The trigger pipe is written when called from the notifier + * thread. + * + *---------------------------------------------------------------------- + */ + +int +TclAsyncNotifier( + int sigNumber, /* Signal number. */ + TCL_UNUSED(Tcl_ThreadId), /* Target thread. */ + TCL_UNUSED(ClientData), /* Notifier data. */ + int *flagPtr, /* Flag to mark. */ + int value) /* Value of mark. */ +{ +#if TCL_THREADS + /* + * WARNING: + * This code most likely runs in a signal handler. Thus, + * only few async-signal-safe system calls are allowed, + * e.g. pthread_self(), sem_post(), write(). + */ + + if (pthread_equal(pthread_self(), (pthread_t) notifierThread)) { + if (notifierThreadRunning) { + *flagPtr = value; + if (!asyncPending) { + asyncPending = 1; + write(triggerPipe, "S", 1); + } + return 1; + } + return 0; + } + + /* + * Re-send the signal to the notifier thread. + */ + + pthread_kill((pthread_t) notifierThread, sigNumber); +#endif + return 0; +} + +/* + *---------------------------------------------------------------------- + * * NotifierThreadProc -- * * This routine is the initial (and only) function executed by the * special notifier thread. Its job is to wait for file descriptors to * become readable or writable or to have an exception condition and then @@ -1854,11 +1969,11 @@ NotifierThreadProc( TCL_UNUSED(ClientData)) { ThreadSpecificData *tsdPtr; fd_set readableMask, writableMask, exceptionalMask; - int i, numFdBits = 0, polling; + int i, ret, numFdBits = 0, polling; struct timeval poll = {0., 0.}, *timePtr; char buf[2]; /* * Look for file events and report them to interested threads. @@ -1907,12 +2022,29 @@ if (receivePipe >= numFdBits) { numFdBits = receivePipe + 1; } FD_SET(receivePipe, &readableMask); - if (select(numFdBits, &readableMask, &writableMask, &exceptionalMask, - timePtr) == -1) { + /* + * Signals are unblocked only during select(). + */ + + pthread_sigmask(SIG_SETMASK, ¬ifierSigMask, NULL); + ret = select(numFdBits, &readableMask, &writableMask, &exceptionalMask, + timePtr); + pthread_sigmask(SIG_BLOCK, &allSigMask, NULL); + + if (ret == -1) { + /* + * In case a signal was caught during select(), + * perform work on async handlers now. + */ + if (errno == EINTR && asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } + /* * Try again immediately on an error. */ continue; @@ -1996,10 +2128,15 @@ * pipe so we need to shut down the notifier thread. */ break; } + + if (asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } } } pthread_exit(0); } @@ -2082,16 +2219,21 @@ * parent the child will exit with an illegal instruction error. So we * reinitialize the lock in the child rather than attempt to unlock it. */ #if defined(USE_OS_UNFAIR_LOCK) - tsdPtr->tsdLock = OS_UNFAIR_LOCK_INIT; + notifierInitLock = OS_UNFAIR_LOCK_INIT; + notifierLock = OS_UNFAIR_LOCK_INIT; + tsdPtr->tsdLock = OS_UNFAIR_LOCK_INIT; #else - UNLOCK_NOTIFIER_TSD; - UNLOCK_NOTIFIER; - UNLOCK_NOTIFIER_INIT; + UNLOCK_NOTIFIER_TSD; + UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_INIT; #endif + + asyncPending = 0; + if (tsdPtr->runLoop) { tsdPtr->runLoop = NULL; if (!noCFafterFork) { CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); CFRelease(tsdPtr->runLoopSource); @@ -2117,10 +2259,16 @@ */ if (!noCFafterFork) { Tcl_InitNotifier(); } + + /* + * Restart the notifier thread for signal handling. + */ + + StartNotifierThread(); } } #endif /* HAVE_PTHREAD_ATFORK */ #else /* HAVE_COREFOUNDATION */ Index: unix/configure ================================================================== --- unix/configure +++ unix/configure @@ -9201,10 +9201,45 @@ fi if test $tcl_ok = no; then printf "%s\n" "#define NO_FD_SET 1" >>confdefs.h +fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pselect" >&5 +printf %s "checking for pselect... " >&6; } +if test ${tcl_cv_func_pselect+y} +then : + printf %s "(cached) " >&6 +else $as_nop + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *func = pselect; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_func_pselect=yes +else $as_nop + tcl_cv_func_pselect=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_func_pselect" >&5 +printf "%s\n" "$tcl_cv_func_pselect" >&6; } +tcl_ok=$tcl_cv_func_pselect +if test $tcl_ok = yes; then + +printf "%s\n" "#define HAVE_PSELECT 1" >>confdefs.h + fi #------------------------------------------------------------------------ # Options for the notifier. Checks for epoll(7) on Linux, and # kqueue(2) on {DragonFly,Free,Net,Open}BSD Index: unix/configure.ac ================================================================== --- unix/configure.ac +++ unix/configure.ac @@ -315,10 +315,17 @@ fi fi if test $tcl_ok = no; then AC_DEFINE(NO_FD_SET, 1, [Do we have fd_set?]) fi + +AC_CACHE_CHECK([for pselect], tcl_cv_func_pselect, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[void *func = pselect;]])],[tcl_cv_func_pselect=yes],[tcl_cv_func_pselect=no])]) +tcl_ok=$tcl_cv_func_pselect +if test $tcl_ok = yes; then + AC_DEFINE(HAVE_PSELECT, 1, [Should we use pselect()?]) +fi #------------------------------------------------------------------------ # Options for the notifier. Checks for epoll(7) on Linux, and # kqueue(2) on {DragonFly,Free,Net,Open}BSD #------------------------------------------------------------------------ Index: unix/tclConfig.h.in ================================================================== --- unix/tclConfig.h.in +++ unix/tclConfig.h.in @@ -187,10 +187,13 @@ /* Define to 1 if you have the `pthread_atfork' function. */ #undef HAVE_PTHREAD_ATFORK /* Define to 1 if you have the `pthread_attr_setstacksize' function. */ #undef HAVE_PTHREAD_ATTR_SETSTACKSIZE + +/* Define to 1 if you have the `pselect' function */ +#undef HAVE_PSELECT /* Does putenv() copy strings or incorporate them by reference? */ #undef HAVE_PUTENV_THAT_COPIES /* Are characters signed? */ Index: unix/tclEpollNotfy.c ================================================================== --- unix/tclEpollNotfy.c +++ unix/tclEpollNotfy.c @@ -109,10 +109,11 @@ * fds */ struct epoll_event *readyEvents; /* Pointer to at most maxReadyEvents events * returned by epoll_wait(2). */ size_t maxReadyEvents; /* Count of epoll_events in readyEvents. */ + int asyncPending; /* True when signal triggered thread. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* @@ -476,10 +477,14 @@ } else { timePtr->tv_sec = 0; timePtr->tv_usec = 0; } } + if (tsdPtr->asyncPending) { + tsdPtr->asyncPending = 0; + TclAsyncMarkFromNotifier(); + } return numFound; } /* *---------------------------------------------------------------------- @@ -762,10 +767,64 @@ } filePtr->readyMask = mask; } return 0; } + +/* + *---------------------------------------------------------------------- + * + * TclAsyncNotifier -- + * + * This procedure sets the async mark of an async handler to a + * given value, if it is called from the target thread. + * + * Result: + * True, when the handler will be marked, false otherwise. + * + * Side effects: + * The signal may be resent to the target thread. + * + *---------------------------------------------------------------------- + */ + +int +TclAsyncNotifier( + int sigNumber, /* Signal number. */ + Tcl_ThreadId threadId, /* Target thread. */ + ClientData clientData, /* Notifier data. */ + int *flagPtr, /* Flag to mark. */ + int value) /* Value of mark. */ +{ +#if TCL_THREADS + /* + * WARNING: + * This code most likely runs in a signal handler. Thus, + * only few async-signal-safe system calls are allowed, + * e.g. pthread_self(), sem_post(), write(). + */ + + if (pthread_equal(pthread_self(), (pthread_t) threadId)) { + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; + + *flagPtr = value; + if (tsdPtr != NULL && !tsdPtr->asyncPending) { + tsdPtr->asyncPending = 1; + TclpAlertNotifier(tsdPtr); + return 1; + } + return 0; + } + + /* + * Re-send the signal to the proper target thread. + */ + + pthread_kill((pthread_t) threadId, sigNumber); +#endif + return 0; +} #endif /* NOTIFIER_EPOLL && TCL_THREADS */ #else TCL_MAC_EMPTY_FILE(unix_tclEpollNotfy_c) #endif /* !HAVE_COREFOUNDATION */ Index: unix/tclKqueueNotfy.c ================================================================== --- unix/tclKqueueNotfy.c +++ unix/tclKqueueNotfy.c @@ -100,10 +100,11 @@ int eventsFd; /* kqueue(2) file descriptor used to wait for * fds. */ struct kevent *readyEvents; /* Pointer to at most maxReadyEvents events * returned by kevent(2). */ size_t maxReadyEvents; /* Count of kevents in readyEvents. */ + int asyncPending; /* True when signal triggered thread. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* @@ -163,15 +164,15 @@ struct kevent changeList[2]; struct PlatformEventData *newPedPtr; Tcl_StatBuf fdStat; if (isNew) { - newPedPtr = (struct PlatformEventData *) + newPedPtr = (struct PlatformEventData *) ckalloc(sizeof(struct PlatformEventData)); - newPedPtr->filePtr = filePtr; - newPedPtr->tsdPtr = tsdPtr; - filePtr->pedPtr = newPedPtr; + newPedPtr->filePtr = filePtr; + newPedPtr->tsdPtr = tsdPtr; + filePtr->pedPtr = newPedPtr; } /* * N.B. As discussed in Tcl_WaitForEvent(), kqueue(2) does not reproduce * the `always ready' {select,poll}(2) behaviour for regular files @@ -211,11 +212,11 @@ if (filePtr->mask & TCL_WRITABLE) { EV_SET(&changeList[numChanges], (uintptr_t) filePtr->fd, EVFILT_WRITE, op, 0, 0, filePtr->pedPtr); numChanges++; } - if (numChanges) { + if (numChanges) { if (kevent(tsdPtr->eventsFd, changeList, numChanges, NULL, 0, NULL) == -1) { Tcl_Panic("kevent: %s", strerror(errno)); } } @@ -361,11 +362,11 @@ filePtr = (FileHandler *) ckalloc(sizeof(FileHandler)); filePtr->fd = tsdPtr->triggerPipe[0]; filePtr->mask = TCL_READABLE; PlatformEventsControl(filePtr, tsdPtr, EV_ADD, 1); if (!tsdPtr->readyEvents) { - tsdPtr->maxReadyEvents = 512; + tsdPtr->maxReadyEvents = 512; tsdPtr->readyEvents = (struct kevent *) ckalloc( tsdPtr->maxReadyEvents * sizeof(tsdPtr->readyEvents[0])); } LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr); @@ -480,10 +481,14 @@ timersub(timePtr, &tv_delta, timePtr); } else { timePtr->tv_sec = 0; timePtr->tv_usec = 0; } + } + if (tsdPtr->asyncPending) { + tsdPtr->asyncPending = 0; + TclAsyncMarkFromNotifier(); } return numFound; } /* @@ -758,10 +763,64 @@ } filePtr->readyMask |= mask; } return 0; } + +/* + *---------------------------------------------------------------------- + * + * TclAsyncNotifier -- + * + * This procedure sets the async mark of an async handler to a + * given value, if it is called from the target thread. + * + * Result: + * True, when the handler will be marked, false otherwise. + * + * Side effects: + * The signal may be resent to the target thread. + * + *---------------------------------------------------------------------- + */ + +int +TclAsyncNotifier( + int sigNumber, /* Signal number. */ + Tcl_ThreadId threadId, /* Target thread. */ + ClientData clientData, /* Notifier data. */ + int *flagPtr, /* Flag to mark. */ + int value) /* Value of mark. */ +{ +#if TCL_THREADS + /* + * WARNING: + * This code most likely runs in a signal handler. Thus, + * only few async-signal-safe system calls are allowed, + * e.g. pthread_self(), sem_post(), write(). + */ + + if (pthread_equal(pthread_self(), (pthread_t) threadId)) { + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; + + *flagPtr = value; + if (tsdPtr != NULL && !tsdPtr->asyncPending) { + tsdPtr->asyncPending = 1; + TclpAlertNotifier(tsdPtr); + return 1; + } + return 0; + } + + /* + * Re-send the signal to the proper target thread. + */ + + pthread_kill((pthread_t) threadId, sigNumber); +#endif + return 0; +} #endif /* NOTIFIER_KQUEUE && TCL_THREADS */ #else TCL_MAC_EMPTY_FILE(unix_tclKqueueNotfy_c) #endif /* !HAVE_COREFOUNDATION */ Index: unix/tclSelectNotfy.c ================================================================== --- unix/tclSelectNotfy.c +++ unix/tclSelectNotfy.c @@ -146,10 +146,11 @@ * * You must hold the notifierMutex lock before writing to the pipe. */ static int triggerPipe = -1; +static int otherPipe = -1; /* * The notifierMutex locks access to all of the global notifier state. */ @@ -161,14 +162,20 @@ * You must hold the notifierInitMutex before accessing this variable. */ static int notifierThreadRunning = 0; +/* + * The following static flag indicates that async handlers are pending. + */ + +static int asyncPending = 0; + /* * The notifier thread signals the notifierCV when it has finished * initializing the triggerPipe and right before the notifier thread - * terminates. + * terminates. This condition is used to deal with the signal mask, too. */ static pthread_cond_t notifierCV = PTHREAD_COND_INITIALIZER; /* @@ -188,10 +195,18 @@ /* * This is the thread ID of the notifier thread that does select. */ static Tcl_ThreadId notifierThread; + +/* + * Signal mask information for notifier thread. + */ + +static sigset_t notifierSigMask; +static sigset_t allSigMask; + #endif /* TCL_THREADS */ /* * Static routines defined in this file. */ @@ -407,10 +422,18 @@ if (result) { Tcl_Panic("Tcl_FinalizeNotifier: %s", "unable to join notifier thread"); } notifierThreadRunning = 0; + + /* + * If async marks are outstanding, perform actions now. + */ + if (asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } } } /* * Clean up any synchronization objects in the thread local storage. @@ -873,10 +896,65 @@ } /* *---------------------------------------------------------------------- * + * TclAsyncNotifier -- + * + * This procedure sets the async mark of an async handler to a + * given value, if it is called from the notifier thread. + * + * Result: + * True, when the handler will be marked, false otherwise. + * + * Side effetcs: + * The trigger pipe is written when called from the notifier + * thread. + * + *---------------------------------------------------------------------- + */ + +int +TclAsyncNotifier( + int sigNumber, /* Signal number. */ + Tcl_ThreadId threadId, /* Target thread. */ + TCL_UNUSED(ClientData), /* Notifier data. */ + int *flagPtr, /* Flag to mark. */ + int value) /* Value of mark. */ +{ +#if TCL_THREADS + /* + * WARNING: + * This code most likely runs in a signal handler. Thus, + * only few async-signal-safe system calls are allowed, + * e.g. pthread_self(), sem_post(), write(). + */ + + if (pthread_equal(pthread_self(), (pthread_t) notifierThread)) { + if (notifierThreadRunning) { + *flagPtr = value; + if (!asyncPending) { + asyncPending = 1; + write(triggerPipe, "S", 1); + } + return 1; + } + return 0; + } + + /* + * Re-send the signal to the notifier thread. + */ + + pthread_kill((pthread_t) notifierThread, sigNumber); +#endif + return 0; +} + +/* + *---------------------------------------------------------------------- + * * NotifierThreadProc -- * * This routine is the initial (and only) function executed by the * special notifier thread. Its job is to wait for file descriptors to * become readable or writable or to have an exception condition and then @@ -904,20 +982,27 @@ { ThreadSpecificData *tsdPtr; fd_set readableMask; fd_set writableMask; fd_set exceptionMask; - int i; - int fds[2], receivePipe; + int i, fds[2], receivePipe, ret; long found; struct timeval poll = {0, 0}, *timePtr; char buf[2]; int numFdBits = 0; if (pipe(fds) != 0) { Tcl_Panic("NotifierThreadProc: %s", "could not create trigger pipe"); } + + /* + * Ticket [c6897e6e6a]. + */ + + if (fds[0] >= FD_SETSIZE || fds[1] >= FD_SETSIZE) { + Tcl_Panic("NotifierThreadProc: %s", "too many open files"); + } receivePipe = fds[0]; if (TclUnixSetBlockingMode(receivePipe, TCL_MODE_NONBLOCKING) < 0) { Tcl_Panic("NotifierThreadProc: %s", @@ -940,10 +1025,11 @@ * Install the write end of the pipe into the global variable. */ pthread_mutex_lock(¬ifierMutex); triggerPipe = fds[1]; + otherPipe = fds[0]; /* * Signal any threads that are waiting. */ @@ -1000,16 +1086,48 @@ if (receivePipe >= numFdBits) { numFdBits = receivePipe + 1; } FD_SET(receivePipe, &readableMask); - if (select(numFdBits, &readableMask, &writableMask, &exceptionMask, - timePtr) == -1) { + /* + * Signals are unblocked only during select(). + */ + +#ifdef HAVE_PSELECT + { + struct timespec tspec, *tspecPtr; + + if (timePtr == NULL) { + tspecPtr = NULL; + } else { + tspecPtr = &tspec; + tspecPtr->tv_sec = timePtr->tv_sec; + tspecPtr->tv_nsec = timePtr->tv_usec * 1000; + } + ret = pselect(numFdBits, &readableMask, &writableMask, + &exceptionMask, tspecPtr, ¬ifierSigMask); + } +#else + pthread_sigmask(SIG_SETMASK, ¬ifierSigMask, NULL); + ret = select(numFdBits, &readableMask, &writableMask, &exceptionMask, + timePtr); + pthread_sigmask(SIG_BLOCK, &allSigMask, NULL); +#endif + + if (ret == -1) { /* - * Try again immediately on an error. + * In case a signal was caught during select(), + * perform work on async handlers now. */ + if (errno == EINTR && asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } + /* + * Try again immediately on select() error. + */ continue; } /* * Alert any threads that are waiting on a ready file descriptor. @@ -1061,10 +1179,16 @@ */ break; } } while (1); + + if (asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } + if ((i == 0) || (buf[0] == 'q')) { break; } } @@ -1074,10 +1198,11 @@ */ close(receivePipe); pthread_mutex_lock(¬ifierMutex); triggerPipe = -1; + otherPipe = -1; pthread_cond_broadcast(¬ifierCV); pthread_mutex_unlock(¬ifierMutex); TclpThreadExit(0); } Index: unix/tclUnixNotfy.c ================================================================== --- unix/tclUnixNotfy.c +++ unix/tclUnixNotfy.c @@ -348,28 +348,28 @@ AlertSingleThread( ThreadSpecificData *tsdPtr) { tsdPtr->eventReady = 1; if (tsdPtr->onList) { - /* - * Remove the ThreadSpecificData structure of this thread from the - * waiting list. This prevents us from continuously spinning on - * epoll_wait until the other threads runs and services the file - * event. - */ - - if (tsdPtr->prevPtr) { + /* + * Remove the ThreadSpecificData structure of this thread from the + * waiting list. This prevents us from continuously spinning on + * epoll_wait until the other threads runs and services the file + * event. + */ + + if (tsdPtr->prevPtr) { tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; - } else { + } else { waitingListPtr = tsdPtr->nextPtr; - } - if (tsdPtr->nextPtr) { + } + if (tsdPtr->nextPtr) { tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; - } - tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; - tsdPtr->onList = 0; - tsdPtr->pollState = 0; + } + tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; + tsdPtr->onList = 0; + tsdPtr->pollState = 0; } #ifdef __CYGWIN__ PostMessageW(tsdPtr->hwnd, 1024, 0, 0); #else /* !__CYGWIN__ */ pthread_cond_broadcast(&tsdPtr->waitCV); @@ -401,10 +401,14 @@ } pthread_mutex_init(¬ifierInitMutex, NULL); pthread_mutex_init(¬ifierMutex, NULL); pthread_cond_init(¬ifierCV, NULL); +#ifdef NOTIFIER_SELECT + asyncPending = 0; +#endif + /* * notifierThreadRunning == 1: thread is running, (there might be data in * notifier lists) * atForkInit == 0: InitNotifier was never called * notifierCount != 0: unbalanced InitNotifier() / FinalizeNotifier calls @@ -418,10 +422,14 @@ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); notifierThreadRunning = 0; close(triggerPipe); triggerPipe = -1; +#ifdef NOTIFIER_SELECT + close(otherPipe); + otherPipe = -1; +#endif /* * The waitingListPtr might contain event info from multiple * threads, which are invalid here, so setting it to NULL is not * unreasonable. */ @@ -454,14 +462,52 @@ */ } } Tcl_InitNotifier(); + +#ifdef NOTIFIER_SELECT + /* + * Restart the notifier thread for signal handling. + */ + + StartNotifierThread("AtForkChild"); +#endif } #endif /* HAVE_PTHREAD_ATFORK */ #endif /* TCL_THREADS */ #endif /* NOTIFIER_SELECT */ + +/* + *---------------------------------------------------------------------- + * + * TclpNotifierData -- + * + * This function returns a ClientData pointer to be associated + * with a Tcl_AsyncHandler. + * + * Results: + * For the epoll and kqueue notifiers, this function returns the + * thread specific data. Otherwise NULL. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +ClientData +TclpNotifierData(void) +{ +#if defined(NOTIFIER_EPOLL) || defined(NOTIFIER_KQUEUE) + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + return (ClientData) tsdPtr; +#else + return NULL; +#endif +} /* *---------------------------------------------------------------------- * * TclUnixWaitForFile -- Index: win/tclWinNotify.c ================================================================== --- win/tclWinNotify.c +++ win/tclWinNotify.c @@ -351,10 +351,36 @@ } /* *---------------------------------------------------------------------- * + * TclAsyncNotifier -- + * + * This procedure is a no-op on Windows. + * + * Result: + * Always true. + * + * Side effetcs: + * None. + *---------------------------------------------------------------------- + */ + +int +TclAsyncNotifier( + int sigNumber, /* Signal number. */ + Tcl_ThreadId threadId, /* Target thread. */ + ClientData clientData, /* Notifier data. */ + int *flagPtr, /* Flag to mark. */ + int value) /* Value of mark. */ +{ + return 0; +} + +/* + *---------------------------------------------------------------------- + * * NotifierProc -- * * This procedure is invoked by Windows to process events on the notifier * window. Messages will be sent to this window in response to external * timer events or calls to TclpAlertTsdPtr-> @@ -390,10 +416,33 @@ */ Tcl_ServiceAll(); return 0; } + +/* + *---------------------------------------------------------------------- + * + * TclpNotifierData -- + * + * This function returns a ClientData pointer to be associated + * with a Tcl_AsyncHandler. + * + * Results: + * On Windows, returns always NULL. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +ClientData +TclpNotifierData(void) +{ + return NULL; +} /* *---------------------------------------------------------------------- * * TclpWaitForEvent --