diff options
Diffstat (limited to 'contrib/tcl/unix/tclUnixNotfy.c')
-rw-r--r-- | contrib/tcl/unix/tclUnixNotfy.c | 553 |
1 files changed, 374 insertions, 179 deletions
diff --git a/contrib/tcl/unix/tclUnixNotfy.c b/contrib/tcl/unix/tclUnixNotfy.c index 7dce634..74c0ffc 100644 --- a/contrib/tcl/unix/tclUnixNotfy.c +++ b/contrib/tcl/unix/tclUnixNotfy.c @@ -1,16 +1,17 @@ -/* +/* * tclUnixNotify.c -- * - * This file contains Unix-specific procedures for the notifier, - * which is the lowest-level part of the Tcl event loop. This file - * works together with ../generic/tclNotify.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. * - * Copyright (c) 1995 Sun Microsystems, Inc. + * 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. * - * SCCS: @(#) tclUnixNotfy.c 1.31 96/07/23 16:17:29 + * SCCS: @(#) tclUnixNotfy.c 1.41 97/06/02 16:45:24 */ #include "tclInt.h" @@ -18,176 +19,393 @@ #include <signal.h> /* - * The information below is used to provide read, write, and - * exception masks to select during calls to Tcl_DoOneEvent. + * This structure is used to keep track of the notifier info for a + * 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; /* Procedure 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. */ -static fd_mask checkMasks[3*MASK_SIZE]; +typedef struct FileHandlerEvent { + 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. + */ + +static struct { + FileHandler *firstFileHandlerPtr; + /* Pointer to head of file handler list. */ + fd_mask checkMasks[3*MASK_SIZE]; /* This array 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_WatchFile. */ -static fd_mask readyMasks[3*MASK_SIZE]; + * Tcl_CreateFileHandler. */ + fd_mask readyMasks[3*MASK_SIZE]; /* This array reflects the readable/writable * conditions that were found to exist by the * last call to select. */ -static int numFdBits; /* Number of valid bits in checkMasks + int numFdBits; /* Number of valid bits in checkMasks * (one more than highest fd for which * Tcl_WatchFile has been called). */ +} notifier; + +/* + * The following static indicates whether this module has been initialized. + */ + +static int initialized = 0; + +/* + * Static routines defined in this file. + */ + +static void InitNotifier _ANSI_ARGS_((void)); +static void NotifierExitHandler _ANSI_ARGS_(( + ClientData clientData)); +static int FileHandlerEventProc _ANSI_ARGS_((Tcl_Event *evPtr, + int flags)); + +/* + *---------------------------------------------------------------------- + * + * InitNotifier -- + * + * Initializes the notifier state. + * + * Results: + * None. + * + * Side effects: + * Creates a new exit handler. + * + *---------------------------------------------------------------------- + */ + +static void +InitNotifier() +{ + initialized = 1; + memset(¬ifier, 0, sizeof(notifier)); + Tcl_CreateExitHandler(NotifierExitHandler, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * NotifierExitHandler -- + * + * This function is called to cleanup the notifier state before + * Tcl is unloaded. + * + * Results: + * None. + * + * Side effects: + * Destroys the notifier window. + * + *---------------------------------------------------------------------- + */ +static void +NotifierExitHandler(clientData) + ClientData clientData; /* Not used. */ +{ + initialized = 0; +} + /* - * Static routines in this file: + *---------------------------------------------------------------------- + * + * Tcl_SetTimer -- + * + * This procedure sets the current notifier timer value. This + * interface is not implemented in this notifier because we are + * always running inside of Tcl_DoOneEvent. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- */ -static int MaskEmpty _ANSI_ARGS_((long *maskPtr)); +void +Tcl_SetTimer(timePtr) + Tcl_Time *timePtr; /* Timeout value, may be NULL. */ +{ + /* + * The interval timer doesn't do anything in this implementation, + * because the only event loop is via Tcl_DoOneEvent, which passes + * timeout values to Tcl_WaitForEvent. + */ +} /* *---------------------------------------------------------------------- * - * Tcl_WatchFile -- + * Tcl_CreateFileHandler -- * - * Arrange for Tcl_DoOneEvent to include this file in the masks - * for the next call to select. This procedure is invoked by - * event sources, which are in turn invoked by Tcl_DoOneEvent - * before it invokes select. + * This procedure registers a file handler with the Xt notifier. * * Results: * None. * * Side effects: - * - * The notifier will generate a file event when the I/O channel - * given by fd next becomes ready in the way indicated by mask. - * If fd is already registered then the old mask will be replaced - * with the new one. Once the event is sent, the notifier will - * not send any more events about the fd until the next call to - * Tcl_NotifyFile. + * Creates a new file handler structure and registers one or more + * input procedures with Xt. * *---------------------------------------------------------------------- */ void -Tcl_WatchFile(file, mask) - Tcl_File file; /* Generic file handle for a stream. */ +Tcl_CreateFileHandler(fd, mask, proc, clientData) + int fd; /* Handle of stream to watch. */ int mask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: - * indicates conditions to wait for - * in select. */ + * indicates conditions under which + * proc should be called. */ + Tcl_FileProc *proc; /* Procedure to call for each + * selected event. */ + ClientData clientData; /* Arbitrary data to pass to proc. */ { - int fd, type, index; - fd_mask bit; - - fd = (int) Tcl_GetFileInfo(file, &type); - - if (type != TCL_UNIX_FD) { - panic("Tcl_WatchFile: unexpected file type"); + FileHandler *filePtr; + int index, bit; + + if (!initialized) { + InitNotifier(); } - if (fd >= FD_SETSIZE) { - panic("Tcl_WatchFile can't handle file id %d", fd); + for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; + filePtr = filePtr->nextPtr) { + if (filePtr->fd == fd) { + break; + } + } + if (filePtr == NULL) { + filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); + filePtr->fd = fd; + filePtr->readyMask = 0; + filePtr->nextPtr = notifier.firstFileHandlerPtr; + notifier.firstFileHandlerPtr = filePtr; } + filePtr->proc = proc; + filePtr->clientData = clientData; + filePtr->mask = mask; + + /* + * Update the check masks for this file. + */ index = fd/(NBBY*sizeof(fd_mask)); bit = 1 << (fd%(NBBY*sizeof(fd_mask))); if (mask & TCL_READABLE) { - checkMasks[index] |= bit; - } + notifier.checkMasks[index] |= bit; + } else { + notifier.checkMasks[index] &= ~bit; + } if (mask & TCL_WRITABLE) { - (checkMasks+MASK_SIZE)[index] |= bit; + (notifier.checkMasks+MASK_SIZE)[index] |= bit; + } else { + (notifier.checkMasks+MASK_SIZE)[index] &= ~bit; } if (mask & TCL_EXCEPTION) { - (checkMasks+2*(MASK_SIZE))[index] |= bit; + (notifier.checkMasks+2*(MASK_SIZE))[index] |= bit; + } else { + (notifier.checkMasks+2*(MASK_SIZE))[index] &= ~bit; } - if (numFdBits <= fd) { - numFdBits = fd+1; + if (notifier.numFdBits <= fd) { + notifier.numFdBits = fd+1; } } /* *---------------------------------------------------------------------- * - * Tcl_FileReady -- + * Tcl_DeleteFileHandler -- * - * Indicates what conditions (readable, writable, etc.) were - * present on a file the last time the notifier invoked select. - * This procedure is typically invoked by event sources to see - * if they should queue events. + * Cancel a previously-arranged callback arrangement for + * a file. * * Results: - * The return value is 0 if none of the conditions specified by mask - * was true for fd the last time the system checked. If any of the - * conditions were true, then the return value is a mask of those - * that were true. + * None. * * Side effects: - * None. + * If a callback was previously registered on file, remove it. * *---------------------------------------------------------------------- */ -int -Tcl_FileReady(file, mask) - Tcl_File file; /* Generic file handle for a stream. */ - int mask; /* OR'ed combination of TCL_READABLE, - * TCL_WRITABLE, and TCL_EXCEPTION: - * indicates conditions caller cares about. */ +void +Tcl_DeleteFileHandler(fd) + int fd; /* Stream id for which to remove callback procedure. */ { - int index, result, type, fd; - fd_mask bit; + FileHandler *filePtr, *prevPtr; + int index, bit, mask, i; + + if (!initialized) { + InitNotifier(); + } - fd = (int) Tcl_GetFileInfo(file, &type); - if (type != TCL_UNIX_FD) { - panic("Tcl_FileReady: unexpected file type"); + /* + * Find the entry for the given file (and return if there + * isn't one). + */ + + for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ; + prevPtr = filePtr, filePtr = filePtr->nextPtr) { + if (filePtr == NULL) { + return; + } + if (filePtr->fd == fd) { + break; + } } + /* + * Update the check masks for this file. + */ + index = fd/(NBBY*sizeof(fd_mask)); bit = 1 << (fd%(NBBY*sizeof(fd_mask))); - result = 0; - if ((mask & TCL_READABLE) && (readyMasks[index] & bit)) { - result |= TCL_READABLE; + + if (filePtr->mask & TCL_READABLE) { + notifier.checkMasks[index] &= ~bit; } - if ((mask & TCL_WRITABLE) && ((readyMasks+MASK_SIZE)[index] & bit)) { - result |= TCL_WRITABLE; + if (filePtr->mask & TCL_WRITABLE) { + (notifier.checkMasks+MASK_SIZE)[index] &= ~bit; + } + if (filePtr->mask & TCL_EXCEPTION) { + (notifier.checkMasks+2*(MASK_SIZE))[index] &= ~bit; + } + + /* + * Find current max fd. + */ + + if (fd+1 == notifier.numFdBits) { + for (notifier.numFdBits = 0; index >= 0; index--) { + mask = notifier.checkMasks[index] + | (notifier.checkMasks+MASK_SIZE)[index] + | (notifier.checkMasks+2*(MASK_SIZE))[index]; + if (mask) { + for (i = (NBBY*sizeof(fd_mask)); i > 0; i--) { + if (mask & (1 << (i-1))) { + break; + } + } + notifier.numFdBits = index * (NBBY*sizeof(fd_mask)) + i; + break; + } + } } - if ((mask & TCL_EXCEPTION) && ((readyMasks+(2*MASK_SIZE))[index] & bit)) { - result |= TCL_EXCEPTION; + + /* + * Clean up information in the callback record. + */ + + if (prevPtr == NULL) { + notifier.firstFileHandlerPtr = filePtr->nextPtr; + } else { + prevPtr->nextPtr = filePtr->nextPtr; } - return result; + ckfree((char *) filePtr); } /* *---------------------------------------------------------------------- * - * MaskEmpty -- + * FileHandlerEventProc -- * - * Returns nonzero if mask is empty (has no bits set). + * This procedure is called by Tcl_ServiceEvent when a file event + * reaches the front of the event queue. This procedure is + * responsible for actually handling the event by invoking the + * callback for the file handler. * * Results: - * Nonzero if the mask is empty, zero otherwise. + * Returns 1 if the event was handled, meaning it should be removed + * from the queue. Returns 0 if the event was not handled, meaning + * it should stay on the queue. The only time the event isn't + * handled is if the TCL_FILE_EVENTS flag bit isn't set. * * Side effects: - * None + * Whatever the file handler's callback procedure does. * *---------------------------------------------------------------------- */ static int -MaskEmpty(maskPtr) - long *maskPtr; +FileHandlerEventProc(evPtr, flags) + Tcl_Event *evPtr; /* Event to service. */ + int flags; /* Flags that indicate what events to + * handle, such as TCL_FILE_EVENTS. */ { - long *runPtr, *tailPtr; - int found, sz; - - sz = 3 * ((MASK_SIZE) / sizeof(long)) * sizeof(fd_mask); - for (runPtr = maskPtr, tailPtr = maskPtr + sz, found = 0; - runPtr < tailPtr; - runPtr++) { - if (*runPtr != 0) { - found = 1; - break; - } + FileHandler *filePtr; + FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; + int mask; + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Search through the file handlers to find the one whose handle matches + * the event. We do this rather than keeping a pointer to the file + * handler directly in the event, so that the handler can be deleted + * while the event is queued without leaving a dangling pointer. + */ + + for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; + filePtr = filePtr->nextPtr) { + if (filePtr->fd != fileEvPtr->fd) { + continue; + } + + /* + * The code is tricky for two reasons: + * 1. The file handler's desired events could have changed + * since the time when the event was queued, so AND the + * ready mask with the desired mask. + * 2. The file could have been closed and re-opened since + * the time when the event was queued. This is why the + * ready mask is stored in the file handler rather than + * the queued event: it will be zeroed when a new + * file handler is created for the newly opened file. + */ + + mask = filePtr->readyMask & filePtr->mask; + filePtr->readyMask = 0; + if (mask != 0) { + (*filePtr->proc)(filePtr->clientData, mask); + } + break; } - return !found; + return 1; } /* @@ -195,50 +413,55 @@ MaskEmpty(maskPtr) * * Tcl_WaitForEvent -- * - * This procedure does the lowest level wait for events in a - * platform-specific manner. It uses information provided by - * previous calls to Tcl_WatchFile, plus the timePtr argument, - * to determine what to wait for and how long to wait. + * 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: - * The return value is normally TCL_OK. However, if there are - * no events to wait for (e.g. no files and no timers) so that - * the procedure would block forever, then it returns TCL_ERROR. + * Returns -1 if the select would block forever, otherwise + * returns 0. * * Side effects: - * May put the process to sleep for a while, depending on timePtr. - * When this procedure returns, an event of interest to the application - * has probably, but not necessarily, occurred. + * Queues file events that are detected by the select. * *---------------------------------------------------------------------- */ int Tcl_WaitForEvent(timePtr) - Tcl_Time *timePtr; /* Specifies the maximum amount of time - * that this procedure should block before - * returning. The time is given as an - * interval, not an absolute wakeup time. - * NULL means block forever. */ + Tcl_Time *timePtr; /* Maximum block time, or NULL. */ { + FileHandler *filePtr; + FileHandlerEvent *fileEvPtr; struct timeval timeout, *timeoutPtr; - int numFound; + int bit, index, mask, numFound; - memcpy((VOID *) readyMasks, (VOID *) checkMasks, - 3*MASK_SIZE*sizeof(fd_mask)); - if (timePtr == NULL) { - if ((numFdBits == 0) || (MaskEmpty((long *) readyMasks))) { - return TCL_ERROR; - } - timeoutPtr = NULL; - } else { - timeoutPtr = &timeout; + if (!initialized) { + InitNotifier(); + } + + /* + * 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) { timeout.tv_sec = timePtr->sec; timeout.tv_usec = timePtr->usec; + timeoutPtr = &timeout; + } else if (notifier.numFdBits == 0) { + return -1; + } else { + timeoutPtr = NULL; } - numFound = select(numFdBits, (SELECT_MASK *) &readyMasks[0], - (SELECT_MASK *) &readyMasks[MASK_SIZE], - (SELECT_MASK *) &readyMasks[2*MASK_SIZE], timeoutPtr); + + memcpy((VOID *) notifier.readyMasks, (VOID *) notifier.checkMasks, + 3*MASK_SIZE*sizeof(fd_mask)); + numFound = select(notifier.numFdBits, + (SELECT_MASK *) ¬ifier.readyMasks[0], + (SELECT_MASK *) ¬ifier.readyMasks[MASK_SIZE], + (SELECT_MASK *) ¬ifier.readyMasks[2*MASK_SIZE], timeoutPtr); /* * Some systems don't clear the masks after an error, so @@ -246,77 +469,49 @@ Tcl_WaitForEvent(timePtr) */ if (numFound == -1) { - memset((VOID *) readyMasks, 0, 3*MASK_SIZE*sizeof(fd_mask)); + memset((VOID *) notifier.readyMasks, 0, 3*MASK_SIZE*sizeof(fd_mask)); } /* - * Reset the check masks in preparation for the next call to - * select. + * Queue all detected file events before returning. */ - numFdBits = 0; - memset((VOID *) checkMasks, 0, 3*MASK_SIZE*sizeof(fd_mask)); - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_Sleep -- - * - * Delay execution for the specified number of milliseconds. - * - * Results: - * None. - * - * Side effects: - * Time passes. - * - *---------------------------------------------------------------------- - */ + for (filePtr = notifier.firstFileHandlerPtr; + (filePtr != NULL) && (numFound > 0); + filePtr = filePtr->nextPtr) { + index = filePtr->fd / (NBBY*sizeof(fd_mask)); + bit = 1 << (filePtr->fd % (NBBY*sizeof(fd_mask))); + mask = 0; -void -Tcl_Sleep(ms) - int ms; /* Number of milliseconds to sleep. */ -{ - static struct timeval delay; - Tcl_Time before, after; - - /* - * The only trick here is that select appears to return early - * under some conditions, so we have to check to make sure that - * the right amount of time really has elapsed. If it's too - * early, go back to sleep again. - */ + if (notifier.readyMasks[index] & bit) { + mask |= TCL_READABLE; + } + if ((notifier.readyMasks+MASK_SIZE)[index] & bit) { + mask |= TCL_WRITABLE; + } + if ((notifier.readyMasks+2*(MASK_SIZE))[index] & bit) { + mask |= TCL_EXCEPTION; + } - TclpGetTime(&before); - after = before; - after.sec += ms/1000; - after.usec += (ms%1000)*1000; - if (after.usec > 1000000) { - after.usec -= 1000000; - after.sec += 1; - } - while (1) { - delay.tv_sec = after.sec - before.sec; - delay.tv_usec = after.usec - before.usec; - if (delay.tv_usec < 0) { - delay.tv_usec += 1000000; - delay.tv_sec -= 1; + if (!mask) { + continue; + } else { + numFound--; } /* - * Special note: must convert delay.tv_sec to int before comparing - * to zero, since delay.tv_usec is unsigned on some platforms. + * Don't bother to queue an event if the mask was previously + * non-zero since an event must still be on the queue. */ - if ((((int) delay.tv_sec) < 0) - || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) { - break; + if (filePtr->readyMask == 0) { + fileEvPtr = (FileHandlerEvent *) ckalloc( + sizeof(FileHandlerEvent)); + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); } - (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, - (SELECT_MASK *) 0, &delay); - TclpGetTime(&before); + filePtr->readyMask = mask; } + return 0; } - |