diff options
Diffstat (limited to 'macosx/tclMacOSXNotify.c')
-rw-r--r-- | macosx/tclMacOSXNotify.c | 1589 |
1 files changed, 1278 insertions, 311 deletions
diff --git a/macosx/tclMacOSXNotify.c b/macosx/tclMacOSXNotify.c index a5e9627..ef80192 100644 --- a/macosx/tclMacOSXNotify.c +++ b/macosx/tclMacOSXNotify.c @@ -6,14 +6,11 @@ * This file works together with generic/tclNotify.c. * * Copyright (c) 1995-1997 Sun Microsystems, Inc. - * Copyright 2001, Apple Computer, Inc. - * Copyright (c) 2005 Tcl Core Team. - * Copyright (c) 2005 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. - * - * RCS: @(#) $Id: tclMacOSXNotify.c,v 1.6 2005/11/27 02:33:49 das Exp $ */ #include "tclInt.h" @@ -22,8 +19,180 @@ #include <CoreFoundation/CoreFoundation.h> #include <pthread.h> -extern TclStubs tclStubs; -extern Tcl_NotifierProcs tclOriginalNotifier; +/* #define TCL_MAC_DEBUG_NOTIFIER 1 */ + +/* + * 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 /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 */ +#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_DECL /*aslclient asl*/ +#else +#define TclMacOSXNotifierDbgMsg(m, ...) +#define OPEN_NOTIFIER_LOG +#define CLOSE_NOTIFIER_LOG +#define ENABLE_ASL +#define DISABLE_ASL +#define ASLCLIENT_DECL +#endif /* TCL_MAC_DEBUG_NOTIFIER */ /* * This structure is used to keep track of the notifier info for a registered @@ -78,6 +247,32 @@ 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 @@ -88,26 +283,26 @@ 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. */ + ASLCLIENT_DECL; } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; @@ -149,73 +344,87 @@ static int triggerPipe = -1; static int receivePipe = -1; /* Output end of triggerPipe */ /* - * We use Darwin-native spinlocks instead of 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, making them immune to most priority-inversion livelocks (c.f. man - * 3 OSSpinLockLock). - */ - -#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) -/* - * Use OSSpinLock API where available (Tiger or later). - */ - -#include <libkern/OSAtomic.h> - -#else -/* - * Otherwise, use commpage spinlock SPI directly. + * The following static indicates if the notifier thread is running. + * + * You must hold the notifierInitLock before accessing this variable. */ -typedef uint32_t OSSpinLock; -extern void _spin_lock(OSSpinLock *lock); -extern void _spin_unlock(OSSpinLock *lock); -#define OSSpinLockLock(p) _spin_lock(p) -#define OSSpinLockUnlock(p) _spin_unlock(p) - -#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ +static int notifierThreadRunning; /* - * These spinlocks lock access to the global notifier state. + * 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 OSSpinLock notifierInitLock = 0; -static OSSpinLock notifierLock = 0; +static pthread_t notifierThread; /* - * Macros abstracting notifier locking/unlocking + * Custom runloop mode for running with only the runloop source for the + * notifier thread. */ -#define LOCK_NOTIFIER_INIT OSSpinLockLock(¬ifierInitLock) -#define UNLOCK_NOTIFIER_INIT OSSpinLockUnlock(¬ifierInitLock) -#define LOCK_NOTIFIER OSSpinLockLock(¬ifierLock) -#define UNLOCK_NOTIFIER OSSpinLockUnlock(¬ifierLock) +#ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE +#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" +#endif +#ifdef __CONSTANT_CFSTRINGS__ +#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) +#else +static CFStringRef tclEventsOnlyRunLoopMode = NULL; +#endif /* - * 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. + * CFTimeInterval to wait forever. */ -#define POLL_WANT 0x1 -#define POLL_DONE 0x2 +#define CF_TIMEINTERVAL_FOREVER 5.05e8 /* - * This is the thread ID of the notifier thread that does select. + * Static routines defined in this file. */ -static pthread_t notifierThread; +static void StartNotifierThread(void); +static void NotifierThreadProc(ClientData clientData) + __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; +static void AtForkPrepare(void); +static void AtForkParent(void); +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; +#define MayUsePthreadAtfork() (pthread_atfork != NULL) +#else +#define MayUsePthreadAtfork() (1) +#endif /* HAVE_WEAK_IMPORT */ /* - * Static routines defined in this file. + * On Darwin 9 and later, it is not possible to call CoreFoundation after + * a fork. */ -static void NotifierThreadProc(ClientData clientData); -static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); +#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \ + MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +MODULE_SCOPE long tclMacOSXDarwinRelease; +#define noCFafterFork (tclMacOSXDarwinRelease >= 9) +#else /* MAC_OS_X_VERSION_MIN_REQUIRED */ +#define noCFafterFork 1 +#endif /* MAC_OS_X_VERSION_MIN_REQUIRED */ +#endif /* HAVE_PTHREAD_ATFORK */ /* *---------------------------------------------------------------------- @@ -225,7 +434,7 @@ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); * Initializes the platform specific notifier state. * * Results: - * Returns a handle to the notifier state for this thread.. + * Returns a handle to the notifier state for this thread. * * Side effects: * None. @@ -236,57 +445,211 @@ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); ClientData Tcl_InitNotifier(void) { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; + + if (tclNotifierHooks.initNotifierProc) { + return tclNotifierHooks.initNotifierProc(); + } - 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"); + } +#endif + +#ifndef __CONSTANT_CFSTRINGS__ + if (!tclEventsOnlyRunLoopMode) { + tclEventsOnlyRunLoopMode = CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE); + } +#endif /* - * Initialize CFRunLoopSource and add it to CFRunLoop of this thread + * Initialize CFRunLoopSource and add it to CFRunLoop of this thread. */ if (!tsdPtr->runLoop) { 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."); + Tcl_Panic("Tcl_InitNotifier: could not create CFRunLoopSource"); } CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopCommonModes); - tsdPtr->runLoopSource = runLoopSource; + CFRunLoopAddSource(runLoop, runLoopSource, tclEventsOnlyRunLoopMode); + + 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; +#ifdef HAVE_PTHREAD_ATFORK /* - * Initialize trigger pipe and start the Notifier thread if necessary. + * Install pthread_atfork handlers to reinitialize the notifier in the + * child of a fork. */ - LOCK_NOTIFIER_INIT; + if (MayUsePthreadAtfork() && !atForkInit) { + int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild); + + if (result) { + Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed"); + } + atForkInit = 1; + } +#endif /* HAVE_PTHREAD_ATFORK */ if (notifierCount == 0) { - int fds[2], status, result; - pthread_attr_t attr; + int fds[2], status; + + /* + * Initialize trigger pipe. + */ if (pipe(fds) != 0) { - Tcl_Panic("Tcl_InitNotifier: could not create trigger pipe."); + Tcl_Panic("Tcl_InitNotifier: could not create trigger pipe"); } 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]; triggerPipe = fds[1]; + /* + * Create notifier thread lazily in Tcl_WaitForEvent() to avoid + * interfering with fork() followed immediately by execve() (we cannot + * execve() when more than one thread is present). + */ + + notifierThreadRunning = 0; + OPEN_NOTIFIER_LOG; + } + ENABLE_ASL; + notifierCount++; + UNLOCK_NOTIFIER_INIT; + + return tsdPtr; +} + +/* + *---------------------------------------------------------------------- + * + * 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); @@ -295,14 +658,13 @@ Tcl_InitNotifier(void) (void * (*)(void *))NotifierThreadProc, NULL); pthread_attr_destroy(&attr); if (result) { - Tcl_Panic("Tcl_InitNotifier: unable to start notifier thread."); + Tcl_Panic("StartNotifierThread: unable to start notifier thread"); } + notifierThreadRunning = 1; } - notifierCount++; UNLOCK_NOTIFIER_INIT; - - return (ClientData) tsdPtr; } + /* *---------------------------------------------------------------------- @@ -326,10 +688,18 @@ void Tcl_FinalizeNotifier( ClientData clientData) /* Not used. */ { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; + + if (tclNotifierHooks.finalizeNotifierProc) { + tclNotifierHooks.finalizeNotifierProc(clientData); + return; + } + + 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 @@ -337,49 +707,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 (result) { + Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier " + "thread"); + } + notifierThreadRunning = 0; + } - result = pthread_join(notifierThread, NULL); - if (result) { - Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier thread."); + 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; } /* @@ -405,15 +789,19 @@ void Tcl_AlertNotifier( ClientData clientData) { - ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; + ThreadSpecificData *tsdPtr = clientData; - LOCK_NOTIFIER; + if (tclNotifierHooks.alertNotifierProc) { + tclNotifierHooks.alertNotifierProc(clientData); + return; + } + + LOCK_NOTIFIER_TSD; if (tsdPtr->runLoop) { - tsdPtr->eventReady = 1; CFRunLoopSourceSignal(tsdPtr->runLoopSource); CFRunLoopWakeUp(tsdPtr->runLoop); } - UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_TSD; } /* @@ -421,32 +809,73 @@ 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. * *---------------------------------------------------------------------- */ void Tcl_SetTimer( - Tcl_Time *timePtr) /* Timeout value, may be NULL. */ + const 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 (tclNotifierHooks.setTimerProc) { + tclNotifierHooks.setTimerProc(timePtr); + return; + } - if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { - tclStubs.tcl_SetTimer(timePtr); + 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) +{ } /* @@ -470,6 +899,28 @@ Tcl_ServiceModeHook( int mode) /* Either TCL_SERVICE_ALL, or * TCL_SERVICE_NONE. */ { + ThreadSpecificData *tsdPtr; + + if (tclNotifierHooks.serviceModeHookProc) { + tclNotifierHooks.serviceModeHookProc(mode); + return; + } + + 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(); + } + } } /* @@ -499,15 +950,16 @@ 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 != - tclOriginalNotifier.createFileHandlerProc) { - tclStubs.tcl_CreateFileHandler(fd, mask, proc, clientData); + if (tclNotifierHooks.createFileHandlerProc) { + tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData); return; } + tsdPtr = TCL_TSD_INIT(&dataKey); + for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; filePtr = filePtr->nextPtr) { if (filePtr->fd == fd) { @@ -515,7 +967,7 @@ Tcl_CreateFileHandler( } } if (filePtr == NULL) { - filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); + filePtr = ckalloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->readyMask = 0; filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; @@ -529,24 +981,26 @@ Tcl_CreateFileHandler( * Update the check masks for this file. */ + LOCK_NOTIFIER_TSD; if (mask & TCL_READABLE) { - FD_SET(fd, &(tsdPtr->checkMasks.readable)); + FD_SET(fd, &tsdPtr->checkMasks.readable); } else { - FD_CLR(fd, &(tsdPtr->checkMasks.readable)); + FD_CLR(fd, &tsdPtr->checkMasks.readable); } if (mask & TCL_WRITABLE) { - FD_SET(fd, &(tsdPtr->checkMasks.writable)); + FD_SET(fd, &tsdPtr->checkMasks.writable); } else { - FD_CLR(fd, &(tsdPtr->checkMasks.writable)); + FD_CLR(fd, &tsdPtr->checkMasks.writable); } if (mask & TCL_EXCEPTION) { - FD_SET(fd, &(tsdPtr->checkMasks.exceptional)); + FD_SET(fd, &tsdPtr->checkMasks.exceptional); } else { - FD_CLR(fd, &(tsdPtr->checkMasks.exceptional)); + FD_CLR(fd, &tsdPtr->checkMasks.exceptional); } if (tsdPtr->numFdBits <= fd) { tsdPtr->numFdBits = fd+1; } + UNLOCK_NOTIFIER_TSD; } /* @@ -571,21 +1025,23 @@ 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) { - tclStubs.tcl_DeleteFileHandler(fd); + if (tclNotifierHooks.deleteFileHandlerProc) { + tclNotifierHooks.deleteFileHandlerProc(fd); 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; } @@ -595,35 +1051,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; + if (FD_ISSET(i, &tsdPtr->checkMasks.readable) + || FD_ISSET(i, &tsdPtr->checkMasks.writable) + || FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) { + 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. */ @@ -633,7 +1095,7 @@ Tcl_DeleteFileHandler( } else { prevPtr->nextPtr = filePtr->nextPtr; } - ckfree((char *) filePtr); + ckfree(filePtr); } /* @@ -702,7 +1164,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; } @@ -719,167 +1192,540 @@ 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. * *---------------------------------------------------------------------- */ int Tcl_WaitForEvent( - Tcl_Time *timePtr) /* Maximum block time, or NULL. */ + const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { - FileHandler *filePtr; - FileHandlerEvent *fileEvPtr; - int mask; - Tcl_Time myTime; - int waitForFiles; - Tcl_Time *myTimePtr; - 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); + if (tclNotifierHooks.waitForEventProc) { + return tclNotifierHooks.waitForEventProc(timePtr); } + result = -1; + polling = 0; + waitTime = CF_TIMEINTERVAL_FOREVER; + tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->runLoop) { + Tcl_Panic("Tcl_WaitForEvent: Notifier not initialized"); + } + + if (timePtr) { + Tcl_Time vTime = *timePtr; - if (timePtr != NULL) { /* * TIP #233 (Virtualized Time). Is virtual time in effect? And do we * actually have something to scale? If yes to both then we call the * handler to do this scaling. */ - myTime.sec = timePtr->sec; - myTime.usec = timePtr->usec; + 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. + */ - if (myTime.sec != 0 || myTime.usec != 0) { - (*tclScaleTimeProcPtr) (&myTime, tclTimeClientData); + polling = 1; } + } - myTimePtr = &myTime; - } else { - myTimePtr = NULL; + StartNotifierThread(); + + LOCK_NOTIFIER_TSD; + tsdPtr->polling = polling; + UNLOCK_NOTIFIER_TSD; + tsdPtr->runLoopSourcePerformed = 0; + + /* + * 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. + */ + + 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; } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * QueueFileEvents -- + * + * CFRunLoopSource callback for queueing file events. + * + * Results: + * None. + * + * Side effects: + * Queues file events that are detected by the select. + * + *---------------------------------------------------------------------- + */ + +static void +QueueFileEvents( + void *info) +{ + SelectMasks readyMasks; + FileHandler *filePtr; + ThreadSpecificData *tsdPtr = info; + /* - * Place this thread on the list of interested threads, signal the - * notifier thread, and wait for a response or a timeout. + * Queue all detected file events. */ - LOCK_NOTIFIER; + 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; - waitForFiles = (tsdPtr->numFdBits > 0); - if (myTimePtr != NULL && myTimePtr->sec == 0 && myTimePtr->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. - */ + for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); + filePtr = filePtr->nextPtr) { + int mask = 0; - waitForFiles = 1; - tsdPtr->pollState = POLL_WANT; - myTimePtr = NULL; - } else { - tsdPtr->pollState = 0; - } + 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 (waitForFiles) { /* - * Add the ThreadSpecificData structure of this thread to the list of - * ThreadSpecificData structures of all threads that are waiting on - * file events. + * Don't bother to queue an event if the mask was previously non-zero + * since an event must still be on the queue. */ - tsdPtr->nextPtr = waitingListPtr; - if (waitingListPtr) { - waitingListPtr->prevPtr = tsdPtr; - } - tsdPtr->prevPtr = 0; - waitingListPtr = tsdPtr; - tsdPtr->onList = 1; + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = ckalloc(sizeof(FileHandlerEvent)); - write(triggerPipe, "", 1); + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + } + filePtr->readyMask = mask; } +} + +/* + *---------------------------------------------------------------------- + * + * UpdateWaitingListAndServiceEvents -- + * + * CFRunLoopObserver callback for updating waitingList and + * servicing Tcl events. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ - FD_ZERO(&(tsdPtr->readyMasks.readable)); - FD_ZERO(&(tsdPtr->readyMasks.writable)); - FD_ZERO(&(tsdPtr->readyMasks.exceptional)); +static void +UpdateWaitingListAndServiceEvents( + CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void *info) +{ + ThreadSpecificData *tsdPtr = info; - if (!tsdPtr->eventReady) { - CFTimeInterval waitTime; + 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. + * + *---------------------------------------------------------------------- + */ - if (myTimePtr == NULL) { - waitTime = 1.0e10; /* Wait forever, as per CFRunLoop.c */ +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 { - waitTime = myTimePtr->sec + 1.0e-6 * myTimePtr->usec; + 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 (signalNotifier) { + write(triggerPipe, "", 1); } - UNLOCK_NOTIFIER; - CFRunLoopRunInMode(kCFRunLoopDefaultMode, waitTime, TRUE); - LOCK_NOTIFIER; } - tsdPtr->eventReady = 0; - 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. - */ + return changeWaitingList; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_Sleep -- + * + * Delay execution for the specified number of milliseconds. + * + * Results: + * None. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ - if (tsdPtr->prevPtr) { - tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; - } else { - waitingListPtr = tsdPtr->nextPtr; +void +Tcl_Sleep( + int ms) /* Number of milliseconds to sleep. */ +{ + Tcl_Time vdelay; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (ms <= 0) { + return; + } + + /* + * TIP #233: Scale from virtual time to real-time. + */ + + vdelay.sec = ms / 1000; + vdelay.usec = (ms % 1000) * 1000; + tclScaleTimeProcPtr(&vdelay, tclTimeClientData); + + + 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 (tsdPtr->nextPtr) { - tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; + 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); } - tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; - tsdPtr->onList = 0; - write(triggerPipe, "", 1); + } 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 /* - * Queue all detected file events before returning. + * Sanity check fd. */ - for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); - filePtr = filePtr->nextPtr) { + 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 - mask = 0; - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.readable))) { - mask |= TCL_READABLE; + /* + * 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; } - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.writable))) { - mask |= TCL_WRITABLE; + timeoutPtr = &blockTime; + } else if (timeout == 0) { + timeoutPtr = &blockTime; + blockTime.tv_sec = 0; + blockTime.tv_usec = 0; + } else { + timeoutPtr = NULL; + } + + /* + * 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; + } } - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.exceptional))) { - mask |= TCL_EXCEPTION; + + /* + * 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); } - if (!mask) { + /* + * 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; } /* @@ -911,11 +1757,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]; @@ -933,30 +1776,27 @@ 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))) { + if (FD_ISSET(i, &tsdPtr->checkMasks.readable)) { FD_SET(i, &readableMask); } - if (FD_ISSET(i, &(tsdPtr->checkMasks.writable))) { + if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) { FD_SET(i, &writableMask); } - if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) { + if (FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) { FD_SET(i, &exceptionalMask); } } 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; } } @@ -986,48 +1826,54 @@ 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); @@ -1056,8 +1902,129 @@ NotifierThreadProc( } } } - pthread_exit (0); + pthread_exit(0); } + +#ifdef HAVE_PTHREAD_ATFORK +/* + *---------------------------------------------------------------------- + * + * AtForkPrepare -- + * + * Lock the notifier in preparation for a fork. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +AtForkPrepare(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + LOCK_NOTIFIER_INIT; + LOCK_NOTIFIER; + LOCK_NOTIFIER_TSD; +} + +/* + *---------------------------------------------------------------------- + * + * AtForkParent -- + * + * Unlock the notifier in the parent after a fork. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +AtForkParent(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + UNLOCK_NOTIFIER_TSD; + UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_INIT; +} + +/* + *---------------------------------------------------------------------- + * + * AtForkChild -- + * + * Unlock and reinstall the notifier in the child after a fork. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +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); + } + } + tsdPtr->runLoopSource = NULL; + tsdPtr->runLoopTimer = NULL; + } + if (notifierCount > 0) { + notifierCount = 1; + notifierThreadRunning = 0; + + /* + * Assume that the return value of Tcl_InitNotifier in the child will + * be identical to the one stored as clientData in tclNotify.c's + * ThreadSpecificData by the parent's TclInitNotifier, so discard the + * return value here. This assumption may require the fork() to be + * executed in the main thread of the parent, otherwise + * Tcl_AlertNotifier may break in the child. + */ + + if (!noCFafterFork) { + Tcl_InitNotifier(); + } + } +} +#endif /* HAVE_PTHREAD_ATFORK */ + +#else /* HAVE_COREFOUNDATION */ + +void +TclMacOSXNotifierAddRunLoopMode( + const void *runLoopMode) +{ + Tcl_Panic("TclMacOSXNotifierAddRunLoopMode: " + "Tcl not built with CoreFoundation support"); +} + #endif /* HAVE_COREFOUNDATION */ /* |