summaryrefslogtreecommitdiffstats
path: root/macosx/tclMacOSXNotify.c
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/tclMacOSXNotify.c')
-rw-r--r--macosx/tclMacOSXNotify.c1425
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(&notifierInitLock)
+#define UNLOCK_NOTIFIER_INIT SpinLockUnlock(&notifierInitLock)
+#define LOCK_NOTIFIER SpinLockLock(&notifierLock)
+#define UNLOCK_NOTIFIER SpinLockUnlock(&notifierLock)
+#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(&notifierInitLock)
+#undef LOCK_NOTIFIER
+#define LOCK_NOTIFIER SpinLockLockDbg(&notifierLock)
+#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(&notifierInitLock)
-#define UNLOCK_NOTIFIER_INIT SpinLockUnlock(&notifierInitLock)
-#define LOCK_NOTIFIER SpinLockLock(&notifierLock)
-#define UNLOCK_NOTIFIER SpinLockUnlock(&notifierLock)
-
-/*
- * 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(&notifierThread, &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(&notifierThread, &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(&notifierLock)) {
+ 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:
+ */