sl@0: /* sl@0: * tclUnixNotify.c -- sl@0: * sl@0: * This file contains the implementation of the select-based sl@0: * Unix-specific notifier, which is the lowest-level part of the sl@0: * Tcl event loop. This file works together with sl@0: * ../generic/tclNotify.c. sl@0: * sl@0: * Copyright (c) 1995-1997 Sun Microsystems, Inc. sl@0: * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclUnixNotfy.c,v 1.11.2.16 2006/08/22 17:45:02 andreas_kupries Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: #if defined(__SYMBIAN32__) && defined(__WINSCW__) sl@0: #include "tclSymbianGlobals.h" sl@0: #define dataKey getdataKey(8) sl@0: #endif sl@0: sl@0: #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier sl@0: * is in tclMacOSXNotify.c */ sl@0: #ifndef __SYMBIAN32__ // added to prevent link errors on armv5 sl@0: #include sl@0: #endif sl@0: sl@0: extern TclStubs tclStubs; sl@0: extern Tcl_NotifierProcs tclOriginalNotifier; sl@0: sl@0: /* sl@0: * This structure is used to keep track of the notifier info for a sl@0: * a registered file. sl@0: */ sl@0: sl@0: typedef struct FileHandler { sl@0: int fd; sl@0: int mask; /* Mask of desired events: TCL_READABLE, sl@0: * etc. */ sl@0: int readyMask; /* Mask of events that have been seen since the sl@0: * last time file handlers were invoked for sl@0: * this file. */ sl@0: Tcl_FileProc *proc; /* Procedure to call, in the style of sl@0: * Tcl_CreateFileHandler. */ sl@0: ClientData clientData; /* Argument to pass to proc. */ sl@0: struct FileHandler *nextPtr;/* Next in list of all files we care about. */ sl@0: } FileHandler; sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when sl@0: * file handlers are ready to fire. sl@0: */ sl@0: sl@0: typedef struct FileHandlerEvent { sl@0: Tcl_Event header; /* Information that is standard for sl@0: * all events. */ sl@0: int fd; /* File descriptor that is ready. Used sl@0: * to find the FileHandler structure for sl@0: * the file (can't point directly to the sl@0: * FileHandler structure because it could sl@0: * go away while the event is queued). */ sl@0: } FileHandlerEvent; sl@0: sl@0: /* sl@0: * sl@0: * The following structure contains a set of select() masks to track sl@0: * readable, writable, and exceptional conditions. sl@0: */ sl@0: sl@0: typedef struct SelectMasks { sl@0: fd_set readable; sl@0: fd_set writable; sl@0: fd_set exceptional; sl@0: } SelectMasks; sl@0: sl@0: /* sl@0: * The following static structure contains the state information for the sl@0: * select based implementation of the Tcl notifier. One of these structures sl@0: * is created for each thread that is using the notifier. sl@0: */ sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: FileHandler *firstFileHandlerPtr; sl@0: /* Pointer to head of file handler list. */ sl@0: sl@0: SelectMasks checkMasks; /* This structure is used to build up the masks sl@0: * to be used in the next call to select. sl@0: * Bits are set in response to calls to sl@0: * Tcl_CreateFileHandler. */ sl@0: SelectMasks readyMasks; /* This array reflects the readable/writable sl@0: * conditions that were found to exist by the sl@0: * last call to select. */ sl@0: int numFdBits; /* Number of valid bits in checkMasks sl@0: * (one more than highest fd for which sl@0: * Tcl_WatchFile has been called). */ sl@0: #ifdef TCL_THREADS sl@0: int onList; /* True if it is in this list */ sl@0: unsigned int pollState; /* pollState is used to implement a polling sl@0: * handshake between each thread and the sl@0: * notifier thread. Bits defined below. */ sl@0: struct ThreadSpecificData *nextPtr, *prevPtr; sl@0: /* All threads that are currently waiting on sl@0: * an event have their ThreadSpecificData sl@0: * structure on a doubly-linked listed formed sl@0: * from these pointers. You must hold the sl@0: * notifierMutex lock before accessing these sl@0: * fields. */ sl@0: Tcl_Condition waitCV; /* Any other thread alerts a notifier sl@0: * that an event is ready to be processed sl@0: * by signaling this condition variable. */ sl@0: int eventReady; /* True if an event is ready to be processed. sl@0: * Used as condition flag together with sl@0: * waitCV above. */ sl@0: #endif sl@0: } ThreadSpecificData; sl@0: sl@0: #if !defined(__SYMBIAN32__) || !defined(__WINSCW__) sl@0: static Tcl_ThreadDataKey dataKey; sl@0: #endif sl@0: sl@0: #ifdef TCL_THREADS sl@0: /* sl@0: * The following static indicates the number of threads that have sl@0: * initialized notifiers. sl@0: * sl@0: * You must hold the notifierMutex lock before accessing this variable. sl@0: */ sl@0: sl@0: static int notifierCount = 0; sl@0: sl@0: /* sl@0: * The following variable points to the head of a doubly-linked list of sl@0: * of ThreadSpecificData structures for all threads that are currently sl@0: * waiting on an event. sl@0: * sl@0: * You must hold the notifierMutex lock before accessing this list. sl@0: */ sl@0: sl@0: static ThreadSpecificData *waitingListPtr = NULL; sl@0: sl@0: /* sl@0: * The notifier thread spends all its time in select() waiting for a sl@0: * file descriptor associated with one of the threads on the waitingListPtr sl@0: * list to do something interesting. But if the contents of the sl@0: * waitingListPtr list ever changes, we need to wake up and restart sl@0: * the select() system call. You can wake up the notifier thread by sl@0: * writing a single byte to the file descriptor defined below. This sl@0: * file descriptor is the input-end of a pipe and the notifier thread is sl@0: * listening for data on the output-end of the same pipe. Hence writing sl@0: * to this file descriptor will cause the select() system call to return sl@0: * and wake up the notifier thread. sl@0: * sl@0: * You must hold the notifierMutex lock before accessing this list. sl@0: */ sl@0: sl@0: static int triggerPipe = -1; sl@0: sl@0: /* sl@0: * The notifierMutex locks access to all of the global notifier state. sl@0: */ sl@0: sl@0: TCL_DECLARE_MUTEX(notifierMutex) sl@0: sl@0: /* sl@0: * The notifier thread signals the notifierCV when it has finished sl@0: * initializing the triggerPipe and right before the notifier sl@0: * thread terminates. sl@0: */ sl@0: sl@0: static Tcl_Condition notifierCV; sl@0: sl@0: /* sl@0: * The pollState bits sl@0: * POLL_WANT is set by each thread before it waits on its condition sl@0: * variable. It is checked by the notifier before it does sl@0: * select. sl@0: * POLL_DONE is set by the notifier if it goes into select after sl@0: * seeing POLL_WANT. The idea is to ensure it tries a select sl@0: * with the same bits the initial thread had set. sl@0: */ sl@0: #define POLL_WANT 0x1 sl@0: #define POLL_DONE 0x2 sl@0: sl@0: /* sl@0: * This is the thread ID of the notifier thread that does select. sl@0: */ sl@0: static Tcl_ThreadId notifierThread; sl@0: sl@0: #endif sl@0: sl@0: /* sl@0: * Static routines defined in this file. sl@0: */ sl@0: sl@0: #ifdef TCL_THREADS sl@0: static void NotifierThreadProc _ANSI_ARGS_((ClientData clientData)); sl@0: #endif sl@0: static int FileHandlerEventProc _ANSI_ARGS_((Tcl_Event *evPtr, sl@0: int flags)); sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_InitNotifier -- sl@0: * sl@0: * Initializes the platform specific notifier state. sl@0: * sl@0: * Results: sl@0: * Returns a handle to the notifier state for this thread.. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C ClientData sl@0: Tcl_InitNotifier() sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: #ifdef TCL_THREADS sl@0: tsdPtr->eventReady = 0; sl@0: sl@0: /* sl@0: * Start the Notifier thread if necessary. sl@0: */ sl@0: sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: if (notifierCount == 0) { sl@0: if (TclpThreadCreate(¬ifierThread, NotifierThreadProc, NULL, sl@0: TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) { sl@0: panic("Tcl_InitNotifier: unable to start notifier thread"); sl@0: } sl@0: } sl@0: notifierCount++; sl@0: sl@0: /* sl@0: * Wait for the notifier pipe to be created. sl@0: */ sl@0: sl@0: while (triggerPipe < 0) { sl@0: Tcl_ConditionWait(¬ifierCV, ¬ifierMutex, NULL); sl@0: } sl@0: sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: #endif sl@0: return (ClientData) tsdPtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_FinalizeNotifier -- sl@0: * sl@0: * This function is called to cleanup the notifier state before sl@0: * a thread is terminated. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May terminate the background notifier thread if this is the sl@0: * last notifier instance. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_FinalizeNotifier(clientData) sl@0: ClientData clientData; /* Not used. */ sl@0: { sl@0: #ifdef TCL_THREADS sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: notifierCount--; sl@0: sl@0: /* sl@0: * If this is the last thread to use the notifier, close the notifier sl@0: * pipe and wait for the background thread to terminate. sl@0: */ sl@0: sl@0: if (notifierCount == 0) { sl@0: int result; sl@0: if (triggerPipe < 0) { sl@0: panic("Tcl_FinalizeNotifier: notifier pipe not initialized"); sl@0: } sl@0: sl@0: /* sl@0: * Send "q" message to the notifier thread so that it will sl@0: * terminate. The notifier will return from its call to select() sl@0: * and notice that a "q" message has arrived, it will then close sl@0: * its side of the pipe and terminate its thread. Note the we can sl@0: * not just close the pipe and check for EOF in the notifier sl@0: * thread because if a background child process was created with sl@0: * exec, select() would not register the EOF on the pipe until the sl@0: * child processes had terminated. [Bug: 4139] [Bug: 1222872] sl@0: */ sl@0: sl@0: write(triggerPipe, "q", 1); sl@0: close(triggerPipe); sl@0: while(triggerPipe >= 0) { sl@0: Tcl_ConditionWait(¬ifierCV, ¬ifierMutex, NULL); sl@0: } sl@0: result = Tcl_JoinThread(notifierThread, NULL); sl@0: if (result) { sl@0: Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier thread"); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Clean up any synchronization objects in the thread local storage. sl@0: */ sl@0: sl@0: Tcl_ConditionFinalize(&(tsdPtr->waitCV)); sl@0: sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: #endif sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_AlertNotifier -- sl@0: * sl@0: * Wake up the specified notifier from any thread. This routine sl@0: * is called by the platform independent notifier code whenever sl@0: * the Tcl_ThreadAlert routine is called. This routine is sl@0: * guaranteed not to be called on a given notifier after sl@0: * Tcl_FinalizeNotifier is called for that notifier. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Signals the notifier condition variable for the specified sl@0: * notifier. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_AlertNotifier(clientData) sl@0: ClientData clientData; sl@0: { sl@0: #ifdef TCL_THREADS sl@0: ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: tsdPtr->eventReady = 1; sl@0: Tcl_ConditionNotify(&tsdPtr->waitCV); sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: #endif sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_SetTimer -- sl@0: * sl@0: * This procedure sets the current notifier timer value. This sl@0: * interface is not implemented in this notifier because we are sl@0: * always running inside of Tcl_DoOneEvent. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_SetTimer(timePtr) sl@0: Tcl_Time *timePtr; /* Timeout value, may be NULL. */ sl@0: { sl@0: /* sl@0: * The interval timer doesn't do anything in this implementation, sl@0: * because the only event loop is via Tcl_DoOneEvent, which passes sl@0: * timeout values to Tcl_WaitForEvent. sl@0: */ sl@0: sl@0: if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { sl@0: tclStubs.tcl_SetTimer(timePtr); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_ServiceModeHook -- sl@0: * sl@0: * This function is invoked whenever the service mode changes. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_ServiceModeHook(mode) sl@0: int mode; /* Either TCL_SERVICE_ALL, or sl@0: * TCL_SERVICE_NONE. */ sl@0: { sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_CreateFileHandler -- sl@0: * sl@0: * This procedure registers a file handler with the select notifier. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new file handler structure. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_CreateFileHandler(fd, mask, proc, clientData) sl@0: int fd; /* Handle of stream to watch. */ sl@0: int mask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, and TCL_EXCEPTION: sl@0: * indicates conditions under which sl@0: * proc should be called. */ sl@0: Tcl_FileProc *proc; /* Procedure to call for each sl@0: * selected event. */ sl@0: ClientData clientData; /* Arbitrary data to pass to proc. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: FileHandler *filePtr; sl@0: sl@0: if (tclStubs.tcl_CreateFileHandler != tclOriginalNotifier.createFileHandlerProc) { sl@0: tclStubs.tcl_CreateFileHandler(fd, mask, proc, clientData); sl@0: return; sl@0: } sl@0: sl@0: for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; sl@0: filePtr = filePtr->nextPtr) { sl@0: if (filePtr->fd == fd) { sl@0: break; sl@0: } sl@0: } sl@0: if (filePtr == NULL) { sl@0: filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); sl@0: filePtr->fd = fd; sl@0: filePtr->readyMask = 0; sl@0: filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; sl@0: tsdPtr->firstFileHandlerPtr = filePtr; sl@0: } sl@0: filePtr->proc = proc; sl@0: filePtr->clientData = clientData; sl@0: filePtr->mask = mask; sl@0: sl@0: /* sl@0: * Update the check masks for this file. sl@0: */ sl@0: sl@0: if ( mask & TCL_READABLE ) { sl@0: FD_SET( fd, &(tsdPtr->checkMasks.readable) ); sl@0: } else { sl@0: FD_CLR( fd, &(tsdPtr->checkMasks.readable) ); sl@0: } sl@0: if ( mask & TCL_WRITABLE ) { sl@0: FD_SET( fd, &(tsdPtr->checkMasks.writable) ); sl@0: } else { sl@0: FD_CLR( fd, &(tsdPtr->checkMasks.writable) ); sl@0: } sl@0: if ( mask & TCL_EXCEPTION ) { sl@0: FD_SET( fd, &(tsdPtr->checkMasks.exceptional) ); sl@0: } else { sl@0: FD_CLR( fd, &(tsdPtr->checkMasks.exceptional) ); sl@0: } sl@0: if (tsdPtr->numFdBits <= fd) { sl@0: tsdPtr->numFdBits = fd+1; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_DeleteFileHandler -- sl@0: * sl@0: * Cancel a previously-arranged callback arrangement for sl@0: * a file. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * If a callback was previously registered on file, remove it. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_DeleteFileHandler(fd) sl@0: int fd; /* Stream id for which to remove callback procedure. */ sl@0: { sl@0: FileHandler *filePtr, *prevPtr; sl@0: int i; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (tclStubs.tcl_DeleteFileHandler != tclOriginalNotifier.deleteFileHandlerProc) { sl@0: tclStubs.tcl_DeleteFileHandler(fd); sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Find the entry for the given file (and return if there isn't one). sl@0: */ sl@0: sl@0: for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; sl@0: prevPtr = filePtr, filePtr = filePtr->nextPtr) { sl@0: if (filePtr == NULL) { sl@0: return; sl@0: } sl@0: if (filePtr->fd == fd) { sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Update the check masks for this file. sl@0: */ sl@0: sl@0: if (filePtr->mask & TCL_READABLE) { sl@0: FD_CLR( fd, &(tsdPtr->checkMasks.readable) ); sl@0: } sl@0: if (filePtr->mask & TCL_WRITABLE) { sl@0: FD_CLR( fd, &(tsdPtr->checkMasks.writable) ); sl@0: } sl@0: if (filePtr->mask & TCL_EXCEPTION) { sl@0: FD_CLR( fd, &(tsdPtr->checkMasks.exceptional) ); sl@0: } sl@0: sl@0: /* sl@0: * Find current max fd. sl@0: */ sl@0: sl@0: if (fd+1 == tsdPtr->numFdBits) { sl@0: tsdPtr->numFdBits = 0; sl@0: for (i = fd-1; i >= 0; i--) { sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.readable) ) sl@0: || FD_ISSET( i, &(tsdPtr->checkMasks.writable) ) sl@0: || FD_ISSET( i, &(tsdPtr->checkMasks.exceptional ) ) ) { sl@0: tsdPtr->numFdBits = i+1; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Clean up information in the callback record. sl@0: */ sl@0: sl@0: if (prevPtr == NULL) { sl@0: tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; sl@0: } else { sl@0: prevPtr->nextPtr = filePtr->nextPtr; sl@0: } sl@0: ckfree((char *) filePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * FileHandlerEventProc -- sl@0: * sl@0: * This procedure is called by Tcl_ServiceEvent when a file event sl@0: * reaches the front of the event queue. This procedure is sl@0: * responsible for actually handling the event by invoking the sl@0: * callback for the file handler. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed sl@0: * from the queue. Returns 0 if the event was not handled, meaning sl@0: * it should stay on the queue. The only time the event isn't sl@0: * handled is if the TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the file handler's callback procedure does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: FileHandlerEventProc(evPtr, flags) sl@0: Tcl_Event *evPtr; /* Event to service. */ sl@0: int flags; /* Flags that indicate what events to sl@0: * handle, such as TCL_FILE_EVENTS. */ sl@0: { sl@0: int mask; sl@0: FileHandler *filePtr; sl@0: FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Search through the file handlers to find the one whose handle matches sl@0: * the event. We do this rather than keeping a pointer to the file sl@0: * handler directly in the event, so that the handler can be deleted sl@0: * while the event is queued without leaving a dangling pointer. sl@0: */ sl@0: sl@0: tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; sl@0: filePtr = filePtr->nextPtr) { sl@0: if (filePtr->fd != fileEvPtr->fd) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * The code is tricky for two reasons: sl@0: * 1. The file handler's desired events could have changed sl@0: * since the time when the event was queued, so AND the sl@0: * ready mask with the desired mask. sl@0: * 2. The file could have been closed and re-opened since sl@0: * the time when the event was queued. This is why the sl@0: * ready mask is stored in the file handler rather than sl@0: * the queued event: it will be zeroed when a new sl@0: * file handler is created for the newly opened file. sl@0: */ sl@0: sl@0: mask = filePtr->readyMask & filePtr->mask; sl@0: filePtr->readyMask = 0; sl@0: if (mask != 0) { sl@0: (*filePtr->proc)(filePtr->clientData, mask); sl@0: } sl@0: break; sl@0: } sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_WaitForEvent -- sl@0: * sl@0: * This function is called by Tcl_DoOneEvent to wait for new sl@0: * events on the message queue. If the block time is 0, then sl@0: * Tcl_WaitForEvent just polls without blocking. sl@0: * sl@0: * Results: sl@0: * Returns -1 if the select would block forever, otherwise sl@0: * returns 0. sl@0: * sl@0: * Side effects: sl@0: * Queues file events that are detected by the select. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_WaitForEvent(timePtr) sl@0: Tcl_Time *timePtr; /* Maximum block time, or NULL. */ sl@0: { sl@0: FileHandler *filePtr; sl@0: FileHandlerEvent *fileEvPtr; sl@0: int mask; sl@0: #ifdef TCL_THREADS sl@0: int waitForFiles; sl@0: #else sl@0: /* Impl. notes: timeout & timeoutPtr are used if, and only if sl@0: * threads are not enabled. They are the arguments for the regular sl@0: * select() used when the core is not thread-enabled. */ sl@0: sl@0: struct timeval timeout, *timeoutPtr; sl@0: int numFound; sl@0: #endif sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { sl@0: return tclStubs.tcl_WaitForEvent(timePtr); sl@0: } sl@0: sl@0: #ifndef TCL_THREADS sl@0: /* sl@0: * Set up the timeout structure. Note that if there are no events to sl@0: * check for, we return with a negative result rather than blocking sl@0: * forever. sl@0: */ sl@0: sl@0: if (timePtr) { sl@0: timeout.tv_sec = timePtr->sec; sl@0: timeout.tv_usec = timePtr->usec; sl@0: timeoutPtr = &timeout; sl@0: } else if (tsdPtr->numFdBits == 0) { sl@0: /* sl@0: * If there are no threads, no timeout, and no fds registered, sl@0: * then there are no events possible and we must avoid deadlock. sl@0: * Note that this is not entirely correct because there might sl@0: * be a signal that could interrupt the select call, but we sl@0: * don't handle that case if we aren't using threads. sl@0: */ sl@0: sl@0: return -1; sl@0: } else { sl@0: timeoutPtr = NULL; sl@0: } sl@0: #endif sl@0: sl@0: #ifdef TCL_THREADS sl@0: /* sl@0: * Place this thread on the list of interested threads, signal the sl@0: * notifier thread, and wait for a response or a timeout. sl@0: */ sl@0: sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: sl@0: waitForFiles = (tsdPtr->numFdBits > 0); sl@0: if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0 sl@0: #if defined(__APPLE__) && defined(__LP64__) sl@0: /* sl@0: * On 64-bit Darwin, pthread_cond_timedwait() appears to have a bug sl@0: * that causes it to wait forever when passed an absolute time which sl@0: * has already been exceeded by the system time; as a workaround, sl@0: * when given a very brief timeout, just do a poll. [Bug 1457797] sl@0: */ sl@0: || timePtr->usec < 10 sl@0: #endif sl@0: )) { sl@0: /* sl@0: * Cannot emulate a polling select with a polling condition variable. sl@0: * Instead, pretend to wait for files and tell the notifier sl@0: * thread what we are doing. The notifier thread makes sure sl@0: * it goes through select with its select mask in the same state sl@0: * as ours currently is. We block until that happens. sl@0: */ sl@0: sl@0: waitForFiles = 1; sl@0: tsdPtr->pollState = POLL_WANT; sl@0: timePtr = NULL; sl@0: } else { sl@0: tsdPtr->pollState = 0; sl@0: } sl@0: sl@0: if (waitForFiles) { sl@0: /* sl@0: * Add the ThreadSpecificData structure of this thread to the list sl@0: * of ThreadSpecificData structures of all threads that are waiting sl@0: * on file events. sl@0: */ sl@0: sl@0: sl@0: tsdPtr->nextPtr = waitingListPtr; sl@0: if (waitingListPtr) { sl@0: waitingListPtr->prevPtr = tsdPtr; sl@0: } sl@0: tsdPtr->prevPtr = 0; sl@0: waitingListPtr = tsdPtr; sl@0: tsdPtr->onList = 1; sl@0: sl@0: write(triggerPipe, "", 1); sl@0: } sl@0: sl@0: FD_ZERO( &(tsdPtr->readyMasks.readable) ); sl@0: FD_ZERO( &(tsdPtr->readyMasks.writable) ); sl@0: FD_ZERO( &(tsdPtr->readyMasks.exceptional) ); sl@0: sl@0: if (!tsdPtr->eventReady) { sl@0: Tcl_ConditionWait(&tsdPtr->waitCV, ¬ifierMutex, timePtr); sl@0: } sl@0: tsdPtr->eventReady = 0; sl@0: sl@0: if (waitForFiles && tsdPtr->onList) { sl@0: /* sl@0: * Remove the ThreadSpecificData structure of this thread from the sl@0: * waiting list. Alert the notifier thread to recompute its select sl@0: * masks - skipping this caused a hang when trying to close a pipe sl@0: * which the notifier thread was still doing a select on. sl@0: */ sl@0: sl@0: if (tsdPtr->prevPtr) { sl@0: tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; sl@0: } else { sl@0: waitingListPtr = tsdPtr->nextPtr; sl@0: } sl@0: if (tsdPtr->nextPtr) { sl@0: tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; sl@0: } sl@0: tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; sl@0: tsdPtr->onList = 0; sl@0: write(triggerPipe, "", 1); sl@0: } sl@0: sl@0: sl@0: #else sl@0: tsdPtr->readyMasks = tsdPtr->checkMasks; sl@0: numFound = select( tsdPtr->numFdBits, sl@0: &(tsdPtr->readyMasks.readable), sl@0: &(tsdPtr->readyMasks.writable), sl@0: &(tsdPtr->readyMasks.exceptional), sl@0: timeoutPtr ); sl@0: sl@0: /* sl@0: * Some systems don't clear the masks after an error, so sl@0: * we have to do it here. sl@0: */ sl@0: sl@0: if (numFound == -1) { sl@0: FD_ZERO( &(tsdPtr->readyMasks.readable ) ); sl@0: FD_ZERO( &(tsdPtr->readyMasks.writable ) ); sl@0: FD_ZERO( &(tsdPtr->readyMasks.exceptional ) ); sl@0: } sl@0: #endif sl@0: sl@0: /* sl@0: * Queue all detected file events before returning. sl@0: */ sl@0: sl@0: for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); sl@0: filePtr = filePtr->nextPtr) { sl@0: sl@0: mask = 0; sl@0: if ( FD_ISSET( filePtr->fd, &(tsdPtr->readyMasks.readable) ) ) { sl@0: mask |= TCL_READABLE; sl@0: } sl@0: if ( FD_ISSET( filePtr->fd, &(tsdPtr->readyMasks.writable) ) ) { sl@0: mask |= TCL_WRITABLE; sl@0: } sl@0: if ( FD_ISSET( filePtr->fd, &(tsdPtr->readyMasks.exceptional) ) ) { sl@0: mask |= TCL_EXCEPTION; sl@0: } sl@0: sl@0: if (!mask) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Don't bother to queue an event if the mask was previously sl@0: * non-zero since an event must still be on the queue. sl@0: */ sl@0: sl@0: if (filePtr->readyMask == 0) { sl@0: fileEvPtr = (FileHandlerEvent *) ckalloc( sl@0: sizeof(FileHandlerEvent)); sl@0: fileEvPtr->header.proc = FileHandlerEventProc; sl@0: fileEvPtr->fd = filePtr->fd; sl@0: Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); sl@0: } sl@0: filePtr->readyMask = mask; sl@0: } sl@0: #ifdef TCL_THREADS sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: #endif sl@0: return 0; sl@0: } sl@0: sl@0: #ifdef TCL_THREADS sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * NotifierThreadProc -- sl@0: * sl@0: * This routine is the initial (and only) function executed by the sl@0: * special notifier thread. Its job is to wait for file descriptors sl@0: * to become readable or writable or to have an exception condition sl@0: * and then to notify other threads who are interested in this sl@0: * information by signalling a condition variable. Other threads sl@0: * can signal this notifier thread of a change in their interests sl@0: * by writing a single byte to a special pipe that the notifier sl@0: * thread is monitoring. sl@0: * sl@0: * Result: sl@0: * None. Once started, this routine never exits. It dies with sl@0: * the overall process. sl@0: * sl@0: * Side effects: sl@0: * The trigger pipe used to signal the notifier thread is created sl@0: * when the notifier thread first starts. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: NotifierThreadProc(clientData) sl@0: ClientData clientData; /* Not used. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: fd_set readableMask; sl@0: fd_set writableMask; sl@0: fd_set exceptionalMask; sl@0: int fds[2]; sl@0: int i, status, numFdBits = 0, receivePipe; sl@0: long found; sl@0: struct timeval poll = {0., 0.}, *timePtr; sl@0: char buf[2]; sl@0: sl@0: if (pipe(fds) != 0) { sl@0: panic("NotifierThreadProc: could not create trigger pipe."); sl@0: } sl@0: sl@0: receivePipe = fds[0]; sl@0: sl@0: #ifndef USE_FIONBIO sl@0: status = fcntl(receivePipe, F_GETFL); sl@0: status |= O_NONBLOCK; sl@0: if (fcntl(receivePipe, F_SETFL, status) < 0) { sl@0: panic("NotifierThreadProc: could not make receive pipe non blocking."); sl@0: } sl@0: status = fcntl(fds[1], F_GETFL); sl@0: status |= O_NONBLOCK; sl@0: if (fcntl(fds[1], F_SETFL, status) < 0) { sl@0: panic("NotifierThreadProc: could not make trigger pipe non blocking."); sl@0: } sl@0: #else sl@0: if (ioctl(receivePipe, (int) FIONBIO, &status) < 0) { sl@0: panic("NotifierThreadProc: could not make receive pipe non blocking."); sl@0: } sl@0: if (ioctl(fds[1], (int) FIONBIO, &status) < 0) { sl@0: panic("NotifierThreadProc: could not make trigger pipe non blocking."); sl@0: } sl@0: #endif sl@0: sl@0: /* sl@0: * Install the write end of the pipe into the global variable. sl@0: */ sl@0: sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: triggerPipe = fds[1]; sl@0: sl@0: /* sl@0: * Signal any threads that are waiting. sl@0: */ sl@0: sl@0: Tcl_ConditionNotify(¬ifierCV); sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: sl@0: /* sl@0: * Look for file events and report them to interested threads. sl@0: */ sl@0: sl@0: while (1) { sl@0: sl@0: FD_ZERO( &readableMask ); sl@0: FD_ZERO( &writableMask ); sl@0: FD_ZERO( &exceptionalMask ); sl@0: sl@0: /* sl@0: * Compute the logical OR of the select masks from all the sl@0: * waiting notifiers. sl@0: */ sl@0: sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: timePtr = NULL; sl@0: for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { sl@0: for ( i = tsdPtr->numFdBits-1; i >= 0; --i ) { sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.readable) ) ) { sl@0: FD_SET( i, &readableMask ); sl@0: } sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.writable) ) ) { sl@0: FD_SET( i, &writableMask ); sl@0: } sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.exceptional) ) ) { sl@0: FD_SET( i, &exceptionalMask ); sl@0: } sl@0: } sl@0: if ( tsdPtr->numFdBits > numFdBits ) { sl@0: numFdBits = tsdPtr->numFdBits; sl@0: } sl@0: if (tsdPtr->pollState & POLL_WANT) { sl@0: /* sl@0: * Here we make sure we go through select() with the same sl@0: * mask bits that were present when the thread tried to poll. sl@0: */ sl@0: sl@0: tsdPtr->pollState |= POLL_DONE; sl@0: timePtr = &poll; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: sl@0: /* sl@0: * Set up the select mask to include the receive pipe. sl@0: */ sl@0: sl@0: if ( receivePipe >= numFdBits ) { sl@0: numFdBits = receivePipe + 1; sl@0: } sl@0: FD_SET( receivePipe, &readableMask ); sl@0: sl@0: if ( select( numFdBits, &readableMask, &writableMask, sl@0: &exceptionalMask, timePtr) == -1 ) { sl@0: /* sl@0: * Try again immediately on an error. sl@0: */ sl@0: sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Alert any threads that are waiting on a ready file descriptor. sl@0: */ sl@0: sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { sl@0: found = 0; sl@0: sl@0: for ( i = tsdPtr->numFdBits-1; i >= 0; --i ) { sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.readable) ) sl@0: && FD_ISSET( i, &readableMask ) ) { sl@0: FD_SET( i, &(tsdPtr->readyMasks.readable) ); sl@0: found = 1; sl@0: } sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.writable) ) sl@0: && FD_ISSET( i, &writableMask ) ) { sl@0: FD_SET( i, &(tsdPtr->readyMasks.writable) ); sl@0: found = 1; sl@0: } sl@0: if ( FD_ISSET( i, &(tsdPtr->checkMasks.exceptional) ) sl@0: && FD_ISSET( i, &exceptionalMask ) ) { sl@0: FD_SET( i, &(tsdPtr->readyMasks.exceptional) ); sl@0: found = 1; sl@0: } sl@0: } sl@0: sl@0: if (found || (tsdPtr->pollState & POLL_DONE)) { sl@0: tsdPtr->eventReady = 1; sl@0: if (tsdPtr->onList) { sl@0: /* sl@0: * Remove the ThreadSpecificData structure of this sl@0: * thread from the waiting list. This prevents us from sl@0: * continuously spining on select until the other sl@0: * threads runs and services the file event. sl@0: */ sl@0: sl@0: if (tsdPtr->prevPtr) { sl@0: tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; sl@0: } else { sl@0: waitingListPtr = tsdPtr->nextPtr; sl@0: } sl@0: if (tsdPtr->nextPtr) { sl@0: tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; sl@0: } sl@0: tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; sl@0: tsdPtr->onList = 0; sl@0: tsdPtr->pollState = 0; sl@0: } sl@0: Tcl_ConditionNotify(&tsdPtr->waitCV); sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: sl@0: /* sl@0: * Consume the next byte from the notifier pipe if the pipe was sl@0: * readable. Note that there may be multiple bytes pending, but sl@0: * to avoid a race condition we only read one at a time. sl@0: */ sl@0: sl@0: if ( FD_ISSET( receivePipe, &readableMask ) ) { sl@0: i = read(receivePipe, buf, 1); sl@0: sl@0: if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) { sl@0: /* sl@0: * Someone closed the write end of the pipe or sent us a sl@0: * Quit message [Bug: 4139] and then closed the write end sl@0: * of the pipe so we need to shut down the notifier thread. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Clean up the read end of the pipe and signal any threads waiting on sl@0: * termination of the notifier thread. sl@0: */ sl@0: sl@0: close(receivePipe); sl@0: Tcl_MutexLock(¬ifierMutex); sl@0: triggerPipe = -1; sl@0: Tcl_ConditionNotify(¬ifierCV); sl@0: Tcl_MutexUnlock(¬ifierMutex); sl@0: sl@0: TclpThreadExit (0); sl@0: } sl@0: #endif sl@0: sl@0: #endif /* HAVE_COREFOUNDATION */