839 lines
24 KiB
C
Vendored
839 lines
24 KiB
C
Vendored
/*
|
||
* 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 © 1995-1997 Sun Microsystems, Inc.
|
||
* Copyright © 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 */
|
||
#if defined(NOTIFIER_KQUEUE) && TCL_THREADS
|
||
|
||
#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; /* File descriptor that this is describing a
|
||
* handler for. */
|
||
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. */
|
||
void *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
|
||
* TclpFinalizeNotifier. */
|
||
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. */
|
||
int asyncPending; /* True when signal triggered thread. */
|
||
} ThreadSpecificData;
|
||
|
||
static Tcl_ThreadDataKey dataKey;
|
||
|
||
/*
|
||
* Forward declarations of internal functions.
|
||
*/
|
||
|
||
static void PlatformEventsControl(FileHandler *filePtr,
|
||
ThreadSpecificData *tsdPtr, int op, int isNew);
|
||
static int PlatformEventsTranslate(struct kevent *eventPtr);
|
||
static int PlatformEventsWait(struct kevent *events,
|
||
size_t numEvents, struct timeval *timePtr);
|
||
|
||
/*
|
||
* Incorporate the base notifier implementation.
|
||
*/
|
||
|
||
#include "tclUnixNotfy.c"
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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.)
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
PlatformEventsControl(
|
||
FileHandler *filePtr,
|
||
ThreadSpecificData *tsdPtr,
|
||
int op,
|
||
int isNew)
|
||
{
|
||
int numChanges;
|
||
struct kevent changeList[2];
|
||
struct PlatformEventData *newPedPtr;
|
||
Tcl_StatBuf fdStat;
|
||
|
||
if (isNew) {
|
||
newPedPtr = (struct PlatformEventData *)
|
||
Tcl_Alloc(sizeof(struct PlatformEventData));
|
||
newPedPtr->filePtr = filePtr;
|
||
newPedPtr->tsdPtr = tsdPtr;
|
||
filePtr->pedPtr = newPedPtr;
|
||
}
|
||
|
||
/*
|
||
* N.B. As discussed in Tcl_WaitForEvent(), kqueue(2) does not reproduce
|
||
* the `always ready' {select,poll}(2) behaviour for regular files
|
||
* (S_IFREG) prior to FreeBSD 11.0-RELEASE. Therefore, filePtr is in these
|
||
* cases simply added or deleted from the list of FileHandlers associated
|
||
* with regular files belonging to tsdPtr.
|
||
*/
|
||
|
||
if (TclOSfstat(filePtr->fd, &fdStat) == -1) {
|
||
Tcl_Panic("fstat: %s", strerror(errno));
|
||
} else if ((fdStat.st_mode & S_IFMT) == S_IFREG
|
||
|| (fdStat.st_mode & S_IFMT) == S_IFDIR
|
||
|| (fdStat.st_mode & S_IFMT) == S_IFLNK) {
|
||
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 writability
|
||
* 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;
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpFinalizeNotifier --
|
||
*
|
||
* 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
|
||
TclpFinalizeNotifier(
|
||
TCL_UNUSED(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) {
|
||
Tcl_Free(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));
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpInitNotifier --
|
||
*
|
||
* Initializes the platform specific notifier state.
|
||
*
|
||
* 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:
|
||
* Returns a handle to the notifier state for this thread.
|
||
*
|
||
* 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 *
|
||
TclpInitNotifier(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 = (FileHandler *) Tcl_Alloc(sizeof(FileHandler));
|
||
filePtr->fd = tsdPtr->triggerPipe[0];
|
||
filePtr->mask = TCL_READABLE;
|
||
PlatformEventsControl(filePtr, tsdPtr, EV_ADD, 1);
|
||
if (!tsdPtr->readyEvents) {
|
||
tsdPtr->maxReadyEvents = 512;
|
||
tsdPtr->readyEvents = (struct kevent *) Tcl_Alloc(
|
||
tsdPtr->maxReadyEvents * sizeof(tsdPtr->readyEvents[0]));
|
||
}
|
||
LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr);
|
||
|
||
return tsdPtr;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static 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.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static 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 specifies a
|
||
* timeout of {0,0}, kevent(2) will poll. Otherwise, the timeout 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;
|
||
}
|
||
}
|
||
if (tsdPtr->asyncPending) {
|
||
tsdPtr->asyncPending = 0;
|
||
TclAsyncMarkFromNotifier();
|
||
}
|
||
return numFound;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpCreateFileHandler --
|
||
*
|
||
* 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
|
||
TclpCreateFileHandler(
|
||
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. */
|
||
void *clientData) /* Arbitrary data to pass to proc. */
|
||
{
|
||
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
|
||
FileHandler *filePtr = LookUpFileHandler(tsdPtr, fd, NULL);
|
||
int isNew = (filePtr == NULL);
|
||
|
||
if (isNew) {
|
||
filePtr = (FileHandler *) Tcl_Alloc(sizeof(FileHandler));
|
||
filePtr->fd = fd;
|
||
filePtr->readyMask = 0;
|
||
filePtr->nextPtr = tsdPtr->firstFileHandlerPtr;
|
||
tsdPtr->firstFileHandlerPtr = filePtr;
|
||
}
|
||
filePtr->proc = proc;
|
||
filePtr->clientData = clientData;
|
||
filePtr->mask = mask;
|
||
|
||
PlatformEventsControl(filePtr, tsdPtr, EV_ADD, isNew);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpDeleteFileHandler --
|
||
*
|
||
* 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
|
||
TclpDeleteFileHandler(
|
||
int fd) /* Stream id for which to remove callback
|
||
* function. */
|
||
{
|
||
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
|
||
FileHandler *filePtr, *prevPtr;
|
||
|
||
/*
|
||
* Find the entry for the given file (and return if there isn't one).
|
||
*/
|
||
|
||
filePtr = LookUpFileHandler(tsdPtr, fd, &prevPtr);
|
||
if (filePtr == NULL) {
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Update the check masks for this file.
|
||
*/
|
||
|
||
PlatformEventsControl(filePtr, tsdPtr, EV_DELETE, 0);
|
||
if (filePtr->pedPtr) {
|
||
Tcl_Free(filePtr->pedPtr);
|
||
}
|
||
|
||
/*
|
||
* Clean up information in the callback record.
|
||
*/
|
||
|
||
if (prevPtr == NULL) {
|
||
tsdPtr->firstFileHandlerPtr = filePtr->nextPtr;
|
||
} else {
|
||
prevPtr->nextPtr = filePtr->nextPtr;
|
||
}
|
||
Tcl_Free(filePtr);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpWaitForEvent --
|
||
*
|
||
* This function is called by Tcl_DoOneEvent to wait for new events on
|
||
* the message queue. If the block time is 0, then TclpWaitForEvent 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
|
||
TclpWaitForEvent(
|
||
const Tcl_Time *timePtr) /* Maximum block time, or NULL. */
|
||
{
|
||
FileHandler *filePtr;
|
||
int mask;
|
||
Tcl_Time vTime;
|
||
struct timeval timeout, *timeoutPtr;
|
||
/* 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. */
|
||
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;
|
||
TclScaleTime(&vTime);
|
||
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 = (FileHandlerEvent *)
|
||
Tcl_Alloc(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 PlatformEventsWait()
|
||
* 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]) {
|
||
i = read(tsdPtr->triggerPipe[0], buf, 1);
|
||
if ((i == -1) && (errno != EAGAIN)) {
|
||
Tcl_Panic("Tcl_WaitForEvent: read from %p->triggerPipe: %s",
|
||
tsdPtr, strerror(errno));
|
||
}
|
||
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 = (FileHandlerEvent *)
|
||
Tcl_Alloc(sizeof(FileHandlerEvent));
|
||
|
||
fileEvPtr->header.proc = FileHandlerEventProc;
|
||
fileEvPtr->fd = filePtr->fd;
|
||
Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
|
||
}
|
||
filePtr->readyMask |= mask;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclAsyncNotifier --
|
||
*
|
||
* This procedure sets the async mark of an async handler to a
|
||
* given value, if it is called from the target thread.
|
||
*
|
||
* Result:
|
||
* True, when the handler will be marked, false otherwise.
|
||
*
|
||
* Side effects:
|
||
* The signal may be resent to the target thread.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclAsyncNotifier(
|
||
int sigNumber, /* Signal number. */
|
||
Tcl_ThreadId threadId, /* Target thread. */
|
||
void *clientData, /* Notifier data. */
|
||
int *flagPtr, /* Flag to mark. */
|
||
int value) /* Value of mark. */
|
||
{
|
||
#if TCL_THREADS
|
||
/*
|
||
* WARNING:
|
||
* This code most likely runs in a signal handler. Thus,
|
||
* only few async-signal-safe system calls are allowed,
|
||
* e.g. pthread_self(), sem_post(), write().
|
||
*/
|
||
|
||
if (pthread_equal(pthread_self(), (pthread_t) threadId)) {
|
||
ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
|
||
|
||
*flagPtr = value;
|
||
if (tsdPtr != NULL && !tsdPtr->asyncPending) {
|
||
tsdPtr->asyncPending = 1;
|
||
TclpAlertNotifier(tsdPtr);
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Re-send the signal to the proper target thread.
|
||
*/
|
||
|
||
pthread_kill((pthread_t) threadId, sigNumber);
|
||
#else
|
||
(void)sigNumber;
|
||
(void)threadId;
|
||
(void)clientData;
|
||
(void)flagPtr;
|
||
(void)value;
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
#endif /* NOTIFIER_KQUEUE && TCL_THREADS */
|
||
#else
|
||
TCL_MAC_EMPTY_FILE(unix_tclKqueueNotfy_c)
|
||
#endif /* !HAVE_COREFOUNDATION */
|
||
|
||
/*
|
||
* Local Variables:
|
||
* mode: c
|
||
* c-basic-offset: 4
|
||
* fill-column: 78
|
||
* End:
|
||
*/
|