diff options
Diffstat (limited to 'unix')
-rw-r--r-- | unix/Makefile.in | 16 | ||||
-rwxr-xr-x | unix/configure | 78 | ||||
-rw-r--r-- | unix/configure.ac | 31 | ||||
-rw-r--r-- | unix/tclEpollNotfy.c | 810 | ||||
-rw-r--r-- | unix/tclKqueueNotfy.c | 842 | ||||
-rw-r--r-- | unix/tclSelectNotfy.c | 1117 | ||||
-rw-r--r-- | unix/tclUnixNotfy.c | 1305 |
7 files changed, 3118 insertions, 1081 deletions
diff --git a/unix/Makefile.in b/unix/Makefile.in index c4f6136..3703f9f 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -345,7 +345,7 @@ UNIX_OBJS = tclUnixChan.o tclUnixEvent.o tclUnixFCmd.o \ tclUnixTime.o tclUnixInit.o tclUnixThrd.o \ tclUnixCompat.o -NOTIFY_OBJS = tclUnixNotfy.o +NOTIFY_OBJS = tclEpollNotfy.o tclKqueueNotfy.o tclSelectNotfy.o MAC_OSX_OBJS = tclMacOSXBundle.o tclMacOSXFCmd.o tclMacOSXNotify.o @@ -565,7 +565,9 @@ UNIX_SRCS = \ $(UNIX_DIR)/tclUnixCompat.c NOTIFY_SRCS = \ - $(UNIX_DIR)/tclUnixNotfy.c + $(UNIX_DIR)/tclEpollNotfy.c \ + $(UNIX_DIR)/tclKqueueNotfy.c \ + $(UNIX_DIR)/tclSelectNotfy.c DL_SRCS = \ $(UNIX_DIR)/tclLoadAix.c \ @@ -1559,8 +1561,14 @@ tclUnixFCmd.o: $(UNIX_DIR)/tclUnixFCmd.c tclUnixFile.o: $(UNIX_DIR)/tclUnixFile.c $(FSHDR) $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixFile.c -tclUnixNotfy.o: $(UNIX_DIR)/tclUnixNotfy.c - $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixNotfy.c +tclEpollNotfy.o: $(UNIX_DIR)/tclEpollNotfy.c + $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclEpollNotfy.c + +tclKqueueNotfy.o: $(UNIX_DIR)/tclKqueueNotfy.c + $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclKqueueNotfy.c + +tclSelectNotfy.o: $(UNIX_DIR)/tclSelectNotfy.c + $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclSelectNotfy.c tclUnixPipe.o: $(UNIX_DIR)/tclUnixPipe.c $(CC) -c $(CC_SWITCHES) $(UNIX_DIR)/tclUnixPipe.c diff --git a/unix/configure b/unix/configure index 741ae47..c7b377d 100755 --- a/unix/configure +++ b/unix/configure @@ -8439,6 +8439,84 @@ $as_echo "#define NO_FD_SET 1" >>confdefs.h fi +#------------------------------------------------------------------------ +# Options for the notifier. Checks for epoll(7) on Linux, and +# kqueue(2) on {DragonFly,Free,Net,Open}BSD +#------------------------------------------------------------------------ + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for advanced notifier support" >&5 +$as_echo_n "checking for advanced notifier support... " >&6; } +case x`uname -s` in + xLinux) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: epoll(7)" >&5 +$as_echo "epoll(7)" >&6; } + for ac_header in sys/epoll.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "sys/epoll.h" "ac_cv_header_sys_epoll_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_epoll_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SYS_EPOLL_H 1 +_ACEOF + +$as_echo "#define NOTIFIER_EPOLL 1" >>confdefs.h + +fi + +done + + for ac_header in sys/eventfd.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "sys/eventfd.h" "ac_cv_header_sys_eventfd_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_eventfd_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_SYS_EVENTFD_H 1 +_ACEOF + +$as_echo "#define HAVE_EVENTFD 1" >>confdefs.h + +fi + +done +;; + xDragonFlyBSD|xFreeBSD|xNetBSD|xOpenBSD) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: kqueue(2)" >&5 +$as_echo "kqueue(2)" >&6; } + # Messy because we want to check if *all* the headers are present, and not + # just *any* + tcl_kqueue_headers=x + for ac_header in sys/types.h sys/event.h sys/time.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + tcl_kqueue_headers=${tcl_kqueue_headers}y +fi + +done + + if test $tcl_kqueue_headers = xyyy; then : + + +$as_echo "#define NOTIFIER_KQUEUE 1" >>confdefs.h + +fi;; + xDarwin) + # Assume that we've got CoreFoundation present (checked elsewhere because + # of wider impact). + { $as_echo "$as_me:${as_lineno-$LINENO}: result: OSX" >&5 +$as_echo "OSX" >&6; };; + *) + cat >>confdefs.h <<_ACEOF +#define NOTIFIER_SELECT 1 +_ACEOF + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; };; +esac + #------------------------------------------------------------------------------ # Find out all about time handling differences. #------------------------------------------------------------------------------ diff --git a/unix/configure.ac b/unix/configure.ac index bafb970..b43da6d 100644 --- a/unix/configure.ac +++ b/unix/configure.ac @@ -300,6 +300,37 @@ if test $tcl_ok = no; then AC_DEFINE(NO_FD_SET, 1, [Do we have fd_set?]) fi +#------------------------------------------------------------------------ +# Options for the notifier. Checks for epoll(7) on Linux, and +# kqueue(2) on {DragonFly,Free,Net,Open}BSD +#------------------------------------------------------------------------ + +AC_MSG_CHECKING([for advanced notifier support]) +case x`uname -s` in + xLinux) + AC_MSG_RESULT([epoll(7)]) + AC_CHECK_HEADERS([sys/epoll.h], + [AC_DEFINE(NOTIFIER_EPOLL, [1], [Is epoll(7) supported?])]) + AC_CHECK_HEADERS([sys/eventfd.h], + [AC_DEFINE(HAVE_EVENTFD, [1], [Is eventfd(2) supported?])]);; + xDragonFlyBSD|xFreeBSD|xNetBSD|xOpenBSD) + AC_MSG_RESULT([kqueue(2)]) + # Messy because we want to check if *all* the headers are present, and not + # just *any* + tcl_kqueue_headers=x + AC_CHECK_HEADERS([sys/types.h sys/event.h sys/time.h], + [tcl_kqueue_headers=${tcl_kqueue_headers}y]) + AS_IF([test $tcl_kqueue_headers = xyyy], [ + AC_DEFINE(NOTIFIER_KQUEUE, [1], [Is kqueue(2) supported?])]);; + xDarwin) + # Assume that we've got CoreFoundation present (checked elsewhere because + # of wider impact). + AC_MSG_RESULT([OSX]);; + *) + AC_DEFINE_UNQUOTED(NOTIFIER_SELECT) + AC_MSG_RESULT([none]);; +esac + #------------------------------------------------------------------------------ # Find out all about time handling differences. #------------------------------------------------------------------------------ diff --git a/unix/tclEpollNotfy.c b/unix/tclEpollNotfy.c new file mode 100644 index 0000000..7355564 --- /dev/null +++ b/unix/tclEpollNotfy.c @@ -0,0 +1,810 @@ +/* + * tclEpollNotfy.c -- + * + * This file contains the implementation of the epoll()-based + * Linux-specific notifier, which is the lowest-level part of the + * Tcl event loop. This file works together with generic/tclNotify.c. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright (c) 2016 Lucio AndrĂ©s Illanes Albornoz <l.illanes@gmx.de> + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifdef NOTIFIER_EPOLL + +#define _GNU_SOURCE /* For pipe2(2) */ +#include "tclInt.h" +#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is + * in tclMacOSXNotify.c */ +#include <fcntl.h> +#include <signal.h> +#include <sys/epoll.h> +#include <sys/eventfd.h> +#include <sys/queue.h> +#include <unistd.h> + +/* + * This structure is used to keep track of the notifier info for a registered + * file. + */ + +struct PlatformEventData; +typedef struct FileHandler { + int fd; + int mask; /* Mask of desired events: TCL_READABLE, + * etc. */ + int readyMask; /* Mask of events that have been seen since + * the last time file handlers were invoked + * for this file. */ + Tcl_FileProc *proc; /* Function to call, in the style of + * Tcl_CreateFileHandler. */ + ClientData clientData; /* Argument to pass to proc. */ + struct FileHandler *nextPtr;/* Next in list of all files we care about. */ + LIST_ENTRY(FileHandler) readyNode; + /* Next/previous in list of FileHandlers asso- + * ciated with regular files (S_IFREG) that are + * ready for I/O. */ + struct PlatformEventData *pedPtr; + /* Pointer to PlatformEventData associating this + * FileHandler with epoll(7) events. */ +} FileHandler; + +/* + * The following structure associates a FileHandler and the thread that owns it + * with the file descriptors of interest and their event masks passed to epoll_ctl(2) + * and their corresponding event(s) returned by epoll_wait(2). + */ + +struct ThreadSpecificData; +struct PlatformEventData { + FileHandler *filePtr; + struct ThreadSpecificData *tsdPtr; +}; + +/* + * The following structure is what is added to the Tcl event queue when file + * handlers are ready to fire. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + int fd; /* File descriptor that is ready. Used to find + * the FileHandler structure for the file + * (can't point directly to the FileHandler + * structure because it could go away while + * the event is queued). */ +} FileHandlerEvent; + +/* + * The following static structure contains the state information for the + * epoll based implementation of the Tcl notifier. One of these structures is + * created for each thread that is using the notifier. + */ + +LIST_HEAD(PlatformReadyFileHandlerList, FileHandler); +typedef struct ThreadSpecificData { + FileHandler *firstFileHandlerPtr; + /* Pointer to head of file handler list. */ + struct PlatformReadyFileHandlerList firstReadyFileHandlerPtr; + /* Pointer to head of list of FileHandlers + * associated with regular files (S_IFREG) + * that are ready for I/O. */ + pthread_mutex_t notifierMutex; + /* Mutex protecting notifier termination in + * PlatformEventsFinalize. */ +#ifdef HAVE_EVENTFD + int triggerEventFd; /* eventfd(2) used by other threads to wake + * up this thread for inter-thread IPC. */ +#else + int triggerPipe[2]; /* pipe(2) used by other threads to wake + * up this thread for inter-thread IPC. */ +#endif /* HAVE_EVENTFD */ + int eventsFd; /* epoll(7) file descriptor used to wait for 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. */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +void PlatformEventsControl(FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew); +static void PlatformEventsFinalize(void); +void PlatformEventsInit(void); +static int PlatformEventsTranslate(struct epoll_event *event); +static int PlatformEventsWait(struct epoll_event *events, size_t numEvents, struct timeval *timePtr); + +#include "tclUnixNotfy.c" + +/* + *---------------------------------------------------------------------- + * + * Tcl_InitNotifier -- + * + * Initializes the platform specific notifier state. + * + * Results: + * Returns a handle to the notifier state for this thread. + * + * Side effects: + * If no initNotifierProc notifier hook exists, PlatformEventsInit + * is called. + * + *---------------------------------------------------------------------- + */ + +ClientData +Tcl_InitNotifier(void) +{ + if (tclNotifierHooks.initNotifierProc) { + return tclNotifierHooks.initNotifierProc(); + } else { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + PlatformEventsInit(); + return tsdPtr; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_FinalizeNotifier -- + * + * This function is called to cleanup the notifier state before a thread + * is terminated. + * + * Results: + * None. + * + * Side effects: + * If no finalizeNotifierProc notifier hook exists, PlatformEvents- + * Finalize is called. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_FinalizeNotifier( + ClientData clientData) /* Not used. */ +{ + if (tclNotifierHooks.finalizeNotifierProc) { + tclNotifierHooks.finalizeNotifierProc(clientData); + return; + } else { + PlatformEventsFinalize(); + } +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsControl -- + * + * This function registers interest for the file descriptor and the mask + * of TCL_* bits associated with filePtr on the epoll file descriptor + * associated with tsdPtr. + * Future calls to epoll_wait will return filePtr and tsdPtr alongside with + * the event registered here via the PlatformEventData struct. + * + * Results: + * None. + * + * Side effects: + * If adding a new file descriptor, a PlatformEventData struct will be + * allocated and associated with filePtr. + * fstat is called on the file descriptor; if it is associated with + * a regular file (S_IFREG,) filePtr is considered to be ready for I/O + * and added to or deleted from the corresponding list in tsdPtr. + * If it is not associated with a regular file, the file descriptor is + * added, modified concerning its mask of events of interest, or deleted + * from the epoll file descriptor of the calling thread. + * + *---------------------------------------------------------------------- + */ + +void +PlatformEventsControl( + FileHandler *filePtr, + ThreadSpecificData *tsdPtr, + int op, + int isNew) +{ + struct epoll_event newEvent; + struct PlatformEventData *newPedPtr; + struct stat fdStat; + + newEvent.events = 0; + if (filePtr->mask & (TCL_READABLE | TCL_EXCEPTION)) { + newEvent.events |= EPOLLIN; + } + if (filePtr->mask & TCL_WRITABLE) { + newEvent.events |= EPOLLOUT; + } + if (isNew) { + newPedPtr = ckalloc(sizeof(*newPedPtr)); + newPedPtr->filePtr = filePtr; + newPedPtr->tsdPtr = tsdPtr; + filePtr->pedPtr = newPedPtr; + } + newEvent.data.ptr = filePtr->pedPtr; + + /* + * N.B. As discussed in Tcl_WaitForEvent(), epoll(7) does not sup- + * port regular files (S_IFREG.) Therefore, filePtr is in these + * cases simply added or deleted from the list of FileHandlers + * associated with regular files belonging to tsdPtr. + */ + + if (fstat(filePtr->fd, &fdStat) == -1) { + Tcl_Panic("fstat: %s", strerror(errno)); + } else if ((fdStat.st_mode & S_IFMT) == S_IFREG) { + switch (op) { + case EPOLL_CTL_ADD: + if (isNew) { + LIST_INSERT_HEAD(&tsdPtr->firstReadyFileHandlerPtr, filePtr, readyNode); + } + break; + case EPOLL_CTL_DEL: + LIST_REMOVE(filePtr, readyNode); + break; + } + return; + } else if (epoll_ctl(tsdPtr->eventsFd, op, filePtr->fd, &newEvent) == -1) { + Tcl_Panic("epoll_ctl: %s", strerror(errno)); + } +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsFinalize -- + * + * This function closes the eventfd and the epoll file descriptor and + * frees the epoll_event structs owned by the thread of the caller. + * The above operations are protected by tsdPtr->notifierMutex, which + * is destroyed thereafter. + * + * Results: + * None. + * + * Side effects: + * While tsdPtr->notifierMutex is held: + * The per-thread eventfd(2) is closed, if non-zero, and set to -1. + * The per-thread epoll(7) fd is closed, if non-zero, and set to 0. + * The per-thread epoll_event structs are freed, if any, and set to 0. + * + * tsdPtr->notifierMutex is destroyed. + * + *---------------------------------------------------------------------- + */ + +void +PlatformEventsFinalize( + void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + pthread_mutex_lock(&tsdPtr->notifierMutex); +#ifdef HAVE_EVENTFD + if (tsdPtr->triggerEventFd) { + close(tsdPtr->triggerEventFd); + tsdPtr->triggerEventFd = -1; + } +#else + if (tsdPtr->triggerPipe[0]) { + close(tsdPtr->triggerPipe[0]); + tsdPtr->triggerPipe[0] = -1; + } + if (tsdPtr->triggerPipe[1]) { + close(tsdPtr->triggerPipe[1]); + tsdPtr->triggerPipe[1] = -1; + } +#endif /* HAVE_EVENTFD */ + if (tsdPtr->eventsFd > 0) { + close(tsdPtr->eventsFd); + tsdPtr->eventsFd = 0; + } + if (tsdPtr->readyEvents) { + ckfree(tsdPtr->readyEvents); + tsdPtr->maxReadyEvents = 0; + } + pthread_mutex_unlock(&tsdPtr->notifierMutex); + if ((errno = pthread_mutex_destroy(&tsdPtr->notifierMutex))) { + Tcl_Panic("pthread_mutex_destroy: %s", strerror(errno)); + } +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsInit -- + * + * This function abstracts creating a kqueue fd via the epoll_create + * system call and allocating memory for the epoll_event structs in + * tsdPtr for the thread of the caller. + * + * Results: + * None. + * + * Side effects: + * The following per-thread entities are initialised: + * notifierMutex is initialised. + * The eventfd(2) is created w/ EFD_CLOEXEC and EFD_NONBLOCK. + * The epoll(7) fd is created w/ EPOLL_CLOEXEC. + * A FileHandler struct is allocated and initialised for the event- + * fd(2), registering interest for TCL_READABLE on it via Platform- + * EventsControl(). + * readyEvents and maxReadyEvents are initialised with 512 epoll_events. + * + *---------------------------------------------------------------------- + */ + +void +PlatformEventsInit( + void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + FileHandler *filePtr; + + errno = pthread_mutex_init(&tsdPtr->notifierMutex, NULL); + if (errno) { + Tcl_Panic("Tcl_InitNotifier: %s", "could not create mutex"); + } +#ifdef HAVE_EVENTFD + if ((tsdPtr->triggerEventFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) <= 0) { + Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger eventfd"); + } +#else + if (pipe2(tsdPtr->triggerPipe, O_CLOEXEC | O_NONBLOCK) != 0) { + Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger pipe"); + } +#endif + if ((tsdPtr->eventsFd = epoll_create1(EPOLL_CLOEXEC)) == -1) { + Tcl_Panic("epoll_create1: %s", strerror(errno)); + } + filePtr = ckalloc(sizeof(*filePtr)); +#ifdef HAVE_EVENTFD + filePtr->fd = tsdPtr->triggerEventFd; +#else + filePtr->fd = tsdPtr->triggerPipe[0]; +#endif + filePtr->mask = TCL_READABLE; + PlatformEventsControl(filePtr, tsdPtr, EPOLL_CTL_ADD, 1); + if (!tsdPtr->readyEvents) { + tsdPtr->maxReadyEvents = 512; + tsdPtr->readyEvents = ckalloc(tsdPtr->maxReadyEvents + * sizeof(tsdPtr->readyEvents[0])); + } + LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr); +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsTranslate -- + * + * This function translates the platform-specific mask of returned + * events in eventPtr to a mask of TCL_* bits. + * + * Results: + * Returns the translated mask. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +PlatformEventsTranslate( + struct epoll_event *eventPtr) +{ + int mask; + + mask = 0; + if (eventPtr->events & (EPOLLIN | EPOLLHUP)) { + mask |= TCL_READABLE; + } + if (eventPtr->events & EPOLLOUT) { + mask |= TCL_WRITABLE; + } + if (eventPtr->events & EPOLLERR) { + mask |= TCL_EXCEPTION; + } + return mask; +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsWait -- + * + * This function abstracts waiting for I/O events via epoll_wait. + * + * Results: + * Returns -1 if epoll_wait failed. Returns 0 if polling and if no + * events became available whilst polling. Returns a pointer to and + * the count of all returned events in all other cases. + * + * Side effects: + * gettimeofday(2), epoll_wait(2), and gettimeofday(2) are called, + * in the specified order. + * If timePtr specifies a positive value, it is updated to reflect + * the amount of time that has passed; if its value would {under, + * over}flow, it is set to zero. + * + *---------------------------------------------------------------------- + */ + +int +PlatformEventsWait( + struct epoll_event *events, + size_t numEvents, + struct timeval *timePtr) +{ + int numFound; + struct timeval tv0, tv1, tv_delta; + int timeout; + + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * If timePtr is NULL, epoll_wait(2) will wait indefinitely. If it + * specifies a timeout of {0,0}, epoll_wait(2) will poll. Otherwise, + * the timeout will simply be converted to milliseconds. + */ + + if (!timePtr) { + timeout = -1; + } else if (!timePtr->tv_sec && !timePtr->tv_usec) { + timeout = 0; + } else { + timeout = (int)timePtr->tv_sec; + } + + /* + * Call (and possibly block on) epoll_wait(2) and substract the delta + * of gettimeofday(2) before and after the call from timePtr if the + * latter is not NULL. Return the number of events returned by epoll_wait(2). + */ + + gettimeofday(&tv0, NULL); + numFound = epoll_wait(tsdPtr->eventsFd, events, (int)numEvents, timeout); + gettimeofday(&tv1, NULL); + if (timePtr && (timePtr->tv_sec && timePtr->tv_usec)) { + timersub(&tv1, &tv0, &tv_delta); + if (!timercmp(&tv_delta, timePtr, >)) { + timersub(timePtr, &tv_delta, timePtr); + } else { + timePtr->tv_sec = 0; + timePtr->tv_usec = 0; + } + } + return numFound; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_CreateFileHandler -- + * + * This function registers a file handler with the epoll notifier + * of the thread of the caller. + * + * Results: + * None. + * + * Side effects: + * Creates a new file handler structure. + * PlatformEventsControl() is called for the new file handler structure. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_CreateFileHandler( + int fd, /* Handle of stream to watch. */ + int mask, /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, and TCL_EXCEPTION: indicates + * conditions under which proc should be + * called. */ + Tcl_FileProc *proc, /* Function to call for each selected + * event. */ + ClientData clientData) /* Arbitrary data to pass to proc. */ +{ + int isNew; + + if (tclNotifierHooks.createFileHandlerProc) { + tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData); + return; + } else { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + FileHandler *filePtr; + + for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; + filePtr = filePtr->nextPtr) { + if (filePtr->fd == fd) { + break; + } + } + if (filePtr == NULL) { + filePtr = ckalloc(sizeof(FileHandler)); + filePtr->fd = fd; + filePtr->readyMask = 0; + filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; + tsdPtr->firstFileHandlerPtr = filePtr; + isNew = 1; + } else { + isNew = 0; + } + filePtr->proc = proc; + filePtr->clientData = clientData; + filePtr->mask = mask; + + PlatformEventsControl(filePtr, tsdPtr, isNew ? + EPOLL_CTL_ADD : EPOLL_CTL_MOD, isNew); + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_DeleteFileHandler -- + * + * Cancel a previously-arranged callback arrangement for a file on + * the epoll file descriptor of the thread of the caller. + * + * Results: + * None. + * + * Side effects: + * If a callback was previously registered on file, remove it. + * PlatformEventsControl() is called for the file handler structure. + * The PlatformEventData struct associated with the new file handler + * structure is freed. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_DeleteFileHandler( + int fd) /* Stream id for which to remove callback + * function. */ +{ + if (tclNotifierHooks.deleteFileHandlerProc) { + tclNotifierHooks.deleteFileHandlerProc(fd); + return; + } else { + FileHandler *filePtr, *prevPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Find the entry for the given file (and return if there isn't one). + */ + + for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; + prevPtr = filePtr, filePtr = filePtr->nextPtr) { + if (filePtr == NULL) { + return; + } + if (filePtr->fd == fd) { + break; + } + } + + /* + * Update the check masks for this file. + */ + + PlatformEventsControl(filePtr, tsdPtr, EPOLL_CTL_DEL, 0); + if (filePtr->pedPtr) { + ckfree(filePtr->pedPtr); + } + + /* + * Clean up information in the callback record. + */ + + if (prevPtr == NULL) { + tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; + } else { + prevPtr->nextPtr = filePtr->nextPtr; + } + ckfree(filePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitForEvent -- + * + * This function is called by Tcl_DoOneEvent to wait for new events on + * the message queue. If the block time is 0, then Tcl_WaitForEvent just + * polls without blocking. + * The waiting logic is implemented in PlatformEventsWait. + * + * Results: + * Returns -1 if PlatformEventsWait() would block forever, otherwise + * returns 0. + * + * Side effects: + * Queues file events that are detected by PlatformEventsWait(). + * + *---------------------------------------------------------------------- + */ + +int +Tcl_WaitForEvent( + const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ +{ + if (tclNotifierHooks.waitForEventProc) { + return tclNotifierHooks.waitForEventProc(timePtr); + } else { + FileHandler *filePtr; + int mask; + Tcl_Time vTime; + /* + * Impl. notes: timeout & timeoutPtr are used if, and only if threads + * are not enabled. They are the arguments for the regular epoll_wait() + * used when the core is not thread-enabled. + */ + + struct timeval timeout, *timeoutPtr; + int numFound, numEvent; + struct PlatformEventData *pedPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int numQueued; + ssize_t i; +#ifdef HAVE_EVENTFD + uint64_t eventFdVal; +#else + char triggerPipeVal; +#endif + + /* + * Set up the timeout structure. Note that if there are no events to + * check for, we return with a negative result rather than blocking + * forever. + */ + + if (timePtr != NULL) { + /* + * TIP #233 (Virtualized Time). Is virtual time in effect? And do + * we actually have something to scale? If yes to both then we + * call the handler to do this scaling. + */ + + if (timePtr->sec != 0 || timePtr->usec != 0) { + vTime = *timePtr; + tclScaleTimeProcPtr(&vTime, tclTimeClientData); + timePtr = &vTime; + } + timeout.tv_sec = timePtr->sec; + timeout.tv_usec = timePtr->usec; + timeoutPtr = &timeout; + } else { + timeoutPtr = NULL; + } + + /* + * Walk the list of FileHandlers associated with regular files + * (S_IFREG) belonging to tsdPtr, queue Tcl events for them, and + * update their mask of events of interest. + * As epoll(7) does not support regular files, the behaviour of + * {select,poll}(2) is simply simulated here: fds associated with + * regular files are added to this list by PlatformEventsControl() + * and processed here before calling (and possibly blocking) on + * PlatformEventsWait(). + */ + + numQueued = 0; + LIST_FOREACH(filePtr, &tsdPtr->firstReadyFileHandlerPtr, readyNode) { + mask = 0; + if (filePtr->mask & TCL_READABLE) { + mask |= TCL_READABLE; + } + if (filePtr->mask & TCL_WRITABLE) { + mask |= TCL_WRITABLE; + } + + /* + * Don't bother to queue an event if the mask was previously + * non-zero since an event must still be on the queue. + */ + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = + ckalloc(sizeof(FileHandlerEvent)); + + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + numQueued++; + } + filePtr->readyMask = mask; + } + + /* + * If any events were queued in the above loop, force PlatformEvents- + * Wait() to poll as there already are events that need to be processed + * at this point. + */ + + if (numQueued) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + timeoutPtr = &timeout; + } + + /* + * Wait or poll for new events, queue Tcl events for the FileHandlers + * corresponding to them, and update the FileHandlers' mask of events + * of interest registered by the last call to Tcl_CreateFileHandler(). + * + * Events for the eventfd(2)/trigger pipe are processed here in order + * to facilitate inter-thread IPC. If another thread intends to wake + * up this thread whilst it's blocking on PlatformEventsWait(), it + * write(2)s to the eventfd(2)/trigger pipe (see Tcl_AlertNotifier(),) + * which in turn will cause PlatformEventsWait() to return immediately. + */ + + numFound = PlatformEventsWait(tsdPtr->readyEvents, tsdPtr->maxReadyEvents, timeoutPtr); + for (numEvent = 0; numEvent < numFound; numEvent++) { + pedPtr = tsdPtr->readyEvents[numEvent].data.ptr; + filePtr = pedPtr->filePtr; + mask = PlatformEventsTranslate(&tsdPtr->readyEvents[numEvent]); +#ifdef HAVE_EVENTFD + if (filePtr->fd == tsdPtr->triggerEventFd) { + i = read(tsdPtr->triggerEventFd, &eventFdVal, sizeof(eventFdVal)); + if ((i != sizeof(eventFdVal)) && (errno != EAGAIN)) { +#else + if (filePtr->fd == tsdPtr->triggerPipe[0]) { + i = read(tsdPtr->triggerPipe[0], &triggerPipeVal, sizeof(triggerPipeVal)); + if ((i != sizeof(triggerPipeVal)) && (errno != EAGAIN)) { +#endif + Tcl_Panic("Tcl_WaitForEvent: " + "read from %p->triggerEventFd: %s", + (void *)tsdPtr, strerror(errno)); + } else { + continue; + } + } + if (!mask) { + continue; + } + + /* + * Don't bother to queue an event if the mask was previously + * non-zero since an event must still be on the queue. + */ + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = + ckalloc(sizeof(FileHandlerEvent)); + + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + } + filePtr->readyMask = mask; + } + return 0; + } +} + +#endif /* !HAVE_COREFOUNDATION */ + +#endif /* NOTIFIER_EPOLL */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/unix/tclKqueueNotfy.c b/unix/tclKqueueNotfy.c new file mode 100644 index 0000000..5522f06 --- /dev/null +++ b/unix/tclKqueueNotfy.c @@ -0,0 +1,842 @@ +/* + * tclKqueueNotfy.c -- + * + * This file contains the implementation of the kqueue()-based + * DragonFly/Free/Net/OpenBSD-specific notifier, which is the lowest- + * level part of the Tcl event loop. This file works together with + * generic/tclNotify.c. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright (c) 2016 Lucio AndrĂ©s Illanes Albornoz <l.illanes@gmx.de> + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifdef NOTIFIER_KQUEUE + +#include "tclInt.h" +#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is + * in tclMacOSXNotify.c */ +#include <signal.h> +#include <sys/types.h> +#include <sys/event.h> +#include <sys/queue.h> +#include <sys/time.h> + +/* + * This structure is used to keep track of the notifier info for a registered + * file. + */ + +struct PlatformEventData; +typedef struct FileHandler { + int fd; + int mask; /* Mask of desired events: TCL_READABLE, + * etc. */ + int readyMask; /* Mask of events that have been seen since + * the last time file handlers were invoked + * for this file. */ + Tcl_FileProc *proc; /* Function to call, in the style of + * Tcl_CreateFileHandler. */ + ClientData clientData; /* Argument to pass to proc. */ + struct FileHandler *nextPtr;/* Next in list of all files we care about. */ + LIST_ENTRY(FileHandler) readyNode; + /* Next/previous in list of FileHandlers asso- + * ciated with regular files (S_IFREG) that are + * ready for I/O. */ + struct PlatformEventData *pedPtr; + /* Pointer to PlatformEventData associating this + * FileHandler with kevent(2) events. */ +} FileHandler; + +/* + * The following structure associates a FileHandler and the thread that owns it + * with the file descriptors of interest and their event masks passed to kevent(2) + * and their corresponding event(s) returned by kevent(2). + */ + +struct ThreadSpecificData; +struct PlatformEventData { + FileHandler *filePtr; + struct ThreadSpecificData *tsdPtr; +}; + +/* + * The following structure is what is added to the Tcl event queue when file + * handlers are ready to fire. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + int fd; /* File descriptor that is ready. Used to find + * the FileHandler structure for the file + * (can't point directly to the FileHandler + * structure because it could go away while + * the event is queued). */ +} FileHandlerEvent; + +/* + * The following static structure contains the state information for the + * kqueue based implementation of the Tcl notifier. One of these structures is + * created for each thread that is using the notifier. + */ + +LIST_HEAD(PlatformReadyFileHandlerList, FileHandler); +typedef struct ThreadSpecificData { + FileHandler *firstFileHandlerPtr; + /* Pointer to head of file handler list. */ + struct PlatformReadyFileHandlerList firstReadyFileHandlerPtr; + /* Pointer to head of list of FileHandlers + * associated with regular files (S_IFREG) + * that are ready for I/O. */ + pthread_mutex_t notifierMutex; + /* Mutex protecting notifier termination in + * PlatformEventsFinalize. */ + int triggerPipe[2]; /* pipe(2) used by other threads to wake + * up this thread for inter-thread IPC. */ + 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. */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +void PlatformEventsControl(FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew); +static void PlatformEventsFinalize(void); +void PlatformEventsInit(void); +static int PlatformEventsTranslate(struct kevent *eventPtr); +static int PlatformEventsWait(struct kevent *events, size_t numEvents, struct timeval *timePtr); + +#include "tclUnixNotfy.c" + +/* + *---------------------------------------------------------------------- + * + * Tcl_InitNotifier -- + * + * Initializes the platform specific notifier state. + * + * Results: + * Returns a handle to the notifier state for this thread. + * + * Side effects: + * If no initNotifierProc notifier hook exists, PlatformEventsInit + * is called. + * + *---------------------------------------------------------------------- + */ + +ClientData +Tcl_InitNotifier(void) +{ + if (tclNotifierHooks.initNotifierProc) { + return tclNotifierHooks.initNotifierProc(); + } else { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + PlatformEventsInit(); + return tsdPtr; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_FinalizeNotifier -- + * + * This function is called to cleanup the notifier state before a thread + * is terminated. + * + * Results: + * None. + * + * Side effects: + * If no finalizeNotifierProc notifier hook exists, PlatformEvents- + * Finalize is called. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_FinalizeNotifier( + ClientData clientData) /* Not used. */ +{ + if (tclNotifierHooks.finalizeNotifierProc) { + tclNotifierHooks.finalizeNotifierProc(clientData); + return; + } else { + PlatformEventsFinalize(); + } +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsControl -- + * + * This function registers interest for the file descriptor and the mask + * of TCL_* bits associated with filePtr on the kqueue file descriptor + * associated with tsdPtr. + * Future calls to kevent will return filePtr and tsdPtr alongside with + * the event registered here via the PlatformEventData struct. + * + * Results: + * None. + * + * Side effects: + * If adding a new file descriptor, a PlatformEventData struct will be + * allocated and associated with filePtr. + * fstat is called on the file descriptor; if it is associated with + * a regular file (S_IFREG,) filePtr is considered to be ready for I/O + * and added to or deleted from the corresponding list in tsdPtr. + * If it is not associated with a regular file, the file descriptor is + * added, modified concerning its mask of events of interest, or deleted + * from the epoll file descriptor of the calling thread. + * If deleting a file descriptor, kevent(2) is called twice specifying + * EVFILT_READ first and then EVFILT_WRITE (see note below.) + * + *---------------------------------------------------------------------- + */ + +void +PlatformEventsControl( + FileHandler *filePtr, + ThreadSpecificData *tsdPtr, + int op, + int isNew) +{ + int numChanges; + struct kevent changeList[2]; + struct PlatformEventData *newPedPtr; + struct stat fdStat; + + if (isNew) { + newPedPtr = ckalloc(sizeof(*newPedPtr)); + newPedPtr->filePtr = filePtr; + newPedPtr->tsdPtr = tsdPtr; + filePtr->pedPtr = newPedPtr; + } + + /* + * N.B. As discussed in Tcl_WaitForEvent(), kqueue(2) does not repro- + * duce the `always ready' {select,poll}(2) behaviour for regular + * files (S_IFREG) prior to FreeBSD 11.0-RELEASE. Therefore, file- + * Ptr is in these cases simply added or deleted from the list of + * FileHandlers associated with regular files belonging to tsdPtr. + */ + + if (fstat(filePtr->fd, &fdStat) == -1) { + Tcl_Panic("fstat: %s", strerror(errno)); + } else if ((fdStat.st_mode & S_IFMT) == S_IFREG) { + switch (op) { + case EV_ADD: + if (isNew) { + LIST_INSERT_HEAD(&tsdPtr->firstReadyFileHandlerPtr, filePtr, readyNode); + } + break; + case EV_DELETE: + LIST_REMOVE(filePtr, readyNode); + break; + } + return; + } + + numChanges = 0; + switch (op) { + case EV_ADD: + if (filePtr->mask & (TCL_READABLE | TCL_EXCEPTION)) { + EV_SET(&changeList[numChanges], (uintptr_t)filePtr->fd, EVFILT_READ, + op, 0, 0, filePtr->pedPtr); + numChanges++; + } + if (filePtr->mask & TCL_WRITABLE) { + EV_SET(&changeList[numChanges], (uintptr_t)filePtr->fd, EVFILT_WRITE, + op, 0, 0, filePtr->pedPtr); + numChanges++; + } + if (numChanges) { + if (kevent(tsdPtr->eventsFd, changeList, numChanges, NULL, 0, NULL) == -1) { + Tcl_Panic("kevent: %s", strerror(errno)); + } + } + break; + case EV_DELETE: + /* + * N.B. kqueue(2) has separate filters for readability and writabi- + * lity fd events. We therefore need to ensure that fds are + * ompletely removed from the kqueue(2) fd when deleting. + * This is exacerbated by changes to filePtr->mask w/o calls + * to PlatforEventsControl() after e.g. an exec(3) in a child + * process. + * As one of these calls can fail, two separate kevent(2) calls + * are made for EVFILT_{READ,WRITE}. + */ + EV_SET(&changeList[0], (uintptr_t)filePtr->fd, EVFILT_READ, op, 0, 0, NULL); + if ((kevent(tsdPtr->eventsFd, changeList, 1, NULL, 0, NULL) == -1) + && (errno != ENOENT)) { + Tcl_Panic("kevent: %s", strerror(errno)); + } + EV_SET(&changeList[0], (uintptr_t)filePtr->fd, EVFILT_WRITE, op, 0, 0, NULL); + if ((kevent(tsdPtr->eventsFd, changeList, 1, NULL, 0, NULL) == -1) + && (errno != ENOENT)) { + Tcl_Panic("kevent: %s", strerror(errno)); + } + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsFinalize -- + * + * This function closes the pipe and the kqueue file descriptors + * and frees the kevent structs owned by the thread of the caller. + * The above operations are protected by tsdPtr->notifierMutex, which + * is destroyed thereafter. + * + * Results: + * None. + * + * Side effects: + * While tsdPtr->notifierMutex is held: + * The per-thread pipe(2) fds are closed, if non-zero, and set to -1. + * The per-thread kqueue(2) fd is closed, if non-zero, and set to 0. + * The per-thread kevent structs are freed, if any, and set to 0. + * + * tsdPtr->notifierMutex is destroyed. + * + *---------------------------------------------------------------------- + */ + +void +PlatformEventsFinalize( + void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + pthread_mutex_lock(&tsdPtr->notifierMutex); + if (tsdPtr->triggerPipe[0]) { + close(tsdPtr->triggerPipe[0]); + tsdPtr->triggerPipe[0] = -1; + } + if (tsdPtr->triggerPipe[1]) { + close(tsdPtr->triggerPipe[1]); + tsdPtr->triggerPipe[1] = -1; + } + if (tsdPtr->eventsFd > 0) { + close(tsdPtr->eventsFd); + tsdPtr->eventsFd = 0; + } + if (tsdPtr->readyEvents) { + ckfree(tsdPtr->readyEvents); + tsdPtr->maxReadyEvents = 0; + } + pthread_mutex_unlock(&tsdPtr->notifierMutex); + if ((errno = pthread_mutex_destroy(&tsdPtr->notifierMutex))) { + Tcl_Panic("pthread_mutex_destroy: %s", strerror(errno)); + } +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsInit -- + * + * This function abstracts creating a kqueue fd via the kqueue + * system call and allocating memory for the kevents structs in + * tsdPtr for the thread of the caller. + * + * Results: + * None. + * + * Side effects: + * The following per-thread entities are initialised: + * notifierMutex is initialised. + * The pipe(2) is created; fcntl(2) is called on both fds to set + * FD_CLOEXEC and O_NONBLOCK. + * The kqueue(2) fd is created; fcntl(2) is called on it to set + * FD_CLOEXEC. + * A FileHandler struct is allocated and initialised for the event- + * fd(2), registering interest for TCL_READABLE on it via Platform- + * EventsControl(). + * readyEvents and maxReadyEvents are initialised with 512 kevents. + + *---------------------------------------------------------------------- + */ + +void +PlatformEventsInit( + void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int i, fdFl; + FileHandler *filePtr; + + errno = pthread_mutex_init(&tsdPtr->notifierMutex, NULL); + if (errno) { + Tcl_Panic("Tcl_InitNotifier: %s", "could not create mutex"); + } + if (pipe(tsdPtr->triggerPipe) != 0) { + Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger pipe"); + } else for (i = 0; i < 2; i++) { + if (fcntl(tsdPtr->triggerPipe[i], F_SETFD, FD_CLOEXEC) == -1) { + Tcl_Panic("fcntl: %s", strerror(errno)); + } else { + fdFl = fcntl(tsdPtr->triggerPipe[i], F_GETFL); + fdFl |= O_NONBLOCK; + } + if (fcntl(tsdPtr->triggerPipe[i], F_SETFL, fdFl) == -1) { + Tcl_Panic("fcntl: %s", strerror(errno)); + } + } + if ((tsdPtr->eventsFd = kqueue()) == -1) { + Tcl_Panic("kqueue: %s", strerror(errno)); + } else if (fcntl(tsdPtr->eventsFd, F_SETFD, FD_CLOEXEC) == -1) { + Tcl_Panic("fcntl: %s", strerror(errno)); + } + filePtr = ckalloc(sizeof(*filePtr)); + filePtr->fd = tsdPtr->triggerPipe[0]; + filePtr->mask = TCL_READABLE; + PlatformEventsControl(filePtr, tsdPtr, EV_ADD, 1); + if (!tsdPtr->readyEvents) { + tsdPtr->maxReadyEvents = 512; + tsdPtr->readyEvents = ckalloc(tsdPtr->maxReadyEvents + * sizeof(tsdPtr->readyEvents[0])); + } + LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr); +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsTranslate -- + * + * This function translates the platform-specific mask of returned + * events in eventPtr to a mask of TCL_* bits. + * + * Results: + * Returns the translated mask. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +PlatformEventsTranslate( + struct kevent *eventPtr) +{ + int mask; + + mask = 0; + if (eventPtr->filter == EVFILT_READ) { + mask |= TCL_READABLE; + if (eventPtr->flags & EV_ERROR) { + mask |= TCL_EXCEPTION; + } + } + if (eventPtr->filter == EVFILT_WRITE) { + mask |= TCL_WRITABLE; + if (eventPtr->flags & EV_ERROR) { + mask |= TCL_EXCEPTION; + } + } + return mask; +} + +/* + *---------------------------------------------------------------------- + * + * PlatformEventsWait -- + * + * This function abstracts waiting for I/O events via the kevent + * system call. + * + * Results: + * Returns -1 if kevent failed. Returns 0 if polling and if no events + * became available whilst polling. Returns a pointer to and the count + * of all returned events in all other cases. + * + * Side effects: + * gettimeofday(2), kevent(2), and gettimeofday(2) are called, + * in the specified order. + * If timePtr specifies a positive value, it is updated to reflect + * the amount of time that has passed; if its value would {under, + * over}flow, it is set to zero. + * + *---------------------------------------------------------------------- + */ + +int +PlatformEventsWait( + struct kevent *events, + size_t numEvents, + struct timeval *timePtr) +{ + int numFound; + struct timeval tv0, tv1, tv_delta; + struct timespec timeout, *timeoutPtr; + + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * If timePtr is NULL, kevent(2) will wait indefinitely. If it speci- + * fies a timeout of {0,0}, kevent(2) will poll. Otherwise, the time- + * out will simply be converted to a timespec. + */ + + if (!timePtr) { + timeoutPtr = NULL; + } else if (!timePtr->tv_sec && !timePtr->tv_usec) { + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + timeoutPtr = &timeout; + } else { + timeout.tv_sec = timePtr->tv_sec; + timeout.tv_nsec = timePtr->tv_usec * 1000; + timeoutPtr = &timeout; + } + + /* + * Call (and possibly block on) kevent(2) and substract the delta of + * gettimeofday(2) before and after the call from timePtr if the latter + * is not NULL. Return the number of events returned by kevent(2). + */ + + gettimeofday(&tv0, NULL); + numFound = kevent(tsdPtr->eventsFd, NULL, 0, events, (int)numEvents, timeoutPtr); + gettimeofday(&tv1, NULL); + if (timePtr && (timePtr->tv_sec && timePtr->tv_usec)) { + timersub(&tv1, &tv0, &tv_delta); + if (!timercmp(&tv_delta, timePtr, >)) { + timersub(timePtr, &tv_delta, timePtr); + } else { + timePtr->tv_sec = 0; + timePtr->tv_usec = 0; + } + } + return numFound; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_CreateFileHandler -- + * + * This function registers a file handler with the kqueue notifier + * of the thread of the caller. + * + * Results: + * None. + * + * Side effects: + * Creates a new file handler structure. + * PlatformEventsControl() is called for the new file handler structure. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_CreateFileHandler( + int fd, /* Handle of stream to watch. */ + int mask, /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, and TCL_EXCEPTION: indicates + * conditions under which proc should be + * called. */ + Tcl_FileProc *proc, /* Function to call for each selected + * event. */ + ClientData clientData) /* Arbitrary data to pass to proc. */ +{ + int isNew; + + if (tclNotifierHooks.createFileHandlerProc) { + tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData); + return; + } else { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + FileHandler *filePtr; + + for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; + filePtr = filePtr->nextPtr) { + if (filePtr->fd == fd) { + break; + } + } + if (filePtr == NULL) { + filePtr = ckalloc(sizeof(FileHandler)); + filePtr->fd = fd; + filePtr->readyMask = 0; + filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; + tsdPtr->firstFileHandlerPtr = filePtr; + isNew = 1; + } else { + isNew = 0; + } + filePtr->proc = proc; + filePtr->clientData = clientData; + filePtr->mask = mask; + + PlatformEventsControl(filePtr, tsdPtr, EV_ADD, isNew); + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_DeleteFileHandler -- + * + * Cancel a previously-arranged callback arrangement for a file on + * the kqueue of the thread of the caller. + * + * Results: + * None. + * + * Side effects: + * If a callback was previously registered on file, remove it. + * PlatformEventsControl() is called for the file handler structure. + * The PlatformEventData struct associated with the new file handler + * structure is freed. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_DeleteFileHandler( + int fd) /* Stream id for which to remove callback + * function. */ +{ + if (tclNotifierHooks.deleteFileHandlerProc) { + tclNotifierHooks.deleteFileHandlerProc(fd); + return; + } else { + FileHandler *filePtr, *prevPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Find the entry for the given file (and return if there isn't one). + */ + + for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; + prevPtr = filePtr, filePtr = filePtr->nextPtr) { + if (filePtr == NULL) { + return; + } + if (filePtr->fd == fd) { + break; + } + } + + /* + * Update the check masks for this file. + */ + + PlatformEventsControl(filePtr, tsdPtr, EV_DELETE, 0); + if (filePtr->pedPtr) { + ckfree(filePtr->pedPtr); + } + + /* + * Clean up information in the callback record. + */ + + if (prevPtr == NULL) { + tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; + } else { + prevPtr->nextPtr = filePtr->nextPtr; + } + ckfree(filePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitForEvent -- + * + * This function is called by Tcl_DoOneEvent to wait for new events on + * the message queue. If the block time is 0, then Tcl_WaitForEvent just + * polls without blocking. + * The waiting logic is implemented in PlatformEventsWait. + * + * Results: + * Returns -1 if PlatformEventsWait() would block forever, otherwise + * returns 0. + * + * Side effects: + * Queues file events that are detected by PlatformEventsWait(). + * + *---------------------------------------------------------------------- + */ + +int +Tcl_WaitForEvent( + const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ +{ + if (tclNotifierHooks.waitForEventProc) { + return tclNotifierHooks.waitForEventProc(timePtr); + } else { + FileHandler *filePtr; + int mask; + Tcl_Time vTime; + /* + * Impl. notes: timeout & timeoutPtr are used if, and only if threads + * are not enabled. They are the arguments for the regular epoll_wait() + * used when the core is not thread-enabled. + */ + + struct timeval timeout, *timeoutPtr; + int numFound, numEvent; + struct PlatformEventData *pedPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int numQueued; + ssize_t i; + char buf[1]; + + /* + * Set up the timeout structure. Note that if there are no events to + * check for, we return with a negative result rather than blocking + * forever. + */ + + if (timePtr != NULL) { + /* + * TIP #233 (Virtualized Time). Is virtual time in effect? And do + * we actually have something to scale? If yes to both then we + * call the handler to do this scaling. + */ + + if (timePtr->sec != 0 || timePtr->usec != 0) { + vTime = *timePtr; + tclScaleTimeProcPtr(&vTime, tclTimeClientData); + timePtr = &vTime; + } + timeout.tv_sec = timePtr->sec; + timeout.tv_usec = timePtr->usec; + timeoutPtr = &timeout; + } else { + timeoutPtr = NULL; + } + + /* + * Walk the list of FileHandlers associated with regular files + * (S_IFREG) belonging to tsdPtr, queue Tcl events for them, and + * update their mask of events of interest. + * kqueue(2), unlike epoll(7), does support regular files, but + * EVFILT_READ only `[r]eturns when the file pointer is not at + * the end of file' as opposed to unconditionally. While FreeBSD + * 11.0-RELEASE adds support for this mode (NOTE_FILE_POLL,) this + * is not used for reasons of compatibility. + * Therefore, the behaviour of {select,poll}(2) is simply simulated + * here: fds associated with regular files are added to this list by + * PlatformEventsControl() and processed here before calling (and + * possibly blocking) on PlatformEventsWait(). + */ + + numQueued = 0; + LIST_FOREACH(filePtr, &tsdPtr->firstReadyFileHandlerPtr, readyNode) { + mask = 0; + if (filePtr->mask & TCL_READABLE) { + mask |= TCL_READABLE; + } + if (filePtr->mask & TCL_WRITABLE) { + mask |= TCL_WRITABLE; + } + + /* + * Don't bother to queue an event if the mask was previously + * non-zero since an event must still be on the queue. + */ + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = + ckalloc(sizeof(FileHandlerEvent)); + + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + numQueued++; + } + filePtr->readyMask = mask; + } + + /* + * If any events were queued in the above loop, force PlatformEvents- + * Wait() to poll as there already are events that need to be processed + * at this point. + */ + + if (numQueued) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + timeoutPtr = &timeout; + } + + /* + * Wait or poll for new events, queue Tcl events for the FileHandlers + * corresponding to them, and update the FileHandlers' mask of events + * of interest registered by the last call to Tcl_CreateFileHandler(). + * + * Events for the trigger pipe are processed here in order to facilitate + * inter-thread IPC. If another thread intends to wake up this thread + * whilst it's blocking on PlatformEventsWait(), it write(2)s to the + * other end of the pipe (see Tcl_AlertNotifier(),) which in turn will + * cause PlatformEventsWait() to return immediately. + */ + + numFound = PlatformEventsWait(tsdPtr->readyEvents, tsdPtr->maxReadyEvents, timeoutPtr); + for (numEvent = 0; numEvent < numFound; numEvent++) { + pedPtr = (struct PlatformEventData *)tsdPtr->readyEvents[numEvent].udata; + filePtr = pedPtr->filePtr; + mask = PlatformEventsTranslate(&tsdPtr->readyEvents[numEvent]); + if (filePtr->fd == tsdPtr->triggerPipe[0]) { + do { + i = read(tsdPtr->triggerPipe[0], buf, 1); + if ((i == -1) && (errno != EAGAIN)) { + Tcl_Panic("Tcl_WaitForEvent: " + "read from %p->triggerPipe: %s", + (void *)tsdPtr, strerror(errno)); + } else { + break; + } + } while (1); + continue; + } + if (!mask) { + continue; + } + + /* + * Don't bother to queue an event if the mask was previously + * non-zero since an event must still be on the queue. + */ + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = + ckalloc(sizeof(FileHandlerEvent)); + + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + } + filePtr->readyMask |= mask; + } + return 0; + } +} + +#endif /* !HAVE_COREFOUNDATION */ + +#endif /* NOTIFIER_KQUEUE */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/unix/tclSelectNotfy.c b/unix/tclSelectNotfy.c new file mode 100644 index 0000000..f57cf74 --- /dev/null +++ b/unix/tclSelectNotfy.c @@ -0,0 +1,1117 @@ +/* + * tclSelectNotfy.c -- + * + * This file contains the implementation of the select()-based + * generic Unix notifier, which is the lowest-level part of the + * Tcl event loop. This file works together with generic/tclNotify.c. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#if !defined(NOTIFIER_EPOLL) && !defined(NOTIFIER_KQUEUE) + +#include "tclInt.h" +#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is + * in tclMacOSXNotify.c */ +#include <signal.h> + +/* + * This structure is used to keep track of the notifier info for a registered + * file. + */ + +typedef struct FileHandler { + int fd; + int mask; /* Mask of desired events: TCL_READABLE, + * etc. */ + int readyMask; /* Mask of events that have been seen since + * the last time file handlers were invoked + * for this file. */ + Tcl_FileProc *proc; /* Function to call, in the style of + * Tcl_CreateFileHandler. */ + ClientData clientData; /* Argument to pass to proc. */ + struct FileHandler *nextPtr;/* Next in list of all files we care about. */ +} FileHandler; + +/* + * The following structure contains a set of select() masks to track readable, + * writable, and exception conditions. + */ + +typedef struct { + fd_set readable; + fd_set writable; + fd_set exception; +} SelectMasks; + +/* + * The following structure is what is added to the Tcl event queue when file + * handlers are ready to fire. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + int fd; /* File descriptor that is ready. Used to find + * the FileHandler structure for the file + * (can't point directly to the FileHandler + * structure because it could go away while + * the event is queued). */ +} FileHandlerEvent; + +/* + * The following static structure contains the state information for the + * select based implementation of the Tcl notifier. One of these structures is + * created for each thread that is using the notifier. + */ + +typedef struct ThreadSpecificData { + FileHandler *firstFileHandlerPtr; + /* Pointer to head of file handler list. */ + SelectMasks checkMasks; /* This structure is used to build up the + * masks to be used in the next call to + * select. Bits are set in response to calls + * to Tcl_CreateFileHandler. */ + SelectMasks readyMasks; /* This array reflects the readable/writable + * conditions that were found to exist by the + * last call to select. */ + int numFdBits; /* Number of valid bits in checkMasks (one + * more than highest fd for which + * Tcl_WatchFile has been called). */ +#ifdef TCL_THREADS + int onList; /* True if it is in this list */ + unsigned int pollState; /* pollState is used to implement a polling + * handshake between each thread and the + * notifier thread. Bits defined below. */ + struct ThreadSpecificData *nextPtr, *prevPtr; + /* All threads that are currently waiting on + * an event have their ThreadSpecificData + * structure on a doubly-linked listed formed + * from these pointers. You must hold the + * notifierMutex lock before accessing these + * fields. */ +#ifdef __CYGWIN__ + void *event; /* Any other thread alerts a notifier + * that an event is ready to be processed + * by sending this event. */ + void *hwnd; /* Messaging window. */ +#else /* !__CYGWIN__ */ + pthread_cond_t waitCV; /* Any other thread alerts a notifier that an + * event is ready to be processed by signaling + * this condition variable. */ +#endif /* __CYGWIN__ */ + int waitCVinitialized; /* Variable to flag initialization of the structure */ + int eventReady; /* True if an event is ready to be processed. + * Used as condition flag together with waitCV + * above. */ +#endif /* TCL_THREADS */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +#ifdef TCL_THREADS +/* + * The following static indicates the number of threads that have initialized + * notifiers. + * + * You must hold the notifierMutex lock before accessing this variable. + */ + +static int notifierCount = 0; + +/* + * The following variable points to the head of a doubly-linked list of + * ThreadSpecificData structures for all threads that are currently waiting on + * an event. + * + * You must hold the notifierMutex lock before accessing this list. + */ + +static ThreadSpecificData *waitingListPtr = NULL; + +/* + * The notifier thread spends all its time in select() waiting for a file + * descriptor associated with one of the threads on the waitingListPtr list to + * do something interesting. But if the contents of the waitingListPtr list + * ever changes, we need to wake up and restart the select() system call. You + * can wake up the notifier thread by writing a single byte to the file + * descriptor defined below. This file descriptor is the input-end of a pipe + * and the notifier thread is listening for data on the output-end of the same + * pipe. Hence writing to this file descriptor will cause the select() system + * call to return and wake up the notifier thread. + * + * You must hold the notifierMutex lock before writing to the pipe. + */ + +static int triggerPipe = -1; + +/* + * The notifierMutex locks access to all of the global notifier state. + */ + +static pthread_mutex_t notifierInitMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t notifierMutex = PTHREAD_MUTEX_INITIALIZER; +/* + * The following static indicates if the notifier thread is running. + * + * You must hold the notifierInitMutex before accessing this variable. + */ + +static int notifierThreadRunning = 0; + +/* + * The notifier thread signals the notifierCV when it has finished + * initializing the triggerPipe and right before the notifier thread + * terminates. + */ + +static pthread_cond_t notifierCV = PTHREAD_COND_INITIALIZER; + +/* + * The pollState bits + * POLL_WANT is set by each thread before it waits on its condition + * variable. It is checked by the notifier before it does select. + * POLL_DONE is set by the notifier if it goes into select after seeing + * POLL_WANT. The idea is to ensure it tries a select with the + * same bits the initial thread had set. + */ + +#define POLL_WANT 0x1 +#define POLL_DONE 0x2 + +/* + * This is the thread ID of the notifier thread that does select. + */ + +static Tcl_ThreadId notifierThread; +#endif /* TCL_THREADS */ + +/* + * Static routines defined in this file. + */ + +#ifdef TCL_THREADS +static void NotifierThreadProc(ClientData clientData); +#if defined(HAVE_PTHREAD_ATFORK) +static int atForkInit = 0; +static void AtForkChild(void); +#endif /* HAVE_PTHREAD_ATFORK */ +#endif /* TCL_THREADS */ +static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); + +/* + * Import of Windows API when building threaded with Cygwin. + */ + +#if defined(TCL_THREADS) && defined(__CYGWIN__) +typedef struct { + void *hwnd; + unsigned int *message; + int wParam; + int lParam; + int time; + int x; + int y; +} MSG; + +typedef struct { + unsigned int style; + void *lpfnWndProc; + int cbClsExtra; + int cbWndExtra; + void *hInstance; + void *hIcon; + void *hCursor; + void *hbrBackground; + void *lpszMenuName; + const void *lpszClassName; +} WNDCLASS; + +extern void __stdcall CloseHandle(void *); +extern void *__stdcall CreateEventW(void *, unsigned char, unsigned char, + void *); +extern void * __stdcall CreateWindowExW(void *, const void *, const void *, + DWORD, int, int, int, int, void *, void *, void *, void *); +extern DWORD __stdcall DefWindowProcW(void *, int, void *, void *); +extern unsigned char __stdcall DestroyWindow(void *); +extern int __stdcall DispatchMessageW(const MSG *); +extern unsigned char __stdcall GetMessageW(MSG *, void *, int, int); +extern void __stdcall MsgWaitForMultipleObjects(DWORD, void *, + unsigned char, DWORD, DWORD); +extern unsigned char __stdcall PeekMessageW(MSG *, void *, int, int, int); +extern unsigned char __stdcall PostMessageW(void *, unsigned int, void *, + void *); +extern void __stdcall PostQuitMessage(int); +extern void *__stdcall RegisterClassW(const WNDCLASS *); +extern unsigned char __stdcall ResetEvent(void *); +extern unsigned char __stdcall TranslateMessage(const MSG *); + +/* + * Threaded-cygwin specific constants and functions in this file: + */ + +static const WCHAR className[] = L"TclNotifier"; +static DWORD __stdcall NotifierProc(void *hwnd, unsigned int message, + void *wParam, void *lParam); +#endif /* TCL_THREADS && __CYGWIN__ */ + + +#include "tclUnixNotfy.c" + +/* + *---------------------------------------------------------------------- + * + * Tcl_InitNotifier -- + * + * Initializes the platform specific notifier state. + * + * Results: + * Returns a handle to the notifier state for this thread. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +ClientData +Tcl_InitNotifier(void) +{ + if (tclNotifierHooks.initNotifierProc) { + return tclNotifierHooks.initNotifierProc(); + } else { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + +#ifdef TCL_THREADS + tsdPtr->eventReady = 0; + + /* + * Initialize thread specific condition variable for this thread. + */ + if (tsdPtr->waitCVinitialized == 0) { +#ifdef __CYGWIN__ + WNDCLASS class; + + class.style = 0; + class.cbClsExtra = 0; + class.cbWndExtra = 0; + class.hInstance = TclWinGetTclInstance(); + class.hbrBackground = NULL; + class.lpszMenuName = NULL; + class.lpszClassName = className; + class.lpfnWndProc = NotifierProc; + class.hIcon = NULL; + class.hCursor = NULL; + + RegisterClassW(&class); + tsdPtr->hwnd = CreateWindowExW(NULL, class.lpszClassName, + class.lpszClassName, 0, 0, 0, 0, 0, NULL, NULL, + TclWinGetTclInstance(), NULL); + tsdPtr->event = CreateEventW(NULL, 1 /* manual */, + 0 /* !signaled */, NULL); +#else + pthread_cond_init(&tsdPtr->waitCV, NULL); +#endif /* __CYGWIN__ */ + tsdPtr->waitCVinitialized = 1; + } + + pthread_mutex_lock(¬ifierInitMutex); +#if defined(HAVE_PTHREAD_ATFORK) + /* + * Install pthread_atfork handlers to clean up the notifier in the + * child of a fork. + */ + + if (!atForkInit) { + int result = pthread_atfork(NULL, NULL, AtForkChild); + + if (result) { + Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed"); + } + atForkInit = 1; + } +#endif /* HAVE_PTHREAD_ATFORK */ + + notifierCount++; + + pthread_mutex_unlock(¬ifierInitMutex); + +#endif /* TCL_THREADS */ + return tsdPtr; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_FinalizeNotifier -- + * + * This function is called to cleanup the notifier state before a thread + * is terminated. + * + * Results: + * None. + * + * Side effects: + * May terminate the background notifier thread if this is the last + * notifier instance. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_FinalizeNotifier( + ClientData clientData) /* Not used. */ +{ + if (tclNotifierHooks.finalizeNotifierProc) { + tclNotifierHooks.finalizeNotifierProc(clientData); + return; + } else { +#ifdef TCL_THREADS + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + pthread_mutex_lock(¬ifierInitMutex); + notifierCount--; + + /* + * If this is the last thread to use the notifier, close the notifier + * pipe and wait for the background thread to terminate. + */ + + if (notifierCount == 0) { + + if (triggerPipe != -1) { + if (write(triggerPipe, "q", 1) != 1) { + Tcl_Panic("Tcl_FinalizeNotifier: %s", + "unable to write q to triggerPipe"); + } + close(triggerPipe); + pthread_mutex_lock(¬ifierMutex); + while(triggerPipe != -1) { + pthread_cond_wait(¬ifierCV, ¬ifierMutex); + } + pthread_mutex_unlock(¬ifierMutex); + if (notifierThreadRunning) { + int result = pthread_join((pthread_t) notifierThread, NULL); + + if (result) { + Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier " + "thread"); + } + notifierThreadRunning = 0; + } + } + } + + /* + * Clean up any synchronization objects in the thread local storage. + */ + +#ifdef __CYGWIN__ + DestroyWindow(tsdPtr->hwnd); + CloseHandle(tsdPtr->event); +#else /* __CYGWIN__ */ + pthread_cond_destroy(&tsdPtr->waitCV); +#endif /* __CYGWIN__ */ + tsdPtr->waitCVinitialized = 0; + + pthread_mutex_unlock(¬ifierInitMutex); +#endif /* TCL_THREADS */ + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_CreateFileHandler -- + * + * This function registers a file handler with the select notifier. + * + * Results: + * None. + * + * Side effects: + * Creates a new file handler structure. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_CreateFileHandler( + int fd, /* Handle of stream to watch. */ + int mask, /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, and TCL_EXCEPTION: indicates + * conditions under which proc should be + * called. */ + Tcl_FileProc *proc, /* Function to call for each selected + * event. */ + ClientData clientData) /* Arbitrary data to pass to proc. */ +{ + if (tclNotifierHooks.createFileHandlerProc) { + tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData); + return; + } else { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + FileHandler *filePtr; + + for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; + filePtr = filePtr->nextPtr) { + if (filePtr->fd == fd) { + break; + } + } + if (filePtr == NULL) { + filePtr = ckalloc(sizeof(FileHandler)); + filePtr->fd = fd; + filePtr->readyMask = 0; + filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; + tsdPtr->firstFileHandlerPtr = filePtr; + } + filePtr->proc = proc; + filePtr->clientData = clientData; + filePtr->mask = mask; + + /* + * Update the check masks for this file. + */ + + if (mask & TCL_READABLE) { + FD_SET(fd, &tsdPtr->checkMasks.readable); + } else { + FD_CLR(fd, &tsdPtr->checkMasks.readable); + } + if (mask & TCL_WRITABLE) { + FD_SET(fd, &tsdPtr->checkMasks.writable); + } else { + FD_CLR(fd, &tsdPtr->checkMasks.writable); + } + if (mask & TCL_EXCEPTION) { + FD_SET(fd, &tsdPtr->checkMasks.exception); + } else { + FD_CLR(fd, &tsdPtr->checkMasks.exception); + } + if (tsdPtr->numFdBits <= fd) { + tsdPtr->numFdBits = fd+1; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_DeleteFileHandler -- + * + * Cancel a previously-arranged callback arrangement for a file. + * + * Results: + * None. + * + * Side effects: + * If a callback was previously registered on file, remove it. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_DeleteFileHandler( + int fd) /* Stream id for which to remove callback + * function. */ +{ + if (tclNotifierHooks.deleteFileHandlerProc) { + tclNotifierHooks.deleteFileHandlerProc(fd); + return; + } else { + FileHandler *filePtr, *prevPtr; + int i; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Find the entry for the given file (and return if there isn't one). + */ + + for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; + prevPtr = filePtr, filePtr = filePtr->nextPtr) { + if (filePtr == NULL) { + return; + } + if (filePtr->fd == fd) { + break; + } + } + + /* + * Update the check masks for this file. + */ + + if (filePtr->mask & TCL_READABLE) { + FD_CLR(fd, &tsdPtr->checkMasks.readable); + } + if (filePtr->mask & TCL_WRITABLE) { + FD_CLR(fd, &tsdPtr->checkMasks.writable); + } + if (filePtr->mask & TCL_EXCEPTION) { + FD_CLR(fd, &tsdPtr->checkMasks.exception); + } + + /* + * Find current max fd. + */ + + if (fd+1 == tsdPtr->numFdBits) { + int numFdBits = 0; + + for (i = fd-1; i >= 0; i--) { + if (FD_ISSET(i, &tsdPtr->checkMasks.readable) + || FD_ISSET(i, &tsdPtr->checkMasks.writable) + || FD_ISSET(i, &tsdPtr->checkMasks.exception)) { + numFdBits = i+1; + break; + } + } + tsdPtr->numFdBits = numFdBits; + } + + /* + * Clean up information in the callback record. + */ + + if (prevPtr == NULL) { + tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; + } else { + prevPtr->nextPtr = filePtr->nextPtr; + } + ckfree(filePtr); + } +} + +#if defined(TCL_THREADS) && defined(__CYGWIN__) + +static DWORD __stdcall +NotifierProc( + void *hwnd, + unsigned int message, + void *wParam, + void *lParam) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (message != 1024) { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + + /* + * Process all of the runnable events. + */ + + tsdPtr->eventReady = 1; + Tcl_ServiceAll(); + return 0; +} +#endif /* TCL_THREADS && __CYGWIN__ */ + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitForEvent -- + * + * This function is called by Tcl_DoOneEvent to wait for new events on + * the message queue. If the block time is 0, then Tcl_WaitForEvent just + * polls without blocking. + * + * Results: + * Returns -1 if the select would block forever, otherwise returns 0. + * + * Side effects: + * Queues file events that are detected by the select. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_WaitForEvent( + const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ +{ + if (tclNotifierHooks.waitForEventProc) { + return tclNotifierHooks.waitForEventProc(timePtr); + } else { + FileHandler *filePtr; + int mask; + Tcl_Time vTime; +#ifdef TCL_THREADS + int waitForFiles; +# ifdef __CYGWIN__ + MSG msg; +# endif /* __CYGWIN__ */ +#else + /* + * Impl. notes: timeout & timeoutPtr are used if, and only if threads + * are not enabled. They are the arguments for the regular select() + * used when the core is not thread-enabled. + */ + + struct timeval timeout, *timeoutPtr; + int numFound; +#endif /* TCL_THREADS */ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Set up the timeout structure. Note that if there are no events to + * check for, we return with a negative result rather than blocking + * forever. + */ + + if (timePtr != NULL) { + /* + * TIP #233 (Virtualized Time). Is virtual time in effect? And do + * we actually have something to scale? If yes to both then we + * call the handler to do this scaling. + */ + + if (timePtr->sec != 0 || timePtr->usec != 0) { + vTime = *timePtr; + tclScaleTimeProcPtr(&vTime, tclTimeClientData); + timePtr = &vTime; + } +#ifndef TCL_THREADS + timeout.tv_sec = timePtr->sec; + timeout.tv_usec = timePtr->usec; + timeoutPtr = &timeout; + } else if (tsdPtr->numFdBits == 0) { + /* + * If there are no threads, no timeout, and no fds registered, + * then there are no events possible and we must avoid deadlock. + * Note that this is not entirely correct because there might be a + * signal that could interrupt the select call, but we don't + * handle that case if we aren't using threads. + */ + + return -1; + } else { + timeoutPtr = NULL; +#endif /* !TCL_THREADS */ + } + +#ifdef TCL_THREADS + /* + * Start notifier thread and place this thread on the list of + * interested threads, signal the notifier thread, and wait for a + * response or a timeout. + */ + StartNotifierThread("Tcl_WaitForEvent"); + + pthread_mutex_lock(¬ifierMutex); + + if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0 +#if defined(__APPLE__) && defined(__LP64__) + /* + * On 64-bit Darwin, pthread_cond_timedwait() appears to have + * a bug that causes it to wait forever when passed an + * absolute time which has already been exceeded by the system + * time; as a workaround, when given a very brief timeout, + * just do a poll. [Bug 1457797] + */ + || timePtr->usec < 10 +#endif /* __APPLE__ && __LP64__ */ + )) { + /* + * Cannot emulate a polling select with a polling condition + * variable. Instead, pretend to wait for files and tell the + * notifier thread what we are doing. The notifier thread makes + * sure it goes through select with its select mask in the same + * state as ours currently is. We block until that happens. + */ + + waitForFiles = 1; + tsdPtr->pollState = POLL_WANT; + timePtr = NULL; + } else { + waitForFiles = (tsdPtr->numFdBits > 0); + tsdPtr->pollState = 0; + } + + if (waitForFiles) { + /* + * Add the ThreadSpecificData structure of this thread to the list + * of ThreadSpecificData structures of all threads that are + * waiting on file events. + */ + + tsdPtr->nextPtr = waitingListPtr; + if (waitingListPtr) { + waitingListPtr->prevPtr = tsdPtr; + } + tsdPtr->prevPtr = 0; + waitingListPtr = tsdPtr; + tsdPtr->onList = 1; + + if ((write(triggerPipe, "", 1) == -1) && (errno != EAGAIN)) { + Tcl_Panic("Tcl_WaitForEvent: %s", + "unable to write to triggerPipe"); + } + } + + FD_ZERO(&tsdPtr->readyMasks.readable); + FD_ZERO(&tsdPtr->readyMasks.writable); + FD_ZERO(&tsdPtr->readyMasks.exception); + + if (!tsdPtr->eventReady) { +#ifdef __CYGWIN__ + if (!PeekMessageW(&msg, NULL, 0, 0, 0)) { + DWORD timeout; + + if (timePtr) { + timeout = timePtr->sec * 1000 + timePtr->usec / 1000; + } else { + timeout = 0xFFFFFFFF; + } + pthread_mutex_unlock(¬ifierMutex); + MsgWaitForMultipleObjects(1, &tsdPtr->event, 0, timeout, 1279); + pthread_mutex_lock(¬ifierMutex); + } +#else + if (timePtr != NULL) { + Tcl_Time now; + struct timespec ptime; + + Tcl_GetTime(&now); + ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000; + ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); + + pthread_cond_timedwait(&tsdPtr->waitCV, ¬ifierMutex, &ptime); + } else { + pthread_cond_wait(&tsdPtr->waitCV, ¬ifierMutex); + } +#endif /* __CYGWIN__ */ + } + tsdPtr->eventReady = 0; + +#ifdef __CYGWIN__ + while (PeekMessageW(&msg, NULL, 0, 0, 0)) { + /* + * Retrieve and dispatch the message. + */ + + DWORD result = GetMessageW(&msg, NULL, 0, 0); + + if (result == 0) { + PostQuitMessage(msg.wParam); + /* What to do here? */ + } else if (result != (DWORD) -1) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + ResetEvent(tsdPtr->event); +#endif /* __CYGWIN__ */ + + if (waitForFiles && tsdPtr->onList) { + /* + * Remove the ThreadSpecificData structure of this thread from the + * waiting list. Alert the notifier thread to recompute its select + * masks - skipping this caused a hang when trying to close a pipe + * which the notifier thread was still doing a select on. + */ + + if (tsdPtr->prevPtr) { + tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; + } else { + waitingListPtr = tsdPtr->nextPtr; + } + if (tsdPtr->nextPtr) { + tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; + } + tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; + tsdPtr->onList = 0; + if ((write(triggerPipe, "", 1) == -1) && (errno != EAGAIN)) { + Tcl_Panic("Tcl_WaitForEvent: %s", + "unable to write to triggerPipe"); + } + } + +#else + tsdPtr->readyMasks = tsdPtr->checkMasks; + numFound = select(tsdPtr->numFdBits, &tsdPtr->readyMasks.readable, + &tsdPtr->readyMasks.writable, &tsdPtr->readyMasks.exception, + timeoutPtr); + + /* + * Some systems don't clear the masks after an error, so we have to do + * it here. + */ + + if (numFound == -1) { + FD_ZERO(&tsdPtr->readyMasks.readable); + FD_ZERO(&tsdPtr->readyMasks.writable); + FD_ZERO(&tsdPtr->readyMasks.exception); + } +#endif /* TCL_THREADS */ + + /* + * Queue all detected file events before returning. + */ + + for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); + filePtr = filePtr->nextPtr) { + mask = 0; + if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.readable)) { + mask |= TCL_READABLE; + } + if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.writable)) { + mask |= TCL_WRITABLE; + } + if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.exception)) { + mask |= TCL_EXCEPTION; + } + + if (!mask) { + continue; + } + + /* + * Don't bother to queue an event if the mask was previously + * non-zero since an event must still be on the queue. + */ + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = + ckalloc(sizeof(FileHandlerEvent)); + + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + } + filePtr->readyMask = mask; + } +#ifdef TCL_THREADS + pthread_mutex_unlock(¬ifierMutex); +#endif /* TCL_THREADS */ + return 0; + } +} + +#ifdef TCL_THREADS + +/* + *---------------------------------------------------------------------- + * + * 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 + * to notify other threads who are interested in this information by + * signalling a condition variable. Other threads can signal this + * notifier thread of a change in their interests by writing a single + * byte to a special pipe that the notifier thread is monitoring. + * + * Result: + * None. Once started, this routine never exits. It dies with the overall + * process. + * + * Side effects: + * The trigger pipe used to signal the notifier thread is created when + * the notifier thread first starts. + * + *---------------------------------------------------------------------- + */ + +static void +NotifierThreadProc( + ClientData clientData) /* Not used. */ +{ + ThreadSpecificData *tsdPtr; + fd_set readableMask; + fd_set writableMask; + fd_set exceptionMask; + int i; + int fds[2], receivePipe; + 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"); + } + + receivePipe = fds[0]; + + if (TclUnixSetBlockingMode(receivePipe, TCL_MODE_NONBLOCKING) < 0) { + Tcl_Panic("NotifierThreadProc: %s", + "could not make receive pipe non blocking"); + } + if (TclUnixSetBlockingMode(fds[1], TCL_MODE_NONBLOCKING) < 0) { + Tcl_Panic("NotifierThreadProc: %s", + "could not make trigger pipe non blocking"); + } + if (fcntl(receivePipe, F_SETFD, FD_CLOEXEC) < 0) { + Tcl_Panic("NotifierThreadProc: %s", + "could not make receive pipe close-on-exec"); + } + if (fcntl(fds[1], F_SETFD, FD_CLOEXEC) < 0) { + Tcl_Panic("NotifierThreadProc: %s", + "could not make trigger pipe close-on-exec"); + } + + /* + * Install the write end of the pipe into the global variable. + */ + + pthread_mutex_lock(¬ifierMutex); + triggerPipe = fds[1]; + + /* + * Signal any threads that are waiting. + */ + + pthread_cond_broadcast(¬ifierCV); + pthread_mutex_unlock(¬ifierMutex); + + /* + * Look for file events and report them to interested threads. + */ + + while (1) { + FD_ZERO(&readableMask); + FD_ZERO(&writableMask); + FD_ZERO(&exceptionMask); + + /* + * Compute the logical OR of the masks from all the waiting + * notifiers. + */ + + pthread_mutex_lock(¬ifierMutex); + timePtr = NULL; + for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { + for (i = tsdPtr->numFdBits-1; i >= 0; --i) { + if (FD_ISSET(i, &tsdPtr->checkMasks.readable)) { + FD_SET(i, &readableMask); + } + if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) { + FD_SET(i, &writableMask); + } + if (FD_ISSET(i, &tsdPtr->checkMasks.exception)) { + FD_SET(i, &exceptionMask); + } + } + if (tsdPtr->numFdBits > numFdBits) { + numFdBits = tsdPtr->numFdBits; + } + if (tsdPtr->pollState & POLL_WANT) { + /* + * Here we make sure we go through select() with the same mask + * bits that were present when the thread tried to poll. + */ + + tsdPtr->pollState |= POLL_DONE; + timePtr = &poll; + } + } + pthread_mutex_unlock(¬ifierMutex); + + /* + * Set up the mask to include the receive pipe. + */ + + if (receivePipe >= numFdBits) { + numFdBits = receivePipe + 1; + } + FD_SET(receivePipe, &readableMask); + + if (select(numFdBits, &readableMask, &writableMask, &exceptionMask, + timePtr) == -1) { + /* + * Try again immediately on an error. + */ + + continue; + } + + /* + * Alert any threads that are waiting on a ready file descriptor. + */ + + pthread_mutex_lock(¬ifierMutex); + for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { + found = 0; + + for (i = tsdPtr->numFdBits-1; i >= 0; --i) { + if (FD_ISSET(i, &tsdPtr->checkMasks.readable) + && FD_ISSET(i, &readableMask)) { + FD_SET(i, &tsdPtr->readyMasks.readable); + found = 1; + } + if (FD_ISSET(i, &tsdPtr->checkMasks.writable) + && FD_ISSET(i, &writableMask)) { + FD_SET(i, &tsdPtr->readyMasks.writable); + found = 1; + } + if (FD_ISSET(i, &tsdPtr->checkMasks.exception) + && FD_ISSET(i, &exceptionMask)) { + FD_SET(i, &tsdPtr->readyMasks.exception); + found = 1; + } + } + + if (found || (tsdPtr->pollState & POLL_DONE)) { + AlertSingleThread(tsdPtr); + } + } + pthread_mutex_unlock(¬ifierMutex); + + /* + * Consume the next byte from the notifier pipe if the pipe was + * readable. Note that there may be multiple bytes pending, but to + * avoid a race condition we only read one at a time. + */ + + do { + i = read(receivePipe, buf, 1); + if (i <= 0) { + break; + } else if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) { + /* + * Someone closed the write end of the pipe or sent us a Quit + * message [Bug: 4139] and then closed the write end of the + * pipe so we need to shut down the notifier thread. + */ + + break; + } + } while (1); + if ((i == 0) || (buf[0] == 'q')) { + break; + } + } + + /* + * Clean up the read end of the pipe and signal any threads waiting on + * termination of the notifier thread. + */ + + close(receivePipe); + pthread_mutex_lock(¬ifierMutex); + triggerPipe = -1; + pthread_cond_broadcast(¬ifierCV); + pthread_mutex_unlock(¬ifierMutex); + + TclpThreadExit(0); +} + +#endif /* TCL_THREADS */ + +#endif /* !HAVE_COREFOUNDATION */ + +#endif /* !NOTIFIER_EPOLL && !NOTIFIER_KQUEUE */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/unix/tclUnixNotfy.c b/unix/tclUnixNotfy.c index e37962d..248504b 100644 --- a/unix/tclUnixNotfy.c +++ b/unix/tclUnixNotfy.c @@ -1,262 +1,33 @@ /* * tclUnixNotfy.c -- * - * This file contains the implementation of the select()-based - * Unix-specific notifier, which is the lowest-level part of the Tcl - * event loop. This file works together with generic/tclNotify.c. + * This file contains subroutines shared by all notifier backend + * implementations on *nix platforms. * * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright (c) 2016 Lucio AndrĂ©s Illanes Albornoz <l.illanes@gmx.de> * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ -#include "tclInt.h" -#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is - * in tclMacOSXNotify.c */ -#include <signal.h> - -/* - * This structure is used to keep track of the notifier info for a registered - * file. - */ - -typedef struct FileHandler { - int fd; - int mask; /* Mask of desired events: TCL_READABLE, - * etc. */ - int readyMask; /* Mask of events that have been seen since - * the last time file handlers were invoked - * for this file. */ - Tcl_FileProc *proc; /* Function to call, in the style of - * Tcl_CreateFileHandler. */ - ClientData clientData; /* Argument to pass to proc. */ - struct FileHandler *nextPtr;/* Next in list of all files we care about. */ -} FileHandler; - -/* - * The following structure is what is added to the Tcl event queue when file - * handlers are ready to fire. - */ - -typedef struct { - Tcl_Event header; /* Information that is standard for all - * events. */ - int fd; /* File descriptor that is ready. Used to find - * the FileHandler structure for the file - * (can't point directly to the FileHandler - * structure because it could go away while - * the event is queued). */ -} FileHandlerEvent; - -/* - * The following structure contains a set of select() masks to track readable, - * writable, and exception conditions. - */ - -typedef struct { - fd_set readable; - fd_set writable; - fd_set exception; -} SelectMasks; - -/* - * The following static structure contains the state information for the - * select based implementation of the Tcl notifier. One of these structures is - * created for each thread that is using the notifier. - */ - -typedef struct ThreadSpecificData { - FileHandler *firstFileHandlerPtr; - /* Pointer to head of file handler list. */ - SelectMasks checkMasks; /* This structure is used to build up the - * masks to be used in the next call to - * select. Bits are set in response to calls - * to Tcl_CreateFileHandler. */ - SelectMasks readyMasks; /* This array reflects the readable/writable - * conditions that were found to exist by the - * last call to select. */ - int numFdBits; /* Number of valid bits in checkMasks (one - * more than highest fd for which - * Tcl_WatchFile has been called). */ -#ifdef TCL_THREADS - int onList; /* True if it is in this list */ - unsigned int pollState; /* pollState is used to implement a polling - * handshake between each thread and the - * notifier thread. Bits defined below. */ - struct ThreadSpecificData *nextPtr, *prevPtr; - /* All threads that are currently waiting on - * an event have their ThreadSpecificData - * structure on a doubly-linked listed formed - * from these pointers. You must hold the - * notifierMutex lock before accessing these - * fields. */ -#ifdef __CYGWIN__ - void *event; /* Any other thread alerts a notifier - * that an event is ready to be processed - * by sending this event. */ - void *hwnd; /* Messaging window. */ -#else /* !__CYGWIN__ */ - pthread_cond_t waitCV; /* Any other thread alerts a notifier that an - * event is ready to be processed by signaling - * this condition variable. */ -#endif /* __CYGWIN__ */ - int waitCVinitialized; /* Variable to flag initialization of the structure */ - int eventReady; /* True if an event is ready to be processed. - * Used as condition flag together with waitCV - * above. */ -#endif /* TCL_THREADS */ -} ThreadSpecificData; - -static Tcl_ThreadDataKey dataKey; - -#ifdef TCL_THREADS -/* - * The following static indicates the number of threads that have initialized - * notifiers. - * - * You must hold the notifierMutex lock before accessing this variable. - */ - -static int notifierCount = 0; - -/* - * The following variable points to the head of a doubly-linked list of - * ThreadSpecificData structures for all threads that are currently waiting on - * an event. - * - * You must hold the notifierMutex lock before accessing this list. - */ - -static ThreadSpecificData *waitingListPtr = NULL; - -/* - * The notifier thread spends all its time in select() waiting for a file - * descriptor associated with one of the threads on the waitingListPtr list to - * do something interesting. But if the contents of the waitingListPtr list - * ever changes, we need to wake up and restart the select() system call. You - * can wake up the notifier thread by writing a single byte to the file - * descriptor defined below. This file descriptor is the input-end of a pipe - * and the notifier thread is listening for data on the output-end of the same - * pipe. Hence writing to this file descriptor will cause the select() system - * call to return and wake up the notifier thread. - * - * You must hold the notifierMutex lock before writing to the pipe. - */ - -static int triggerPipe = -1; - -/* - * The notifierMutex locks access to all of the global notifier state. - */ - -static pthread_mutex_t notifierInitMutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t notifierMutex = PTHREAD_MUTEX_INITIALIZER; -/* - * The following static indicates if the notifier thread is running. - * - * You must hold the notifierInitMutex before accessing this variable. - */ - -static int notifierThreadRunning = 0; - -/* - * The notifier thread signals the notifierCV when it has finished - * initializing the triggerPipe and right before the notifier thread - * terminates. - */ - -static pthread_cond_t notifierCV = PTHREAD_COND_INITIALIZER; - -/* - * The pollState bits - * POLL_WANT is set by each thread before it waits on its condition - * variable. It is checked by the notifier before it does select. - * POLL_DONE is set by the notifier if it goes into select after seeing - * POLL_WANT. The idea is to ensure it tries a select with the - * same bits the initial thread had set. - */ - -#define POLL_WANT 0x1 -#define POLL_DONE 0x2 - -/* - * This is the thread ID of the notifier thread that does select. - */ - -static Tcl_ThreadId notifierThread; - -#endif /* TCL_THREADS */ +#include <poll.h> /* * Static routines defined in this file. */ +#ifdef NOTIFIER_SELECT #ifdef TCL_THREADS static void NotifierThreadProc(ClientData clientData); #if defined(HAVE_PTHREAD_ATFORK) -static int atForkInit = 0; static void AtForkChild(void); #endif /* HAVE_PTHREAD_ATFORK */ #endif /* TCL_THREADS */ +#endif /* NOTIFIER_SELECT */ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); -/* - * Import of Windows API when building threaded with Cygwin. - */ - -#if defined(TCL_THREADS) && defined(__CYGWIN__) -typedef struct { - void *hwnd; - unsigned int *message; - int wParam; - int lParam; - int time; - int x; - int y; -} MSG; - -typedef struct { - unsigned int style; - void *lpfnWndProc; - int cbClsExtra; - int cbWndExtra; - void *hInstance; - void *hIcon; - void *hCursor; - void *hbrBackground; - void *lpszMenuName; - const void *lpszClassName; -} WNDCLASS; - -extern void __stdcall CloseHandle(void *); -extern void *__stdcall CreateEventW(void *, unsigned char, unsigned char, - void *); -extern void * __stdcall CreateWindowExW(void *, const void *, const void *, - DWORD, int, int, int, int, void *, void *, void *, void *); -extern DWORD __stdcall DefWindowProcW(void *, int, void *, void *); -extern unsigned char __stdcall DestroyWindow(void *); -extern int __stdcall DispatchMessageW(const MSG *); -extern unsigned char __stdcall GetMessageW(MSG *, void *, int, int); -extern void __stdcall MsgWaitForMultipleObjects(DWORD, void *, - unsigned char, DWORD, DWORD); -extern unsigned char __stdcall PeekMessageW(MSG *, void *, int, int, int); -extern unsigned char __stdcall PostMessageW(void *, unsigned int, void *, - void *); -extern void __stdcall PostQuitMessage(int); -extern void *__stdcall RegisterClassW(const WNDCLASS *); -extern unsigned char __stdcall ResetEvent(void *); -extern unsigned char __stdcall TranslateMessage(const MSG *); - -/* - * Threaded-cygwin specific constants and functions in this file: - */ - -static const WCHAR className[] = L"TclNotifier"; -static DWORD __stdcall NotifierProc(void *hwnd, unsigned int message, - void *wParam, void *lParam); -#endif /* TCL_THREADS && __CYGWIN__ */ - +#ifdef NOTIFIER_SELECT #if TCL_THREADS /* *---------------------------------------------------------------------- @@ -300,168 +71,7 @@ StartNotifierThread(const char *proc) } } #endif /* TCL_THREADS */ - -/* - *---------------------------------------------------------------------- - * - * Tcl_InitNotifier -- - * - * Initializes the platform specific notifier state. - * - * Results: - * Returns a handle to the notifier state for this thread. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -ClientData -Tcl_InitNotifier(void) -{ - if (tclNotifierHooks.initNotifierProc) { - return tclNotifierHooks.initNotifierProc(); - } else { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - -#ifdef TCL_THREADS - tsdPtr->eventReady = 0; - - /* - * Initialize thread specific condition variable for this thread. - */ - if (tsdPtr->waitCVinitialized == 0) { -#ifdef __CYGWIN__ - WNDCLASS class; - - class.style = 0; - class.cbClsExtra = 0; - class.cbWndExtra = 0; - class.hInstance = TclWinGetTclInstance(); - class.hbrBackground = NULL; - class.lpszMenuName = NULL; - class.lpszClassName = className; - class.lpfnWndProc = NotifierProc; - class.hIcon = NULL; - class.hCursor = NULL; - - RegisterClassW(&class); - tsdPtr->hwnd = CreateWindowExW(NULL, class.lpszClassName, - class.lpszClassName, 0, 0, 0, 0, 0, NULL, NULL, - TclWinGetTclInstance(), NULL); - tsdPtr->event = CreateEventW(NULL, 1 /* manual */, - 0 /* !signaled */, NULL); -#else - pthread_cond_init(&tsdPtr->waitCV, NULL); -#endif /* __CYGWIN__ */ - tsdPtr->waitCVinitialized = 1; - } - - pthread_mutex_lock(¬ifierInitMutex); -#if defined(HAVE_PTHREAD_ATFORK) - /* - * Install pthread_atfork handlers to clean up the notifier in the - * child of a fork. - */ - - if (!atForkInit) { - int result = pthread_atfork(NULL, NULL, AtForkChild); - - if (result) { - Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed"); - } - atForkInit = 1; - } -#endif /* HAVE_PTHREAD_ATFORK */ - - notifierCount++; - - pthread_mutex_unlock(¬ifierInitMutex); - -#endif /* TCL_THREADS */ - return tsdPtr; - } -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_FinalizeNotifier -- - * - * This function is called to cleanup the notifier state before a thread - * is terminated. - * - * Results: - * None. - * - * Side effects: - * May terminate the background notifier thread if this is the last - * notifier instance. - * - *---------------------------------------------------------------------- - */ - -void -Tcl_FinalizeNotifier( - ClientData clientData) /* Not used. */ -{ - if (tclNotifierHooks.finalizeNotifierProc) { - tclNotifierHooks.finalizeNotifierProc(clientData); - return; - } else { -#ifdef TCL_THREADS - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - pthread_mutex_lock(¬ifierInitMutex); - notifierCount--; - - /* - * If this is the last thread to use the notifier, close the notifier - * pipe and wait for the background thread to terminate. - */ - - if (notifierCount == 0) { - - if (triggerPipe != -1) { - if (write(triggerPipe, "q", 1) != 1) { - Tcl_Panic("Tcl_FinalizeNotifier: %s", - "unable to write q to triggerPipe"); - } - close(triggerPipe); - pthread_mutex_lock(¬ifierMutex); - while(triggerPipe != -1) { - pthread_cond_wait(¬ifierCV, ¬ifierMutex); - } - pthread_mutex_unlock(¬ifierMutex); - if (notifierThreadRunning) { - int result = pthread_join((pthread_t) notifierThread, NULL); - - if (result) { - Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier " - "thread"); - } - notifierThreadRunning = 0; - } - } - } - - /* - * Clean up any synchronization objects in the thread local storage. - */ - -#ifdef __CYGWIN__ - DestroyWindow(tsdPtr->hwnd); - CloseHandle(tsdPtr->event); -#else /* __CYGWIN__ */ - pthread_cond_destroy(&tsdPtr->waitCV); -#endif /* __CYGWIN__ */ - tsdPtr->waitCVinitialized = 0; - - pthread_mutex_unlock(¬ifierInitMutex); -#endif /* TCL_THREADS */ - } -} +#endif /* NOTIFIER_SELECT */ /* *---------------------------------------------------------------------- @@ -477,7 +87,13 @@ Tcl_FinalizeNotifier( * None. * * Side effects: - * Signals the notifier condition variable for the specified notifier. + * select(2) notifier: + * signals the notifier condition variable for the specified + * notifier. + * epoll(7) notifier: + * write(2)s to the eventfd(2) of the specified thread. + * kqueue(2) notifier: + * write(2)s to the trigger pipe(2) of the specified thread. * *---------------------------------------------------------------------- */ @@ -486,10 +102,16 @@ void Tcl_AlertNotifier( ClientData clientData) { +#ifdef NOTIFIER_EPOLL +#ifdef HAVE_EVENTFD + uint64_t eventFdVal; +#endif /* HAVE_EVENTFD */ +#endif /* NOTIFIER_EPOLL */ if (tclNotifierHooks.alertNotifierProc) { tclNotifierHooks.alertNotifierProc(clientData); return; } else { +#ifdef NOTIFIER_SELECT #ifdef TCL_THREADS ThreadSpecificData *tsdPtr = clientData; @@ -503,6 +125,27 @@ Tcl_AlertNotifier( # endif /* __CYGWIN__ */ pthread_mutex_unlock(¬ifierMutex); #endif /* TCL_THREADS */ +#else + ThreadSpecificData *tsdPtr = clientData; +#ifdef NOTIFIER_EPOLL +#ifdef HAVE_EVENTFD + eventFdVal = 1; + if (write(tsdPtr->triggerEventFd, &eventFdVal, + sizeof(eventFdVal)) != sizeof(eventFdVal)) { + Tcl_Panic("Tcl_AlertNotifier: unable to write to %p->triggerEventFd", + (void *)tsdPtr); +#else + if (write(tsdPtr->triggerPipe[1], "", 1) != 1) { + Tcl_Panic("Tcl_AlertNotifier: unable to write to %p->triggerEventFd", + (void *)tsdPtr); +#endif +#else + if (write(tsdPtr->triggerPipe[1], "", 1) != 1) { + Tcl_Panic("Tcl_AlertNotifier: unable to write to %p->triggerPipe", + (void *)tsdPtr); +#endif /* NOTIFIER_EPOLL */ + } +#endif /* NOTIFIER_SELECT */ } } @@ -565,173 +208,11 @@ Tcl_ServiceModeHook( tclNotifierHooks.serviceModeHookProc(mode); return; } else if (mode == TCL_SERVICE_ALL) { +#ifdef NOTIFIER_SELECT #if TCL_THREADS StartNotifierThread("Tcl_ServiceModeHook"); #endif - } -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_CreateFileHandler -- - * - * This function registers a file handler with the select notifier. - * - * Results: - * None. - * - * Side effects: - * Creates a new file handler structure. - * - *---------------------------------------------------------------------- - */ - -void -Tcl_CreateFileHandler( - int fd, /* Handle of stream to watch. */ - int mask, /* OR'ed combination of TCL_READABLE, - * TCL_WRITABLE, and TCL_EXCEPTION: indicates - * conditions under which proc should be - * called. */ - Tcl_FileProc *proc, /* Function to call for each selected - * event. */ - ClientData clientData) /* Arbitrary data to pass to proc. */ -{ - if (tclNotifierHooks.createFileHandlerProc) { - tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData); - return; - } else { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - FileHandler *filePtr; - - for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; - filePtr = filePtr->nextPtr) { - if (filePtr->fd == fd) { - break; - } - } - if (filePtr == NULL) { - filePtr = ckalloc(sizeof(FileHandler)); - filePtr->fd = fd; - filePtr->readyMask = 0; - filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; - tsdPtr->firstFileHandlerPtr = filePtr; - } - filePtr->proc = proc; - filePtr->clientData = clientData; - filePtr->mask = mask; - - /* - * Update the check masks for this file. - */ - - if (mask & TCL_READABLE) { - FD_SET(fd, &tsdPtr->checkMasks.readable); - } else { - FD_CLR(fd, &tsdPtr->checkMasks.readable); - } - if (mask & TCL_WRITABLE) { - FD_SET(fd, &tsdPtr->checkMasks.writable); - } else { - FD_CLR(fd, &tsdPtr->checkMasks.writable); - } - if (mask & TCL_EXCEPTION) { - FD_SET(fd, &tsdPtr->checkMasks.exception); - } else { - FD_CLR(fd, &tsdPtr->checkMasks.exception); - } - if (tsdPtr->numFdBits <= fd) { - tsdPtr->numFdBits = fd+1; - } - } -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_DeleteFileHandler -- - * - * Cancel a previously-arranged callback arrangement for a file. - * - * Results: - * None. - * - * Side effects: - * If a callback was previously registered on file, remove it. - * - *---------------------------------------------------------------------- - */ - -void -Tcl_DeleteFileHandler( - int fd) /* Stream id for which to remove callback - * function. */ -{ - if (tclNotifierHooks.deleteFileHandlerProc) { - tclNotifierHooks.deleteFileHandlerProc(fd); - return; - } else { - FileHandler *filePtr, *prevPtr; - int i; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - /* - * Find the entry for the given file (and return if there isn't one). - */ - - for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; - prevPtr = filePtr, filePtr = filePtr->nextPtr) { - if (filePtr == NULL) { - return; - } - if (filePtr->fd == fd) { - break; - } - } - - /* - * Update the check masks for this file. - */ - - if (filePtr->mask & TCL_READABLE) { - FD_CLR(fd, &tsdPtr->checkMasks.readable); - } - if (filePtr->mask & TCL_WRITABLE) { - FD_CLR(fd, &tsdPtr->checkMasks.writable); - } - if (filePtr->mask & TCL_EXCEPTION) { - FD_CLR(fd, &tsdPtr->checkMasks.exception); - } - - /* - * Find current max fd. - */ - - if (fd+1 == tsdPtr->numFdBits) { - int numFdBits = 0; - - for (i = fd-1; i >= 0; i--) { - if (FD_ISSET(i, &tsdPtr->checkMasks.readable) - || FD_ISSET(i, &tsdPtr->checkMasks.writable) - || FD_ISSET(i, &tsdPtr->checkMasks.exception)) { - numFdBits = i+1; - break; - } - } - tsdPtr->numFdBits = numFdBits; - } - - /* - * Clean up information in the callback record. - */ - - if (prevPtr == NULL) { - tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; - } else { - prevPtr->nextPtr = filePtr->nextPtr; - } - ckfree(filePtr); +#endif /* NOTIFIER_SELECT */ } } @@ -809,538 +290,56 @@ FileHandlerEventProc( return 1; } -#if defined(TCL_THREADS) && defined(__CYGWIN__) - -static DWORD __stdcall -NotifierProc( - void *hwnd, - unsigned int message, - void *wParam, - void *lParam) -{ - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - if (message != 1024) { - return DefWindowProcW(hwnd, message, wParam, lParam); - } - - /* - * Process all of the runnable events. - */ - - tsdPtr->eventReady = 1; - Tcl_ServiceAll(); - return 0; -} -#endif /* TCL_THREADS && __CYGWIN__ */ - -/* - *---------------------------------------------------------------------- - * - * Tcl_WaitForEvent -- - * - * This function is called by Tcl_DoOneEvent to wait for new events on - * the message queue. If the block time is 0, then Tcl_WaitForEvent just - * polls without blocking. - * - * Results: - * Returns -1 if the select would block forever, otherwise returns 0. - * - * Side effects: - * Queues file events that are detected by the select. - * - *---------------------------------------------------------------------- - */ - -int -Tcl_WaitForEvent( - const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ -{ - if (tclNotifierHooks.waitForEventProc) { - return tclNotifierHooks.waitForEventProc(timePtr); - } else { - FileHandler *filePtr; - int mask; - Tcl_Time vTime; -#ifdef TCL_THREADS - int waitForFiles; -# ifdef __CYGWIN__ - MSG msg; -# endif /* __CYGWIN__ */ -#else - /* - * Impl. notes: timeout & timeoutPtr are used if, and only if threads - * are not enabled. They are the arguments for the regular select() - * used when the core is not thread-enabled. - */ - - struct timeval timeout, *timeoutPtr; - int numFound; -#endif /* TCL_THREADS */ - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - /* - * Set up the timeout structure. Note that if there are no events to - * check for, we return with a negative result rather than blocking - * forever. - */ - - if (timePtr != NULL) { - /* - * TIP #233 (Virtualized Time). Is virtual time in effect? And do - * we actually have something to scale? If yes to both then we - * call the handler to do this scaling. - */ - - if (timePtr->sec != 0 || timePtr->usec != 0) { - vTime = *timePtr; - tclScaleTimeProcPtr(&vTime, tclTimeClientData); - timePtr = &vTime; - } -#ifndef TCL_THREADS - timeout.tv_sec = timePtr->sec; - timeout.tv_usec = timePtr->usec; - timeoutPtr = &timeout; - } else if (tsdPtr->numFdBits == 0) { - /* - * If there are no threads, no timeout, and no fds registered, - * then there are no events possible and we must avoid deadlock. - * Note that this is not entirely correct because there might be a - * signal that could interrupt the select call, but we don't - * handle that case if we aren't using threads. - */ - - return -1; - } else { - timeoutPtr = NULL; -#endif /* !TCL_THREADS */ - } - -#ifdef TCL_THREADS - /* - * Start notifier thread and place this thread on the list of - * interested threads, signal the notifier thread, and wait for a - * response or a timeout. - */ - StartNotifierThread("Tcl_WaitForEvent"); - - pthread_mutex_lock(¬ifierMutex); - - if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0 -#if defined(__APPLE__) && defined(__LP64__) - /* - * On 64-bit Darwin, pthread_cond_timedwait() appears to have - * a bug that causes it to wait forever when passed an - * absolute time which has already been exceeded by the system - * time; as a workaround, when given a very brief timeout, - * just do a poll. [Bug 1457797] - */ - || timePtr->usec < 10 -#endif /* __APPLE__ && __LP64__ */ - )) { - /* - * Cannot emulate a polling select with a polling condition - * variable. Instead, pretend to wait for files and tell the - * notifier thread what we are doing. The notifier thread makes - * sure it goes through select with its select mask in the same - * state as ours currently is. We block until that happens. - */ - - waitForFiles = 1; - tsdPtr->pollState = POLL_WANT; - timePtr = NULL; - } else { - waitForFiles = (tsdPtr->numFdBits > 0); - tsdPtr->pollState = 0; - } - - if (waitForFiles) { - /* - * Add the ThreadSpecificData structure of this thread to the list - * of ThreadSpecificData structures of all threads that are - * waiting on file events. - */ - - tsdPtr->nextPtr = waitingListPtr; - if (waitingListPtr) { - waitingListPtr->prevPtr = tsdPtr; - } - tsdPtr->prevPtr = 0; - waitingListPtr = tsdPtr; - tsdPtr->onList = 1; - - if ((write(triggerPipe, "", 1) == -1) && (errno != EAGAIN)) { - Tcl_Panic("Tcl_WaitForEvent: %s", - "unable to write to triggerPipe"); - } - } - - FD_ZERO(&tsdPtr->readyMasks.readable); - FD_ZERO(&tsdPtr->readyMasks.writable); - FD_ZERO(&tsdPtr->readyMasks.exception); - - if (!tsdPtr->eventReady) { -#ifdef __CYGWIN__ - if (!PeekMessageW(&msg, NULL, 0, 0, 0)) { - DWORD timeout; - - if (timePtr) { - timeout = timePtr->sec * 1000 + timePtr->usec / 1000; - } else { - timeout = 0xFFFFFFFF; - } - pthread_mutex_unlock(¬ifierMutex); - MsgWaitForMultipleObjects(1, &tsdPtr->event, 0, timeout, 1279); - pthread_mutex_lock(¬ifierMutex); - } -#else - if (timePtr != NULL) { - Tcl_Time now; - struct timespec ptime; - - Tcl_GetTime(&now); - ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000; - ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); - - pthread_cond_timedwait(&tsdPtr->waitCV, ¬ifierMutex, &ptime); - } else { - pthread_cond_wait(&tsdPtr->waitCV, ¬ifierMutex); - } -#endif /* __CYGWIN__ */ - } - tsdPtr->eventReady = 0; - -#ifdef __CYGWIN__ - while (PeekMessageW(&msg, NULL, 0, 0, 0)) { - /* - * Retrieve and dispatch the message. - */ - - DWORD result = GetMessageW(&msg, NULL, 0, 0); - - if (result == 0) { - PostQuitMessage(msg.wParam); - /* What to do here? */ - } else if (result != (DWORD) -1) { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - ResetEvent(tsdPtr->event); -#endif /* __CYGWIN__ */ - - if (waitForFiles && tsdPtr->onList) { - /* - * Remove the ThreadSpecificData structure of this thread from the - * waiting list. Alert the notifier thread to recompute its select - * masks - skipping this caused a hang when trying to close a pipe - * which the notifier thread was still doing a select on. - */ - - if (tsdPtr->prevPtr) { - tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; - } else { - waitingListPtr = tsdPtr->nextPtr; - } - if (tsdPtr->nextPtr) { - tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; - } - tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; - tsdPtr->onList = 0; - if ((write(triggerPipe, "", 1) == -1) && (errno != EAGAIN)) { - Tcl_Panic("Tcl_WaitForEvent: %s", - "unable to write to triggerPipe"); - } - } - -#else - tsdPtr->readyMasks = tsdPtr->checkMasks; - numFound = select(tsdPtr->numFdBits, &tsdPtr->readyMasks.readable, - &tsdPtr->readyMasks.writable, &tsdPtr->readyMasks.exception, - timeoutPtr); - - /* - * Some systems don't clear the masks after an error, so we have to do - * it here. - */ - - if (numFound == -1) { - FD_ZERO(&tsdPtr->readyMasks.readable); - FD_ZERO(&tsdPtr->readyMasks.writable); - FD_ZERO(&tsdPtr->readyMasks.exception); - } -#endif /* TCL_THREADS */ - - /* - * Queue all detected file events before returning. - */ - - for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); - filePtr = filePtr->nextPtr) { - mask = 0; - if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.readable)) { - mask |= TCL_READABLE; - } - if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.writable)) { - mask |= TCL_WRITABLE; - } - if (FD_ISSET(filePtr->fd, &tsdPtr->readyMasks.exception)) { - mask |= TCL_EXCEPTION; - } - - if (!mask) { - continue; - } - - /* - * Don't bother to queue an event if the mask was previously - * non-zero since an event must still be on the queue. - */ - - if (filePtr->readyMask == 0) { - FileHandlerEvent *fileEvPtr = - ckalloc(sizeof(FileHandlerEvent)); - - fileEvPtr->header.proc = FileHandlerEventProc; - fileEvPtr->fd = filePtr->fd; - Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); - } - filePtr->readyMask = mask; - } -#ifdef TCL_THREADS - pthread_mutex_unlock(¬ifierMutex); -#endif /* TCL_THREADS */ - return 0; - } -} - +#ifdef NOTIFIER_SELECT #ifdef TCL_THREADS /* *---------------------------------------------------------------------- * - * NotifierThreadProc -- + * AlertSingleThread -- * - * 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 - * to notify other threads who are interested in this information by - * signalling a condition variable. Other threads can signal this - * notifier thread of a change in their interests by writing a single - * byte to a special pipe that the notifier thread is monitoring. + * Notify a single thread that is waiting on a file descriptor to become + * readable or writable or to have an exception condition. + * notifierMutex must be held. * * Result: - * None. Once started, this routine never exits. It dies with the overall - * process. + * None. * * Side effects: - * The trigger pipe used to signal the notifier thread is created when - * the notifier thread first starts. + * The condition variable associated with the thread is broadcasted. * *---------------------------------------------------------------------- */ static void -NotifierThreadProc( - ClientData clientData) /* Not used. */ +AlertSingleThread( + ThreadSpecificData *tsdPtr) { - ThreadSpecificData *tsdPtr; - fd_set readableMask; - fd_set writableMask; - fd_set exceptionMask; - int fds[2]; - int i, numFdBits = 0, receivePipe; - long found; - struct timeval poll = {0., 0.}, *timePtr; - char buf[2]; - - if (pipe(fds) != 0) { - Tcl_Panic("NotifierThreadProc: %s", "could not create trigger pipe"); - } - - receivePipe = fds[0]; - - if (TclUnixSetBlockingMode(receivePipe, TCL_MODE_NONBLOCKING) < 0) { - Tcl_Panic("NotifierThreadProc: %s", - "could not make receive pipe non blocking"); - } - if (TclUnixSetBlockingMode(fds[1], TCL_MODE_NONBLOCKING) < 0) { - Tcl_Panic("NotifierThreadProc: %s", - "could not make trigger pipe non blocking"); - } - if (fcntl(receivePipe, F_SETFD, FD_CLOEXEC) < 0) { - Tcl_Panic("NotifierThreadProc: %s", - "could not make receive pipe close-on-exec"); - } - if (fcntl(fds[1], F_SETFD, FD_CLOEXEC) < 0) { - Tcl_Panic("NotifierThreadProc: %s", - "could not make trigger pipe close-on-exec"); + 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) { + tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; + } else { + waitingListPtr = tsdPtr->nextPtr; + } + if (tsdPtr->nextPtr) { + tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; + } + tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; + tsdPtr->onList = 0; + tsdPtr->pollState = 0; } - - /* - * Install the write end of the pipe into the global variable. - */ - - pthread_mutex_lock(¬ifierMutex); - triggerPipe = fds[1]; - - /* - * Signal any threads that are waiting. - */ - - pthread_cond_broadcast(¬ifierCV); - pthread_mutex_unlock(¬ifierMutex); - - /* - * Look for file events and report them to interested threads. - */ - - while (1) { - FD_ZERO(&readableMask); - FD_ZERO(&writableMask); - FD_ZERO(&exceptionMask); - - /* - * Compute the logical OR of the select masks from all the waiting - * notifiers. - */ - - pthread_mutex_lock(¬ifierMutex); - timePtr = NULL; - for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { - for (i = tsdPtr->numFdBits-1; i >= 0; --i) { - if (FD_ISSET(i, &tsdPtr->checkMasks.readable)) { - FD_SET(i, &readableMask); - } - if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) { - FD_SET(i, &writableMask); - } - if (FD_ISSET(i, &tsdPtr->checkMasks.exception)) { - FD_SET(i, &exceptionMask); - } - } - if (tsdPtr->numFdBits > numFdBits) { - numFdBits = tsdPtr->numFdBits; - } - if (tsdPtr->pollState & POLL_WANT) { - /* - * Here we make sure we go through select() with the same mask - * bits that were present when the thread tried to poll. - */ - - tsdPtr->pollState |= POLL_DONE; - timePtr = &poll; - } - } - pthread_mutex_unlock(¬ifierMutex); - - /* - * Set up the select mask to include the receive pipe. - */ - - if (receivePipe >= numFdBits) { - numFdBits = receivePipe + 1; - } - FD_SET(receivePipe, &readableMask); - - if (select(numFdBits, &readableMask, &writableMask, &exceptionMask, - timePtr) == -1) { - /* - * Try again immediately on an error. - */ - - continue; - } - - /* - * Alert any threads that are waiting on a ready file descriptor. - */ - - pthread_mutex_lock(¬ifierMutex); - for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { - found = 0; - - for (i = tsdPtr->numFdBits-1; i >= 0; --i) { - if (FD_ISSET(i, &tsdPtr->checkMasks.readable) - && FD_ISSET(i, &readableMask)) { - FD_SET(i, &tsdPtr->readyMasks.readable); - found = 1; - } - if (FD_ISSET(i, &tsdPtr->checkMasks.writable) - && FD_ISSET(i, &writableMask)) { - FD_SET(i, &tsdPtr->readyMasks.writable); - found = 1; - } - if (FD_ISSET(i, &tsdPtr->checkMasks.exception) - && FD_ISSET(i, &exceptionMask)) { - FD_SET(i, &tsdPtr->readyMasks.exception); - found = 1; - } - } - - if (found || (tsdPtr->pollState & POLL_DONE)) { - tsdPtr->eventReady = 1; - if (tsdPtr->onList) { - /* - * Remove the ThreadSpecificData structure of this thread - * from the waiting list. This prevents us from - * continuously spining on select until the other threads - * runs and services the file event. - */ - - if (tsdPtr->prevPtr) { - tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; - } else { - waitingListPtr = tsdPtr->nextPtr; - } - if (tsdPtr->nextPtr) { - tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; - } - tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; - tsdPtr->onList = 0; - tsdPtr->pollState = 0; - } #ifdef __CYGWIN__ - PostMessageW(tsdPtr->hwnd, 1024, 0, 0); + PostMessageW(tsdPtr->hwnd, 1024, 0, 0); #else /* __CYGWIN__ */ - pthread_cond_broadcast(&tsdPtr->waitCV); + pthread_cond_broadcast(&tsdPtr->waitCV); #endif /* __CYGWIN__ */ - } - } - pthread_mutex_unlock(¬ifierMutex); - - /* - * Consume the next byte from the notifier pipe if the pipe was - * readable. Note that there may be multiple bytes pending, but to - * avoid a race condition we only read one at a time. - */ - - if (FD_ISSET(receivePipe, &readableMask)) { - i = read(receivePipe, buf, 1); - - if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) { - /* - * Someone closed the write end of the pipe or sent us a Quit - * message [Bug: 4139] and then closed the write end of the - * pipe so we need to shut down the notifier thread. - */ - - break; - } - } - } - - /* - * Clean up the read end of the pipe and signal any threads waiting on - * termination of the notifier thread. - */ - - close(receivePipe); - pthread_mutex_lock(¬ifierMutex); - triggerPipe = -1; - pthread_cond_broadcast(¬ifierCV); - pthread_mutex_unlock(¬ifierMutex); - - TclpThreadExit(0); } #if defined(HAVE_PTHREAD_ATFORK) @@ -1397,6 +396,8 @@ AtForkChild(void) * The tsdPtr from before the fork is copied as well. But since * we are paranoic, we don't trust its condvar and reset it. */ + pthread_cond_destroy(&tsdPtr->waitCV); + pthread_cond_init(&tsdPtr->waitCV, NULL); #ifdef __CYGWIN__ DestroyWindow(tsdPtr->hwnd); tsdPtr->hwnd = CreateWindowExW(NULL, className, @@ -1407,6 +408,7 @@ AtForkChild(void) pthread_cond_destroy(&tsdPtr->waitCV); pthread_cond_init(&tsdPtr->waitCV, NULL); #endif + /* * In case, we had multiple threads running before the fork, * make sure, we don't try to reach out to their thread local data. @@ -1426,8 +428,157 @@ AtForkChild(void) #endif /* TCL_THREADS */ +#endif /* NOTIFIER_SELECT */ +#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is + * in tclMacOSXNotify.c */ +/* + *---------------------------------------------------------------------- + * + * TclUnixWaitForFile -- + * + * This function waits synchronously for a file to become readable or + * writable, with an optional timeout. + * + * Results: + * The return value is an OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, and TCL_EXCEPTION, indicating the conditions that are + * present on file at the time of the return. This function will not + * return until either "timeout" milliseconds have elapsed or at least + * one of the conditions given by mask has occurred for file (a return + * value of 0 means that a timeout occurred). No normal events will be + * serviced during the execution of this function. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ + +int +TclUnixWaitForFile( + int fd, /* Handle for file on which to wait. */ + int mask, /* What to wait for: OR'ed combination of + * TCL_READABLE, TCL_WRITABLE, and + * TCL_EXCEPTION. */ + int timeout) /* Maximum amount of time to wait for one of + * the conditions in mask to occur, in + * milliseconds. A value of 0 means don't wait + * at all, and a value of -1 means wait + * forever. */ +{ + Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */ + struct timeval blockTime, *timeoutPtr; + struct pollfd pollFds[1]; + int numFound, result = 0, pollTimeout; + + /* + * If there is a non-zero finite timeout, compute the time when we give + * up. + */ + + if (timeout > 0) { + Tcl_GetTime(&now); + abortTime.sec = now.sec + timeout/1000; + abortTime.usec = now.usec + (timeout%1000)*1000; + if (abortTime.usec >= 1000000) { + abortTime.usec -= 1000000; + abortTime.sec += 1; + } + timeoutPtr = &blockTime; + } else if (timeout == 0) { + timeoutPtr = &blockTime; + blockTime.tv_sec = 0; + blockTime.tv_usec = 0; + } else { + timeoutPtr = NULL; + } + + /* + * Setup the pollfd structure for the fd. + */ + + pollFds[0].fd = fd; + pollFds[0].events = pollFds[0].revents = 0; + if (mask & TCL_READABLE) { + pollFds[0].events |= (POLLIN | POLLHUP); + } + if (mask & TCL_WRITABLE) { + pollFds[0].events |= POLLOUT; + } + if (mask & TCL_EXCEPTION) { + pollFds[0].events |= POLLERR; + } + + /* + * Loop in a mini-event loop of our own, waiting for either the file to + * become ready or a timeout to occur. + */ + + while (1) { + if (timeout > 0) { + blockTime.tv_sec = abortTime.sec - now.sec; + blockTime.tv_usec = abortTime.usec - now.usec; + if (blockTime.tv_usec < 0) { + blockTime.tv_sec -= 1; + blockTime.tv_usec += 1000000; + } + if (blockTime.tv_sec < 0) { + blockTime.tv_sec = 0; + blockTime.tv_usec = 0; + } + } + + /* + * Wait for the event or a timeout. + */ + + if (!timeoutPtr) { + pollTimeout = -1; + } else if (!timeoutPtr->tv_sec && !timeoutPtr->tv_usec) { + pollTimeout = 0; + } else { + pollTimeout = (int)timeoutPtr->tv_sec * 1000; + if (timeoutPtr->tv_usec) { + pollTimeout += ((int)timeoutPtr->tv_usec / 1000); + } + } + numFound = poll(pollFds, 1, pollTimeout); + if (numFound == 1) { + result = 0; + if (pollFds[0].events & (POLLIN | POLLHUP)) { + result |= TCL_READABLE; + } + if (pollFds[0].events & POLLOUT) { + result |= TCL_WRITABLE; + } + if (pollFds[0].events & POLLERR) { + result |= TCL_EXCEPTION; + } + if (result) { + break; + } + } + if (timeout == 0) { + break; + } + if (timeout < 0) { + continue; + } + + /* + * The select returned early, so we need to recompute the timeout. + */ + + Tcl_GetTime(&now); + if ((abortTime.sec < now.sec) + || (abortTime.sec==now.sec && abortTime.usec<=now.usec)) { + break; + } + } + return result; +} #endif /* !HAVE_COREFOUNDATION */ - + /* * Local Variables: * mode: c |