diff options
Diffstat (limited to 'macosx/tclMacOSXNotify.c')
-rw-r--r-- | macosx/tclMacOSXNotify.c | 1425 |
1 files changed, 1065 insertions, 360 deletions
diff --git a/macosx/tclMacOSXNotify.c b/macosx/tclMacOSXNotify.c index d342452..fb3c848 100644 --- a/macosx/tclMacOSXNotify.c +++ b/macosx/tclMacOSXNotify.c @@ -6,24 +6,184 @@ * This file works together with generic/tclNotify.c. * * Copyright (c) 1995-1997 Sun Microsystems, Inc. - * Copyright 2001, Apple Computer, Inc. - * Copyright (c) 2005-2008 Daniel A. Steffen <das@users.sourceforge.net> + * Copyright 2001-2009, Apple Inc. + * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net> * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" -#include "tclPort.h" #ifdef HAVE_COREFOUNDATION /* Traditional unix select-based notifier is * in tclUnixNotfy.c */ #include <CoreFoundation/CoreFoundation.h> #include <pthread.h> +/* #define TCL_MAC_DEBUG_NOTIFIER 1 */ + extern TclStubs tclStubs; extern Tcl_NotifierProcs tclOriginalNotifier; /* + * We use the Darwin-native spinlock API rather than pthread mutexes for + * notifier locking: this radically simplifies the implementation and lowers + * overhead. Note that these are not pure spinlocks, they employ various + * strategies to back off and relinquish the processor, making them immune to + * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin + * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s). + */ + +#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) +/* + * Use OSSpinLock API where available (Tiger or later). + */ + +#include <libkern/OSAtomic.h> + +#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 +/* + * Support for weakly importing spinlock API. + */ +#define WEAK_IMPORT_SPINLOCKLOCK +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 +#define VOLATILE volatile +#else +#define VOLATILE +#endif +#ifndef bool +#define bool int +#endif +extern void OSSpinLockLock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; +extern void OSSpinLockUnlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; +extern bool OSSpinLockTry(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; +extern void _spin_lock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; +extern void _spin_unlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; +extern bool _spin_lock_try(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; +static void (* lockLock)(VOLATILE OSSpinLock *lock) = NULL; +static void (* lockUnlock)(VOLATILE OSSpinLock *lock) = NULL; +static bool (* lockTry)(VOLATILE OSSpinLock *lock) = NULL; +#undef VOLATILE +static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT; +static void SpinLockLockInit(void) { + lockLock = OSSpinLockLock != NULL ? OSSpinLockLock : _spin_lock; + lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock; + lockTry = OSSpinLockTry != NULL ? OSSpinLockTry : _spin_lock_try; + if (lockLock == NULL || lockUnlock == NULL) { + Tcl_Panic("SpinLockLockInit: no spinlock API available"); + } +} +#define SpinLockLock(p) lockLock(p) +#define SpinLockUnlock(p) lockUnlock(p) +#define SpinLockTry(p) lockTry(p) +#else +#define SpinLockLock(p) OSSpinLockLock(p) +#define SpinLockUnlock(p) OSSpinLockUnlock(p) +#define SpinLockTry(p) OSSpinLockTry(p) +#endif /* HAVE_WEAK_IMPORT */ +#define SPINLOCK_INIT OS_SPINLOCK_INIT + +#else +/* + * Otherwise, use commpage spinlock SPI directly. + */ + +typedef uint32_t OSSpinLock; +extern void _spin_lock(OSSpinLock *lock); +extern void _spin_unlock(OSSpinLock *lock); +extern int _spin_lock_try(OSSpinLock *lock); +#define SpinLockLock(p) _spin_lock(p) +#define SpinLockUnlock(p) _spin_unlock(p) +#define SpinLockTry(p) _spin_lock_try(p) +#define SPINLOCK_INIT 0 + +#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ + +/* + * These spinlocks lock access to the global notifier state. + */ + +static OSSpinLock notifierInitLock = SPINLOCK_INIT; +static OSSpinLock notifierLock = SPINLOCK_INIT; + +/* + * Macros abstracting notifier locking/unlocking + */ + +#define LOCK_NOTIFIER_INIT SpinLockLock(¬ifierInitLock) +#define UNLOCK_NOTIFIER_INIT SpinLockUnlock(¬ifierInitLock) +#define LOCK_NOTIFIER SpinLockLock(¬ifierLock) +#define UNLOCK_NOTIFIER SpinLockUnlock(¬ifierLock) +#define LOCK_NOTIFIER_TSD SpinLockLock(&tsdPtr->tsdLock) +#define UNLOCK_NOTIFIER_TSD SpinLockUnlock(&tsdPtr->tsdLock) + +#ifdef TCL_MAC_DEBUG_NOTIFIER +#define TclMacOSXNotifierDbgMsg(m, ...) do { \ + fprintf(notifierLog?notifierLog:stderr, "tclMacOSXNotify.c:%d: " \ + "%s() pid %5d thread %10p: " m "\n", __LINE__, __func__, \ + getpid(), pthread_self(), ##__VA_ARGS__); \ + fflush(notifierLog?notifierLog:stderr); \ + } while (0) + +/* + * Debug version of SpinLockLock that logs the time spent waiting for the lock + */ + +#define SpinLockLockDbg(p) if (!SpinLockTry(p)) { \ + Tcl_WideInt s = TclpGetWideClicks(), e; \ + SpinLockLock(p); e = TclpGetWideClicks(); \ + TclMacOSXNotifierDbgMsg("waited on %s for %8.0f ns", \ + #p, TclpWideClicksToNanoseconds(e-s)); \ + } +#undef LOCK_NOTIFIER_INIT +#define LOCK_NOTIFIER_INIT SpinLockLockDbg(¬ifierInitLock) +#undef LOCK_NOTIFIER +#define LOCK_NOTIFIER SpinLockLockDbg(¬ifierLock) +#undef LOCK_NOTIFIER_TSD +#define LOCK_NOTIFIER_TSD SpinLockLockDbg(&tsdPtr->tsdLock) +#include <asl.h> +static FILE *notifierLog = NULL; +#ifndef NOTIFIER_LOG +#define NOTIFIER_LOG "/tmp/tclMacOSXNotify.log" +#endif +#define OPEN_NOTIFIER_LOG if (!notifierLog) { \ + notifierLog = fopen(NOTIFIER_LOG, "a"); \ + /*TclMacOSXNotifierDbgMsg("open log"); \ + asl_set_filter(NULL, \ + ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); \ + asl_add_log_file(NULL, \ + fileno(notifierLog));*/ \ + } +#define CLOSE_NOTIFIER_LOG if (notifierLog) { \ + /*asl_remove_log_file(NULL, \ + fileno(notifierLog)); \ + TclMacOSXNotifierDbgMsg("close log");*/ \ + fclose(notifierLog); \ + notifierLog = NULL; \ + } +#define ENABLE_ASL if (notifierLog) { \ + /*tsdPtr->asl = asl_open(NULL, "com.apple.console", ASL_OPT_NO_REMOTE); \ + asl_set_filter(tsdPtr->asl, \ + ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); \ + asl_add_log_file(tsdPtr->asl, \ + fileno(notifierLog));*/ \ + } +#define DISABLE_ASL /*if (tsdPtr->asl) { \ + if (notifierLog) { \ + asl_remove_log_file(tsdPtr->asl, \ + fileno(notifierLog)); \ + } \ + asl_close(tsdPtr->asl); \ + }*/ +#define ASLCLIENT /*aslclient asl*/ +#else +#define TclMacOSXNotifierDbgMsg(m, ...) +#define OPEN_NOTIFIER_LOG +#define CLOSE_NOTIFIER_LOG +#define ENABLE_ASL +#define DISABLE_ASL +#endif /* TCL_MAC_DEBUG_NOTIFIER */ + +/* * This structure is used to keep track of the notifier info for a registered * file. */ @@ -76,6 +236,30 @@ typedef struct SelectMasks { typedef struct ThreadSpecificData { FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler list. */ + int polled; /* True if the notifier thread has polled for + * this thread. + */ + int sleeping; /* True if runloop is inside Tcl_Sleep. */ + int runLoopSourcePerformed; /* True after the runLoopSource callack was + * performed. */ + int runLoopRunning; /* True if this thread's Tcl runLoop is running */ + int runLoopNestingLevel; /* Level of nested runLoop invocations */ + int runLoopServicingEvents; /* True if this thread's runLoop is servicing + * tcl events */ + /* Must hold the notifierLock before accessing the following fields: */ + /* Start notifierLock section */ + int onList; /* True if this thread is on the waitingList */ + 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. + */ + /* End notifierLock section */ + OSSpinLock tsdLock; /* Must hold this lock before acessing the + * following fields from more than one thread. + */ + /* Start tsdLock section */ 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 @@ -86,26 +270,25 @@ typedef struct ThreadSpecificData { int numFdBits; /* Number of valid bits in checkMasks (one * more than highest fd for which * Tcl_WatchFile has been called). */ - 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 - * notifierLock before accessing these - * fields. */ + int polling; /* True if this thread is polling for events */ + CFRunLoopRef runLoop; /* This thread's CFRunLoop, needs to be woken + * up whenever the runLoopSource is signaled */ CFRunLoopSourceRef runLoopSource; /* Any other thread alerts a notifier that an * event is ready to be processed by signaling * this CFRunLoopSource. */ - CFRunLoopRef runLoop; /* This thread's CFRunLoop, needs to be woken - * up whenever the runLoopSource is - * signaled. */ - int eventReady; /* True if an event is ready to be - * processed. */ + CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; + /* Adds/removes this thread from waitingList + * when the CFRunLoop starts/stops. */ + CFRunLoopTimerRef runLoopTimer; + /* Wakes up CFRunLoop after given timeout when + * running embedded. */ + /* End tsdLock section */ + CFTimeInterval waitTime; /* runLoopTimer wait time when running + * embedded. */ +#ifdef TCL_MAC_DEBUG_NOTIFIER + ASLCLIENT; +#endif } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; @@ -147,134 +330,56 @@ static int triggerPipe = -1; static int receivePipe = -1; /* Output end of triggerPipe */ /* - * We use the Darwin-native spinlock API rather than pthread mutexes for - * notifier locking: this radically simplifies the implementation and lowers - * overhead. Note that these are not pure spinlocks, they employ various - * strategies to back off and relinquish the processor, making them immune to - * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin - * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s). - */ - -#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) -/* - * Use OSSpinLock API where available (Tiger or later). - */ - -#include <libkern/OSAtomic.h> - -#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 -/* - * Support for weakly importing spinlock API. - */ -#define WEAK_IMPORT_SPINLOCKLOCK -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 -#define VOLATILE volatile -#else -#define VOLATILE -#endif -#ifndef bool -#define bool int -#endif -extern void OSSpinLockLock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern void OSSpinLockUnlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern bool OSSpinLockTry(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern void _spin_lock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern void _spin_unlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern bool _spin_lock_try(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -static void (* lockLock)(VOLATILE OSSpinLock *lock) = NULL; -static void (* lockUnlock)(VOLATILE OSSpinLock *lock) = NULL; -static bool (* lockTry)(VOLATILE OSSpinLock *lock) = NULL; -#undef VOLATILE -static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT; -static void SpinLockLockInit(void) { - lockLock = OSSpinLockLock != NULL ? OSSpinLockLock : _spin_lock; - lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock; - lockTry = OSSpinLockTry != NULL ? OSSpinLockTry : _spin_lock_try; - if (lockLock == NULL || lockUnlock == NULL) { - Tcl_Panic("SpinLockLockInit: no spinlock API available"); - } -} -#define SpinLockLock(p) lockLock(p) -#define SpinLockUnlock(p) lockUnlock(p) -#define SpinLockTry(p) lockTry(p) -#else -#define SpinLockLock(p) OSSpinLockLock(p) -#define SpinLockUnlock(p) OSSpinLockUnlock(p) -#define SpinLockTry(p) OSSpinLockTry(p) -#endif /* HAVE_WEAK_IMPORT */ -#define SPINLOCK_INIT OS_SPINLOCK_INIT - -#else -/* - * Otherwise, use commpage spinlock SPI directly. - */ - -typedef uint32_t OSSpinLock; -extern void _spin_lock(OSSpinLock *lock); -extern void _spin_unlock(OSSpinLock *lock); -extern int _spin_lock_try(OSSpinLock *lock); -#define SpinLockLock(p) _spin_lock(p) -#define SpinLockUnlock(p) _spin_unlock(p) -#define SpinLockTry(p) _spin_lock_try(p) -#define SPINLOCK_INIT 0 - -#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ - -/* - * These spinlocks lock access to the global notifier state. - */ - -static OSSpinLock notifierInitLock = SPINLOCK_INIT; -static OSSpinLock notifierLock = SPINLOCK_INIT; - -/* - * Macros abstracting notifier locking/unlocking - */ - -#define LOCK_NOTIFIER_INIT SpinLockLock(¬ifierInitLock) -#define UNLOCK_NOTIFIER_INIT SpinLockUnlock(¬ifierInitLock) -#define LOCK_NOTIFIER SpinLockLock(¬ifierLock) -#define UNLOCK_NOTIFIER SpinLockUnlock(¬ifierLock) - -/* - * 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. + * The following static indicates if the notifier thread is running. + * + * You must hold the notifierInitLock before accessing this variable. */ -#define POLL_WANT 0x1 -#define POLL_DONE 0x2 +static int notifierThreadRunning; /* * This is the thread ID of the notifier thread that does select. + * Only valid when notifierThreadRunning is non-zero. + * + * You must hold the notifierInitLock before accessing this variable. */ static pthread_t notifierThread; /* - * Custom run loop mode containing only the run loop source for the - * notifier thread. + * Custom runloop mode for running with only the runloop source for the + * notifier thread */ #ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE -#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" +#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" #endif #ifdef __CONSTANT_CFSTRINGS__ -#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) +#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) #else static CFStringRef tclEventsOnlyRunLoopMode = NULL; #endif /* + * CFTimeInterval to wait forever. + */ + +#define CF_TIMEINTERVAL_FOREVER 5.05e8 + +/* * Static routines defined in this file. */ +static void StartNotifierThread(void); static void NotifierThreadProc(ClientData clientData) - __attribute__ ((__noreturn__)); + __attribute__ ((__noreturn__)); static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); +static void TimerWakeUp(CFRunLoopTimerRef timer, void *info); +static void QueueFileEvents(void *info); +static void UpdateWaitingListAndServiceEvents(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); +static int OnOffWaitingList(ThreadSpecificData *tsdPtr, int onList, + int signalNotifier); #ifdef HAVE_PTHREAD_ATFORK static int atForkInit = 0; @@ -284,8 +389,8 @@ static void AtForkChild(void); #if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 /* Support for weakly importing pthread_atfork. */ #define WEAK_IMPORT_PTHREAD_ATFORK -extern int pthread_atfork(void (*prepare)(void), void (*parent)(void), - void (*child)(void)) WEAK_IMPORT_ATTRIBUTE; +extern int pthread_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void)) WEAK_IMPORT_ATTRIBUTE; #endif /* HAVE_WEAK_IMPORT */ /* * On Darwin 9 and later, it is not possible to call CoreFoundation after @@ -319,14 +424,15 @@ MODULE_SCOPE long tclMacOSXDarwinRelease; ClientData Tcl_InitNotifier(void) { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; - tsdPtr->eventReady = 0; + tsdPtr = TCL_TSD_INIT(&dataKey); #ifdef WEAK_IMPORT_SPINLOCKLOCK /* * Initialize support for weakly imported spinlock API. */ + if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) { Tcl_Panic("Tcl_InitNotifier: pthread_once failed"); } @@ -346,17 +452,58 @@ Tcl_InitNotifier(void) CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFRunLoopSourceRef runLoopSource; CFRunLoopSourceContext runLoopSourceContext; + CFRunLoopObserverContext runLoopObserverContext; + CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext)); runLoopSourceContext.info = tsdPtr; - runLoopSource = CFRunLoopSourceCreate(NULL, 0, &runLoopSourceContext); + runLoopSourceContext.perform = QueueFileEvents; + runLoopSource = CFRunLoopSourceCreate(NULL, LONG_MIN, + &runLoopSourceContext); if (!runLoopSource) { Tcl_Panic("Tcl_InitNotifier: could not create CFRunLoopSource"); } CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopCommonModes); CFRunLoopAddSource(runLoop, runLoopSource, tclEventsOnlyRunLoopMode); - tsdPtr->runLoopSource = runLoopSource; + + bzero(&runLoopObserverContext, sizeof(CFRunLoopObserverContext)); + runLoopObserverContext.info = tsdPtr; + runLoopObserver = CFRunLoopObserverCreate(NULL, + kCFRunLoopEntry|kCFRunLoopExit|kCFRunLoopBeforeWaiting, TRUE, + LONG_MIN, UpdateWaitingListAndServiceEvents, + &runLoopObserverContext); + if (!runLoopObserver) { + Tcl_Panic("Tcl_InitNotifier: could not create " + "CFRunLoopObserver"); + } + CFRunLoopAddObserver(runLoop, runLoopObserver, kCFRunLoopCommonModes); + + /* + * Create a second CFRunLoopObserver with the same callback as above + * for the tclEventsOnlyRunLoopMode to ensure that the callback can be + * re-entered via Tcl_ServiceAll() in the kCFRunLoopBeforeWaiting case + * (CFRunLoop prevents observer callback re-entry of a given observer + * instance). + */ + + runLoopObserverTcl = CFRunLoopObserverCreate(NULL, + kCFRunLoopEntry|kCFRunLoopExit|kCFRunLoopBeforeWaiting, TRUE, + LONG_MIN, UpdateWaitingListAndServiceEvents, + &runLoopObserverContext); + if (!runLoopObserverTcl) { + Tcl_Panic("Tcl_InitNotifier: could not create " + "CFRunLoopObserver"); + } + CFRunLoopAddObserver(runLoop, runLoopObserverTcl, + tclEventsOnlyRunLoopMode); + tsdPtr->runLoop = runLoop; + tsdPtr->runLoopSource = runLoopSource; + tsdPtr->runLoopObserver = runLoopObserver; + tsdPtr->runLoopObserverTcl = runLoopObserverTcl; + tsdPtr->runLoopTimer = NULL; + tsdPtr->waitTime = CF_TIMEINTERVAL_FOREVER; + tsdPtr->tsdLock = SPINLOCK_INIT; } LOCK_NOTIFIER_INIT; @@ -372,6 +519,7 @@ Tcl_InitNotifier(void) #endif !atForkInit) { int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild); + if (result) { Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed"); } @@ -392,12 +540,14 @@ Tcl_InitNotifier(void) status = fcntl(fds[0], F_GETFL); status |= O_NONBLOCK; if (fcntl(fds[0], F_SETFL, status) < 0) { - Tcl_Panic("Tcl_InitNotifier: could not make receive pipe non blocking"); + Tcl_Panic("Tcl_InitNotifier: could not make receive pipe non " + "blocking"); } status = fcntl(fds[1], F_GETFL); status |= O_NONBLOCK; if (fcntl(fds[1], F_SETFL, status) < 0) { - Tcl_Panic("Tcl_InitNotifier: could not make trigger pipe non blocking"); + Tcl_Panic("Tcl_InitNotifier: could not make trigger pipe non " + "blocking"); } receivePipe = fds[0]; @@ -405,12 +555,14 @@ Tcl_InitNotifier(void) /* * Create notifier thread lazily in Tcl_WaitForEvent() to avoid - * interfering with fork() followed immediately by execve() - * (cannot execve() when more than one thread is present). + * interfering with fork() followed immediately by execve() (we cannot + * execve() when more than one thread is present). */ - notifierThread = 0; + notifierThreadRunning = 0; + OPEN_NOTIFIER_LOG; } + ENABLE_ASL; notifierCount++; UNLOCK_NOTIFIER_INIT; @@ -420,6 +572,82 @@ Tcl_InitNotifier(void) /* *---------------------------------------------------------------------- * + * TclMacOSXNotifierAddRunLoopMode -- + * + * Add the tcl notifier RunLoop source, observer and timer (if any) + * to the given RunLoop mode. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclMacOSXNotifierAddRunLoopMode( + CONST void *runLoopMode) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + CFStringRef mode = (CFStringRef) runLoopMode; + + if (tsdPtr->runLoop) { + CFRunLoopAddSource(tsdPtr->runLoop, tsdPtr->runLoopSource, mode); + CFRunLoopAddObserver(tsdPtr->runLoop, tsdPtr->runLoopObserver, mode); + if (tsdPtr->runLoopTimer) { + CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, mode); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * StartNotifierThread -- + * + * Start notifier thread if necessary. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +StartNotifierThread(void) +{ + LOCK_NOTIFIER_INIT; + if (!notifierCount) { + Tcl_Panic("StartNotifierThread: notifier not initialized"); + } + if (!notifierThreadRunning) { + int result; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_attr_setstacksize(&attr, 60 * 1024); + result = pthread_create(¬ifierThread, &attr, + (void * (*)(void *))NotifierThreadProc, NULL); + pthread_attr_destroy(&attr); + if (result) { + Tcl_Panic("StartNotifierThread: unable to start notifier thread"); + } + notifierThreadRunning = 1; + } + UNLOCK_NOTIFIER_INIT; +} + + +/* + *---------------------------------------------------------------------- + * * Tcl_FinalizeNotifier -- * * This function is called to cleanup the notifier state before a thread @@ -439,10 +667,13 @@ void Tcl_FinalizeNotifier( ClientData clientData) /* Not used. */ { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; + + tsdPtr = TCL_TSD_INIT(&dataKey); LOCK_NOTIFIER_INIT; notifierCount--; + DISABLE_ASL; /* * If this is the last thread to use the notifier, close the notifier pipe @@ -450,52 +681,63 @@ Tcl_FinalizeNotifier( */ if (notifierCount == 0) { - int result; + if (triggerPipe != -1) { + /* + * Send "q" message to the notifier thread so that it will + * terminate. The notifier will return from its call to select() + * and notice that a "q" message has arrived, it will then close + * its side of the pipe and terminate its thread. Note the we can + * not just close the pipe and check for EOF in the notifier thread + * because if a background child process was created with exec, + * select() would not register the EOF on the pipe until the child + * processes had terminated. [Bug: 4139] [Bug: 1222872] + */ - if (triggerPipe < 0) { - Tcl_Panic("Tcl_FinalizeNotifier: notifier pipe not initialized"); - } + write(triggerPipe, "q", 1); + close(triggerPipe); - /* - * Send "q" message to the notifier thread so that it will terminate. - * The notifier will return from its call to select() and notice that - * a "q" message has arrived, it will then close its side of the pipe - * and terminate its thread. Note the we can not just close the pipe - * and check for EOF in the notifier thread because if a background - * child process was created with exec, select() would not register - * the EOF on the pipe until the child processes had terminated. [Bug: - * 4139] [Bug: 1222872] - */ + if (notifierThreadRunning) { + int result = pthread_join(notifierThread, NULL); - write(triggerPipe, "q", 1); - close(triggerPipe); - - if (notifierThread) { - result = pthread_join(notifierThread, NULL); - if (result) { - Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier thread"); + if (result) { + Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier " + "thread"); + } + notifierThreadRunning = 0; } - notifierThread = 0; - } - close(receivePipe); - triggerPipe = -1; + close(receivePipe); + triggerPipe = -1; + } + CLOSE_NOTIFIER_LOG; } UNLOCK_NOTIFIER_INIT; - LOCK_NOTIFIER; /* for concurrency with Tcl_AlertNotifier */ + LOCK_NOTIFIER_TSD; /* For concurrency with Tcl_AlertNotifier */ if (tsdPtr->runLoop) { tsdPtr->runLoop = NULL; /* - * Remove runLoopSource from all CFRunLoops and release it. + * Remove runLoopSource, runLoopObserver and runLoopTimer from all + * CFRunLoops. */ CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); CFRelease(tsdPtr->runLoopSource); tsdPtr->runLoopSource = NULL; + CFRunLoopObserverInvalidate(tsdPtr->runLoopObserver); + CFRelease(tsdPtr->runLoopObserver); + tsdPtr->runLoopObserver = NULL; + CFRunLoopObserverInvalidate(tsdPtr->runLoopObserverTcl); + CFRelease(tsdPtr->runLoopObserverTcl); + tsdPtr->runLoopObserverTcl = NULL; + if (tsdPtr->runLoopTimer) { + CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer); + CFRelease(tsdPtr->runLoopTimer); + tsdPtr->runLoopTimer = NULL; + } } - UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_TSD; } /* @@ -521,15 +763,14 @@ void Tcl_AlertNotifier( ClientData clientData) { - ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; + ThreadSpecificData *tsdPtr = clientData; - LOCK_NOTIFIER; + LOCK_NOTIFIER_TSD; if (tsdPtr->runLoop) { - tsdPtr->eventReady = 1; CFRunLoopSourceSignal(tsdPtr->runLoopSource); CFRunLoopWakeUp(tsdPtr->runLoop); } - UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_TSD; } /* @@ -537,15 +778,13 @@ Tcl_AlertNotifier( * * Tcl_SetTimer -- * - * This function sets the current notifier timer value. This interface is - * not implemented in this notifier because we are always running inside - * of Tcl_DoOneEvent. + * This function sets the current notifier timer value. * * Results: * None. * * Side effects: - * None. + * Replaces any previous timer. * *---------------------------------------------------------------------- */ @@ -554,15 +793,58 @@ void Tcl_SetTimer( 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. - */ + ThreadSpecificData *tsdPtr; + CFRunLoopTimerRef runLoopTimer; + CFTimeInterval waitTime; if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { tclStubs.tcl_SetTimer(timePtr); + return; + } + + tsdPtr = TCL_TSD_INIT(&dataKey); + runLoopTimer = tsdPtr->runLoopTimer; + if (!runLoopTimer) { + return; } + if (timePtr) { + Tcl_Time vTime = *timePtr; + + if (vTime.sec != 0 || vTime.usec != 0) { + tclScaleTimeProcPtr(&vTime, tclTimeClientData); + waitTime = vTime.sec + 1.0e-6 * vTime.usec; + } else { + waitTime = 0; + } + } else { + waitTime = CF_TIMEINTERVAL_FOREVER; + } + tsdPtr->waitTime = waitTime; + CFRunLoopTimerSetNextFireDate(runLoopTimer, + CFAbsoluteTimeGetCurrent() + waitTime); +} + +/* + *---------------------------------------------------------------------- + * + * TimerWakeUp -- + * + * CFRunLoopTimer callback. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +TimerWakeUp( + CFRunLoopTimerRef timer, + void *info) +{ } /* @@ -586,6 +868,23 @@ Tcl_ServiceModeHook( int mode) /* Either TCL_SERVICE_ALL, or * TCL_SERVICE_NONE. */ { + ThreadSpecificData *tsdPtr; + + tsdPtr = TCL_TSD_INIT(&dataKey); + + if (mode == TCL_SERVICE_ALL && !tsdPtr->runLoopTimer) { + if (!tsdPtr->runLoop) { + Tcl_Panic("Tcl_ServiceModeHook: Notifier not initialized"); + } + tsdPtr->runLoopTimer = CFRunLoopTimerCreate(NULL, + CFAbsoluteTimeGetCurrent() + CF_TIMEINTERVAL_FOREVER, + CF_TIMEINTERVAL_FOREVER, 0, 0, TimerWakeUp, NULL); + if (tsdPtr->runLoopTimer) { + CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, + kCFRunLoopCommonModes); + StartNotifierThread(); + } + } } /* @@ -615,7 +914,7 @@ Tcl_CreateFileHandler( * event. */ ClientData clientData) /* Arbitrary data to pass to proc. */ { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; FileHandler *filePtr; if (tclStubs.tcl_CreateFileHandler != @@ -624,6 +923,8 @@ Tcl_CreateFileHandler( return; } + tsdPtr = TCL_TSD_INIT(&dataKey); + for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; filePtr = filePtr->nextPtr) { if (filePtr->fd == fd) { @@ -631,7 +932,7 @@ Tcl_CreateFileHandler( } } if (filePtr == NULL) { - filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); + filePtr = (FileHandler *) ckalloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->readyMask = 0; filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; @@ -645,6 +946,7 @@ Tcl_CreateFileHandler( * Update the check masks for this file. */ + LOCK_NOTIFIER_TSD; if (mask & TCL_READABLE) { FD_SET(fd, &(tsdPtr->checkMasks.readable)); } else { @@ -663,6 +965,7 @@ Tcl_CreateFileHandler( if (tsdPtr->numFdBits <= fd) { tsdPtr->numFdBits = fd+1; } + UNLOCK_NOTIFIER_TSD; } /* @@ -687,8 +990,8 @@ Tcl_DeleteFileHandler( * function. */ { FileHandler *filePtr, *prevPtr; - int i; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int i, numFdBits; + ThreadSpecificData *tsdPtr; if (tclStubs.tcl_DeleteFileHandler != tclOriginalNotifier.deleteFileHandlerProc) { @@ -696,12 +999,15 @@ Tcl_DeleteFileHandler( return; } + tsdPtr = TCL_TSD_INIT(&dataKey); + numFdBits = -1; + /* * 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) { + prevPtr = filePtr, filePtr = filePtr->nextPtr) { if (filePtr == NULL) { return; } @@ -711,35 +1017,41 @@ Tcl_DeleteFileHandler( } /* - * 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.exceptional)); - } - - /* * Find current max fd. */ if (fd+1 == tsdPtr->numFdBits) { - tsdPtr->numFdBits = 0; + 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.exceptional))) { - tsdPtr->numFdBits = i+1; + numFdBits = i+1; break; } } } + LOCK_NOTIFIER_TSD; + if (numFdBits != -1) { + tsdPtr->numFdBits = numFdBits; + } + + /* + * 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.exceptional)); + } + UNLOCK_NOTIFIER_TSD; + /* * Clean up information in the callback record. */ @@ -818,7 +1130,18 @@ FileHandlerEventProc( mask = filePtr->readyMask & filePtr->mask; filePtr->readyMask = 0; if (mask != 0) { - (*filePtr->proc)(filePtr->clientData, mask); + LOCK_NOTIFIER_TSD; + if (mask & TCL_READABLE) { + FD_CLR(filePtr->fd, &(tsdPtr->readyMasks.readable)); + } + if (mask & TCL_WRITABLE) { + FD_CLR(filePtr->fd, &(tsdPtr->readyMasks.writable)); + } + if (mask & TCL_EXCEPTION) { + FD_CLR(filePtr->fd, &(tsdPtr->readyMasks.exceptional)); + } + UNLOCK_NOTIFIER_TSD; + filePtr->proc(filePtr->clientData, mask); } break; } @@ -835,10 +1158,11 @@ FileHandlerEventProc( * polls without blocking. * * Results: - * Returns -1 if the select would block forever, otherwise returns 0. + * Returns 0 if a tcl event or timeout ocurred and 1 if a non-tcl + * CFRunLoop source was processed. * * Side effects: - * Queues file events that are detected by the select. + * None. * *---------------------------------------------------------------------- */ @@ -847,174 +1171,525 @@ int Tcl_WaitForEvent( Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { - FileHandler *filePtr; - FileHandlerEvent *fileEvPtr; - int mask; - int waitForFiles; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int result, polling, runLoopRunning; + CFTimeInterval waitTime; + SInt32 runLoopStatus; + ThreadSpecificData *tsdPtr; if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { return tclStubs.tcl_WaitForEvent(timePtr); } + result = -1; + polling = 0; + waitTime = CF_TIMEINTERVAL_FOREVER; + tsdPtr = TCL_TSD_INIT(&dataKey); - /* - * Start notifier thread if necessary. - */ - - LOCK_NOTIFIER_INIT; - if (!notifierCount) { - Tcl_Panic("Tcl_WaitForEvent: notifier not initialized"); + if (!tsdPtr->runLoop) { + Tcl_Panic("Tcl_WaitForEvent: Notifier not initialized"); } - if (!notifierThread) { - int result; - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_attr_setstacksize(&attr, 60 * 1024); - result = pthread_create(¬ifierThread, &attr, - (void * (*)(void *))NotifierThreadProc, NULL); - pthread_attr_destroy(&attr); - if (result || !notifierThread) { - Tcl_Panic("Tcl_WaitForEvent: unable to start notifier thread"); + if (timePtr) { + Tcl_Time vTime = *timePtr; + + /* + * 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 (vTime.sec != 0 || vTime.usec != 0) { + tclScaleTimeProcPtr(&vTime, tclTimeClientData); + waitTime = vTime.sec + 1.0e-6 * vTime.usec; + } else { + /* + * Polling: 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. + */ + + polling = 1; } } - UNLOCK_NOTIFIER_INIT; + + StartNotifierThread(); + + LOCK_NOTIFIER_TSD; + tsdPtr->polling = polling; + UNLOCK_NOTIFIER_TSD; + tsdPtr->runLoopSourcePerformed = 0; /* - * Place this thread on the list of interested threads, signal the - * notifier thread, and wait for a response or a timeout. + * If the Tcl runloop is already running (e.g. if Tcl_WaitForEvent was + * called recursively) or is servicing events via the runloop observer, + * re-run it in a custom runloop mode containing only the source for the + * notifier thread, otherwise wakeups from other sources added to the + * common runloop modes might get lost or 3rd party event handlers might + * get called when they do not expect to be. */ - LOCK_NOTIFIER; - if (!tsdPtr->runLoop) { - Tcl_Panic("Tcl_WaitForEvent: CFRunLoop not initialized"); - } - waitForFiles = (tsdPtr->numFdBits > 0); - if (timePtr != NULL && timePtr->sec == 0 && timePtr->usec == 0) { - /* - * 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 { - tsdPtr->pollState = 0; + runLoopRunning = tsdPtr->runLoopRunning; + tsdPtr->runLoopRunning = 1; + runLoopStatus = CFRunLoopRunInMode(tsdPtr->runLoopServicingEvents || + runLoopRunning ? tclEventsOnlyRunLoopMode : kCFRunLoopDefaultMode, + waitTime, TRUE); + tsdPtr->runLoopRunning = runLoopRunning; + + LOCK_NOTIFIER_TSD; + tsdPtr->polling = 0; + UNLOCK_NOTIFIER_TSD; + switch (runLoopStatus) { + case kCFRunLoopRunFinished: + Tcl_Panic("Tcl_WaitForEvent: CFRunLoop finished"); + break; + case kCFRunLoopRunTimedOut: + QueueFileEvents(tsdPtr); + result = 0; + break; + case kCFRunLoopRunStopped: + case kCFRunLoopRunHandledSource: + result = tsdPtr->runLoopSourcePerformed ? 0 : 1; + break; } - if (waitForFiles) { - /* - * Add the ThreadSpecificData structure of this thread to the list of - * ThreadSpecificData structures of all threads that are waiting on - * file events. - */ + return result; +} + +/* + *---------------------------------------------------------------------- + * + * QueueFileEvents -- + * + * CFRunLoopSource callback for queueing file events. + * + * Results: + * None. + * + * Side effects: + * Queues file events that are detected by the select. + * + *---------------------------------------------------------------------- + */ - tsdPtr->nextPtr = waitingListPtr; - if (waitingListPtr) { - waitingListPtr->prevPtr = tsdPtr; - } - tsdPtr->prevPtr = 0; - waitingListPtr = tsdPtr; - tsdPtr->onList = 1; +static void +QueueFileEvents( + void *info) +{ + SelectMasks readyMasks; + FileHandler *filePtr; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) info; - write(triggerPipe, "", 1); - } + /* + * Queue all detected file events. + */ + LOCK_NOTIFIER_TSD; + FD_COPY(&(tsdPtr->readyMasks.readable), &readyMasks.readable); + FD_COPY(&(tsdPtr->readyMasks.writable), &readyMasks.writable); + FD_COPY(&(tsdPtr->readyMasks.exceptional), &readyMasks.exceptional); FD_ZERO(&(tsdPtr->readyMasks.readable)); FD_ZERO(&(tsdPtr->readyMasks.writable)); FD_ZERO(&(tsdPtr->readyMasks.exceptional)); + UNLOCK_NOTIFIER_TSD; + tsdPtr->runLoopSourcePerformed = 1; - if (!tsdPtr->eventReady) { - CFTimeInterval waitTime; - CFStringRef runLoopMode; + for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); + filePtr = filePtr->nextPtr) { + int mask = 0; - if (timePtr == NULL) { - waitTime = 1.0e10; /* Wait forever, as per CFRunLoop.c */ - } else { - waitTime = timePtr->sec + 1.0e-6 * timePtr->usec; + if (FD_ISSET(filePtr->fd, &readyMasks.readable)) { + mask |= TCL_READABLE; + } + if (FD_ISSET(filePtr->fd, &readyMasks.writable)) { + mask |= TCL_WRITABLE; + } + if (FD_ISSET(filePtr->fd, &readyMasks.exceptional)) { + mask |= TCL_EXCEPTION; } + if (!mask) { + continue; + } + /* - * If the run loop is already running (e.g. if Tcl_WaitForEvent was - * called recursively), re-run it in a custom run loop mode containing - * only the source for the notifier thread, otherwise wakeups from other - * sources added to the common run loop modes might get lost. + * Don't bother to queue an event if the mask was previously non-zero + * since an event must still be on the queue. */ - if ((runLoopMode = CFRunLoopCopyCurrentMode(tsdPtr->runLoop))) { - CFRelease(runLoopMode); - runLoopMode = tclEventsOnlyRunLoopMode; - } else { - runLoopMode = kCFRunLoopDefaultMode; + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) + ckalloc(sizeof(FileHandlerEvent)); + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); } - UNLOCK_NOTIFIER; - CFRunLoopRunInMode(runLoopMode, waitTime, TRUE); - LOCK_NOTIFIER; + filePtr->readyMask = mask; } - tsdPtr->eventReady = 0; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateWaitingListAndServiceEvents -- + * + * CFRunLoopObserver callback for updating waitingList and + * servicing Tcl events. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ - 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. - */ +static void +UpdateWaitingListAndServiceEvents( + CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void *info) +{ + ThreadSpecificData *tsdPtr = (ThreadSpecificData*) info; - if (tsdPtr->prevPtr) { - tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; + if (tsdPtr->sleeping) { + return; + } + switch (activity) { + case kCFRunLoopEntry: + tsdPtr->runLoopNestingLevel++; + if (tsdPtr->numFdBits > 0 || tsdPtr->polling) { + LOCK_NOTIFIER; + if (!OnOffWaitingList(tsdPtr, 1, 1) && tsdPtr->polling) { + write(triggerPipe, "", 1); + } + UNLOCK_NOTIFIER; + } + break; + case kCFRunLoopExit: + if (tsdPtr->runLoopNestingLevel == 1) { + LOCK_NOTIFIER; + OnOffWaitingList(tsdPtr, 0, 1); + UNLOCK_NOTIFIER; + } + tsdPtr->runLoopNestingLevel--; + break; + case kCFRunLoopBeforeWaiting: + if (tsdPtr->runLoopTimer && !tsdPtr->runLoopServicingEvents && + (tsdPtr->runLoopNestingLevel > 1 || !tsdPtr->runLoopRunning)) { + tsdPtr->runLoopServicingEvents = 1; + while (Tcl_ServiceAll() && tsdPtr->waitTime == 0) {} + tsdPtr->runLoopServicingEvents = 0; + } + break; + default: + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * OnOffWaitingList -- + * + * Add/remove the specified thread to/from the global waitingList + * and optionally signal the notifier. + * + * !!! Requires notifierLock to be held !!! + * + * Results: + * Boolean indicating whether the waitingList was changed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +OnOffWaitingList( + ThreadSpecificData *tsdPtr, + int onList, + int signalNotifier) +{ + int changeWaitingList; +#ifdef TCL_MAC_DEBUG_NOTIFIER + if(SpinLockTry(¬ifierLock)) { + Tcl_Panic("OnOffWaitingList: notifierLock unlocked"); + } +#endif + changeWaitingList = (!onList ^ !tsdPtr->onList); + if (changeWaitingList) { + if (onList) { + tsdPtr->nextPtr = waitingListPtr; + if (waitingListPtr) { + waitingListPtr->prevPtr = tsdPtr; + } + tsdPtr->prevPtr = NULL; + waitingListPtr = tsdPtr; + tsdPtr->onList = 1; } else { - waitingListPtr = tsdPtr->nextPtr; + 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 (tsdPtr->nextPtr) { - tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; + if (signalNotifier) { + write(triggerPipe, "", 1); } - tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; - tsdPtr->onList = 0; - write(triggerPipe, "", 1); + } + + return changeWaitingList; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_Sleep -- + * + * Delay execution for the specified number of milliseconds. + * + * Results: + * None. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_Sleep( + int ms) /* Number of milliseconds to sleep. */ +{ + Tcl_Time vdelay; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (ms <= 0) { + return; } /* - * Queue all detected file events before returning. + * TIP #233: Scale from virtual time to real-time. */ - for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); - filePtr = filePtr->nextPtr) { + vdelay.sec = ms / 1000; + vdelay.usec = (ms % 1000) * 1000; + tclScaleTimeProcPtr(&vdelay, tclTimeClientData); - mask = 0; - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.readable))) { - mask |= TCL_READABLE; + + if (tsdPtr->runLoop) { + CFTimeInterval waitTime; + CFRunLoopTimerRef runLoopTimer = tsdPtr->runLoopTimer; + CFAbsoluteTime nextTimerFire = 0, waitEnd, now; + SInt32 runLoopStatus; + + waitTime = vdelay.sec + 1.0e-6 * vdelay.usec; + now = CFAbsoluteTimeGetCurrent(); + waitEnd = now + waitTime; + + if (runLoopTimer) { + nextTimerFire = CFRunLoopTimerGetNextFireDate(runLoopTimer); + if (nextTimerFire < waitEnd) { + CFRunLoopTimerSetNextFireDate(runLoopTimer, now + + CF_TIMEINTERVAL_FOREVER); + } else { + runLoopTimer = NULL; + } } - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.writable))) { - mask |= TCL_WRITABLE; + tsdPtr->sleeping = 1; + do { + runLoopStatus = CFRunLoopRunInMode(kCFRunLoopDefaultMode, waitTime, + FALSE); + switch (runLoopStatus) { + case kCFRunLoopRunFinished: + Tcl_Panic("Tcl_Sleep: CFRunLoop finished"); + break; + case kCFRunLoopRunStopped: + TclMacOSXNotifierDbgMsg("CFRunLoop stopped"); + waitTime = waitEnd - CFAbsoluteTimeGetCurrent(); + break; + case kCFRunLoopRunTimedOut: + waitTime = 0; + break; + } + } while (waitTime > 0); + tsdPtr->sleeping = 0; + if (runLoopTimer) { + CFRunLoopTimerSetNextFireDate(runLoopTimer, nextTimerFire); } - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.exceptional))) { - mask |= TCL_EXCEPTION; + } else { + struct timespec waitTime; + + waitTime.tv_sec = vdelay.sec; + waitTime.tv_nsec = vdelay.usec * 1000; + while (nanosleep(&waitTime, &waitTime)); + } +} + +/* + *---------------------------------------------------------------------- + * + * 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; + int numFound, result = 0; + fd_set readableMask; + fd_set writableMask; + fd_set exceptionalMask; + +#define SET_BITS(var, bits) ((var) |= (bits)) +#define CLEAR_BITS(var, bits) ((var) &= ~(bits)) + +#ifndef _DARWIN_C_SOURCE + /* + * Sanity check fd. + */ + + if (fd >= FD_SETSIZE) { + Tcl_Panic("TclUnixWaitForFile can't handle file id %d", fd); + /* must never get here, or select masks overrun will occur below */ + } +#endif + + /* + * 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; + } - if (!mask) { + /* + * Initialize the select masks. + */ + + FD_ZERO(&readableMask); + FD_ZERO(&writableMask); + FD_ZERO(&exceptionalMask); + + /* + * 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; + } + } + + /* + * Setup the select masks for the fd. + */ + + if (mask & TCL_READABLE) { + FD_SET(fd, &readableMask); + } + if (mask & TCL_WRITABLE) { + FD_SET(fd, &writableMask); + } + if (mask & TCL_EXCEPTION) { + FD_SET(fd, &exceptionalMask); + } + + /* + * Wait for the event or a timeout. + */ + + numFound = select(fd + 1, &readableMask, &writableMask, + &exceptionalMask, timeoutPtr); + if (numFound == 1) { + if (FD_ISSET(fd, &readableMask)) { + SET_BITS(result, TCL_READABLE); + } + if (FD_ISSET(fd, &writableMask)) { + SET_BITS(result, TCL_WRITABLE); + } + if (FD_ISSET(fd, &exceptionalMask)) { + SET_BITS(result, TCL_EXCEPTION); + } + result &= mask; + if (result) { + break; + } + } + if (timeout == 0) { + break; + } + if (timeout < 0) { continue; } /* - * Don't bother to queue an event if the mask was previously non-zero - * since an event must still be on the queue. + * The select returned early, so we need to recompute the timeout. */ - 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); + Tcl_GetTime(&now); + if ((abortTime.sec < now.sec) + || (abortTime.sec==now.sec && abortTime.usec<=now.usec)) { + break; } - filePtr->readyMask = mask; } - UNLOCK_NOTIFIER; - return 0; + return result; } /* @@ -1046,11 +1721,8 @@ NotifierThreadProc( ClientData clientData) /* Not used. */ { ThreadSpecificData *tsdPtr; - fd_set readableMask; - fd_set writableMask; - fd_set exceptionalMask; - int i, numFdBits = 0; - long found; + fd_set readableMask, writableMask, exceptionalMask; + int i, numFdBits = 0, polling; struct timeval poll = {0., 0.}, *timePtr; char buf[2]; @@ -1068,9 +1740,10 @@ NotifierThreadProc( * notifiers. */ - LOCK_NOTIFIER; timePtr = NULL; + LOCK_NOTIFIER; for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { + LOCK_NOTIFIER_TSD; for (i = tsdPtr->numFdBits-1; i >= 0; --i) { if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))) { FD_SET(i, &readableMask); @@ -1085,13 +1758,9 @@ NotifierThreadProc( 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; + polling = tsdPtr->polling; + UNLOCK_NOTIFIER_TSD; + if ((tsdPtr->polled = polling)) { timePtr = &poll; } } @@ -1121,48 +1790,53 @@ NotifierThreadProc( LOCK_NOTIFIER; for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { - found = 0; + int found = 0; + SelectMasks readyMasks, checkMasks; + + LOCK_NOTIFIER_TSD; + FD_COPY(&(tsdPtr->checkMasks.readable), &checkMasks.readable); + FD_COPY(&(tsdPtr->checkMasks.writable), &checkMasks.writable); + FD_COPY(&(tsdPtr->checkMasks.exceptional), &checkMasks.exceptional); + UNLOCK_NOTIFIER_TSD; + found = tsdPtr->polled; + FD_ZERO(&readyMasks.readable); + FD_ZERO(&readyMasks.writable); + FD_ZERO(&readyMasks.exceptional); for (i = tsdPtr->numFdBits-1; i >= 0; --i) { - if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) + if (FD_ISSET(i, &checkMasks.readable) && FD_ISSET(i, &readableMask)) { - FD_SET(i, &(tsdPtr->readyMasks.readable)); + FD_SET(i, &readyMasks.readable); found = 1; } - if (FD_ISSET(i, &(tsdPtr->checkMasks.writable)) + if (FD_ISSET(i, &checkMasks.writable) && FD_ISSET(i, &writableMask)) { - FD_SET(i, &(tsdPtr->readyMasks.writable)); + FD_SET(i, &readyMasks.writable); found = 1; } - if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional)) + if (FD_ISSET(i, &checkMasks.exceptional) && FD_ISSET(i, &exceptionalMask)) { - FD_SET(i, &(tsdPtr->readyMasks.exceptional)); + FD_SET(i, &readyMasks.exceptional); 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; - } + if (found) { + /* + * Remove the ThreadSpecificData structure of this thread from + * the waiting list. This prevents us from spinning + * continuously on select until the other threads runs and + * services the file event. + */ + + OnOffWaitingList(tsdPtr, 0, 0); + + LOCK_NOTIFIER_TSD; + FD_COPY(&readyMasks.readable, &(tsdPtr->readyMasks.readable)); + FD_COPY(&readyMasks.writable, &(tsdPtr->readyMasks.writable)); + FD_COPY(&readyMasks.exceptional, &(tsdPtr->readyMasks.exceptional)); + UNLOCK_NOTIFIER_TSD; + tsdPtr->polled = 0; if (tsdPtr->runLoop) { CFRunLoopSourceSignal(tsdPtr->runLoopSource); CFRunLoopWakeUp(tsdPtr->runLoop); @@ -1214,8 +1888,11 @@ NotifierThreadProc( static void AtForkPrepare(void) { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + LOCK_NOTIFIER_INIT; LOCK_NOTIFIER; + LOCK_NOTIFIER_TSD; } /* @@ -1237,6 +1914,9 @@ AtForkPrepare(void) static void AtForkParent(void) { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + UNLOCK_NOTIFIER_TSD; UNLOCK_NOTIFIER; UNLOCK_NOTIFIER_INIT; } @@ -1262,18 +1942,25 @@ AtForkChild(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + UNLOCK_NOTIFIER_TSD; UNLOCK_NOTIFIER; UNLOCK_NOTIFIER_INIT; if (tsdPtr->runLoop) { tsdPtr->runLoop = NULL; if (!noCFafterFork) { CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); + CFRelease(tsdPtr->runLoopSource); + if (tsdPtr->runLoopTimer) { + CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer); + CFRelease(tsdPtr->runLoopTimer); + } } - CFRelease(tsdPtr->runLoopSource); tsdPtr->runLoopSource = NULL; + tsdPtr->runLoopTimer = NULL; } if (notifierCount > 0) { - notifierCount = 0; + notifierCount = 1; + notifierThreadRunning = 0; /* * Assume that the return value of Tcl_InitNotifier in the child will @@ -1291,4 +1978,22 @@ AtForkChild(void) } #endif /* HAVE_PTHREAD_ATFORK */ +#else /* HAVE_COREFOUNDATION */ + +void +TclMacOSXNotifierAddRunLoopMode( + CONST void *runLoopMode) +{ + Tcl_Panic("TclMacOSXNotifierAddRunLoopMode: " + "Tcl not built with CoreFoundation support"); +} + #endif /* HAVE_COREFOUNDATION */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |