+++ /dev/null
-/*
- * tclMacOSXNotify.c --
- *
- * This file contains the implementation of a merged CFRunLoop/select()
- * based notifier, which is the lowest-level part of the Tcl event loop.
- * This file works together with generic/tclNotify.c.
- *
- * Copyright (c) 1995-1997 Sun Microsystems, Inc.
- * 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"
-#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 */
-
-/*
- * 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
- * file.
- */
-
-typedef struct FileHandler {
- int fd;
- int mask; /* Mask of desired events: TCL_READABLE,
- * etc. */
- int readyMask; /* Mask of events that have been seen since
- * the last time file handlers were invoked
- * for this file. */
- Tcl_FileProc *proc; /* Function to call, in the style of
- * Tcl_CreateFileHandler. */
- ClientData clientData; /* Argument to pass to proc. */
- struct FileHandler *nextPtr;/* Next in list of all files we care about. */
-} FileHandler;
-
-/*
- * The following structure is what is added to the Tcl event queue when file
- * handlers are ready to fire.
- */
-
-typedef struct FileHandlerEvent {
- Tcl_Event header; /* Information that is standard for all
- * events. */
- int fd; /* File descriptor that is ready. Used to find
- * the FileHandler structure for the file
- * (can't point directly to the FileHandler
- * structure because it could go away while
- * the event is queued). */
-} FileHandlerEvent;
-
-/*
- * The following structure contains a set of select() masks to track readable,
- * writable, and exceptional conditions.
- */
-
-typedef struct SelectMasks {
- fd_set readable;
- fd_set writable;
- fd_set exceptional;
-} SelectMasks;
-
-/*
- * The following static structure contains the state information for the
- * select based implementation of the Tcl notifier. One of these structures is
- * created for each thread that is using the notifier.
- */
-
-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
- * to Tcl_CreateFileHandler. */
- SelectMasks readyMasks; /* This array reflects the readable/writable
- * conditions that were found to exist by the
- * last call to select. */
- int numFdBits; /* Number of valid bits in checkMasks (one
- * more than highest fd for which
- * Tcl_WatchFile has been called). */
- 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. */
- 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;
-
-/*
- * The following static indicates the number of threads that have initialized
- * notifiers.
- *
- * You must hold the notifierInitLock before accessing this variable.
- */
-
-static int notifierCount = 0;
-
-/*
- * The following variable points to the head of a doubly-linked list of
- * ThreadSpecificData structures for all threads that are currently waiting on
- * an event.
- *
- * You must hold the notifierLock before accessing this list.
- */
-
-static ThreadSpecificData *waitingListPtr = NULL;
-
-/*
- * The notifier thread spends all its time in select() waiting for a file
- * descriptor associated with one of the threads on the waitingListPtr list to
- * do something interesting. But if the contents of the waitingListPtr list
- * ever changes, we need to wake up and restart the select() system call. You
- * can wake up the notifier thread by writing a single byte to the file
- * descriptor defined below. This file descriptor is the input-end of a pipe
- * and the notifier thread is listening for data on the output-end of the same
- * pipe. Hence writing to this file descriptor will cause the select() system
- * call to return and wake up the notifier thread.
- *
- * You must hold the notifierLock lock before writing to the pipe.
- */
-
-static int triggerPipe = -1;
-static int receivePipe = -1; /* Output end of triggerPipe */
-
-/*
- * The following static indicates if the notifier thread is running.
- *
- * You must hold the notifierInitLock before accessing this variable.
- */
-
-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 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"
-#endif
-#ifdef __CONSTANT_CFSTRINGS__
-#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__));
-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 */
-
-/*
- * On Darwin 9 and later, it is not possible to call CoreFoundation after
- * a fork.
- */
-
-#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 */
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_InitNotifier --
- *
- * Initializes the platform specific notifier state.
- *
- * Results:
- * Returns a handle to the notifier state for this thread.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-ClientData
-Tcl_InitNotifier(void)
-{
- ThreadSpecificData *tsdPtr;
-
- if (tclNotifierHooks.initNotifierProc) {
- return tclNotifierHooks.initNotifierProc();
- }
-
- 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.
- */
-
- if (!tsdPtr->runLoop) {
- CFRunLoopRef runLoop = CFRunLoopGetCurrent();
- CFRunLoopSourceRef runLoopSource;
- CFRunLoopSourceContext runLoopSourceContext;
- CFRunLoopObserverContext runLoopObserverContext;
- CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl;
-
- bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext));
- runLoopSourceContext.info = tsdPtr;
- 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);
-
- 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
- /*
- * Install pthread_atfork handlers to reinitialize the notifier in the
- * child of a fork.
- */
-
- 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;
-
- /*
- * Initialize trigger pipe.
- */
-
- if (pipe(fds) != 0) {
- 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");
- }
- 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");
- }
-
- 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;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * 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);
- }
- }
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * StartNotifierThread --
- *
- * Start notifier thread if necessary.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-StartNotifierThread(void)
-{
- LOCK_NOTIFIER_INIT;
- if (!notifierCount) {
- Tcl_Panic("StartNotifierThread: notifier not initialized");
- }
- if (!notifierThreadRunning) {
- int result;
- pthread_attr_t attr;
-
- pthread_attr_init(&attr);
- pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
- pthread_attr_setstacksize(&attr, 60 * 1024);
- result = pthread_create(¬ifierThread, &attr,
- (void * (*)(void *))NotifierThreadProc, NULL);
- pthread_attr_destroy(&attr);
- if (result) {
- Tcl_Panic("StartNotifierThread: unable to start notifier thread");
- }
- notifierThreadRunning = 1;
- }
- UNLOCK_NOTIFIER_INIT;
-}
-
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_FinalizeNotifier --
- *
- * This function is called to cleanup the notifier state before a thread
- * is terminated.
- *
- * Results:
- * None.
- *
- * Side effects:
- * May terminate the background notifier thread if this is the last
- * notifier instance.
- *
- *----------------------------------------------------------------------
- */
-
-void
-Tcl_FinalizeNotifier(
- ClientData clientData) /* Not used. */
-{
- 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
- * and wait for the background thread to terminate.
- */
-
- if (notifierCount == 0) {
- 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]
- */
-
- write(triggerPipe, "q", 1);
- close(triggerPipe);
-
- if (notifierThreadRunning) {
- int result = pthread_join(notifierThread, NULL);
-
- if (result) {
- Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier "
- "thread");
- }
- notifierThreadRunning = 0;
- }
-
- close(receivePipe);
- triggerPipe = -1;
- }
- CLOSE_NOTIFIER_LOG;
- }
- UNLOCK_NOTIFIER_INIT;
-
- LOCK_NOTIFIER_TSD; /* For concurrency with Tcl_AlertNotifier */
- if (tsdPtr->runLoop) {
- tsdPtr->runLoop = NULL;
-
- /*
- * 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_TSD;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_AlertNotifier --
- *
- * Wake up the specified notifier from any thread. This routine is called
- * by the platform independent notifier code whenever the Tcl_ThreadAlert
- * routine is called. This routine is guaranteed not to be called on a
- * given notifier after Tcl_FinalizeNotifier is called for that notifier.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Signals the notifier condition variable for the specified notifier.
- *
- *----------------------------------------------------------------------
- */
-
-void
-Tcl_AlertNotifier(
- ClientData clientData)
-{
- ThreadSpecificData *tsdPtr = clientData;
-
- if (tclNotifierHooks.alertNotifierProc) {
- tclNotifierHooks.alertNotifierProc(clientData);
- return;
- }
-
- LOCK_NOTIFIER_TSD;
- if (tsdPtr->runLoop) {
- CFRunLoopSourceSignal(tsdPtr->runLoopSource);
- CFRunLoopWakeUp(tsdPtr->runLoop);
- }
- UNLOCK_NOTIFIER_TSD;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_SetTimer --
- *
- * This function sets the current notifier timer value.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Replaces any previous timer.
- *
- *----------------------------------------------------------------------
- */
-
-void
-Tcl_SetTimer(
- const Tcl_Time *timePtr) /* Timeout value, may be NULL. */
-{
- ThreadSpecificData *tsdPtr;
- CFRunLoopTimerRef runLoopTimer;
- CFTimeInterval waitTime;
-
- if (tclNotifierHooks.setTimerProc) {
- tclNotifierHooks.setTimerProc(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);
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * TimerWakeUp --
- *
- * CFRunLoopTimer callback.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-TimerWakeUp(
- CFRunLoopTimerRef timer,
- void *info)
-{
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_ServiceModeHook --
- *
- * This function is invoked whenever the service mode changes.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-void
-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();
- }
- }
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_CreateFileHandler --
- *
- * This function registers a file handler with the select notifier.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Creates a new file handler structure.
- *
- *----------------------------------------------------------------------
- */
-
-void
-Tcl_CreateFileHandler(
- int fd, /* Handle of stream to watch. */
- int mask, /* OR'ed combination of TCL_READABLE,
- * TCL_WRITABLE, and TCL_EXCEPTION: indicates
- * conditions under which proc should be
- * called. */
- Tcl_FileProc *proc, /* Function to call for each selected
- * event. */
- ClientData clientData) /* Arbitrary data to pass to proc. */
-{
- ThreadSpecificData *tsdPtr;
- FileHandler *filePtr;
-
- 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) {
- break;
- }
- }
- if (filePtr == NULL) {
- filePtr = ckalloc(sizeof(FileHandler));
- filePtr->fd = fd;
- filePtr->readyMask = 0;
- filePtr->nextPtr = tsdPtr->firstFileHandlerPtr;
- tsdPtr->firstFileHandlerPtr = filePtr;
- }
- filePtr->proc = proc;
- filePtr->clientData = clientData;
- filePtr->mask = mask;
-
- /*
- * Update the check masks for this file.
- */
-
- LOCK_NOTIFIER_TSD;
- if (mask & TCL_READABLE) {
- FD_SET(fd, &tsdPtr->checkMasks.readable);
- } else {
- FD_CLR(fd, &tsdPtr->checkMasks.readable);
- }
- if (mask & TCL_WRITABLE) {
- FD_SET(fd, &tsdPtr->checkMasks.writable);
- } else {
- FD_CLR(fd, &tsdPtr->checkMasks.writable);
- }
- if (mask & TCL_EXCEPTION) {
- FD_SET(fd, &tsdPtr->checkMasks.exceptional);
- } else {
- FD_CLR(fd, &tsdPtr->checkMasks.exceptional);
- }
- if (tsdPtr->numFdBits <= fd) {
- tsdPtr->numFdBits = fd+1;
- }
- UNLOCK_NOTIFIER_TSD;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_DeleteFileHandler --
- *
- * Cancel a previously-arranged callback arrangement for a file.
- *
- * Results:
- * None.
- *
- * Side effects:
- * If a callback was previously registered on file, remove it.
- *
- *----------------------------------------------------------------------
- */
-
-void
-Tcl_DeleteFileHandler(
- int fd) /* Stream id for which to remove callback
- * function. */
-{
- FileHandler *filePtr, *prevPtr;
- int i, numFdBits;
- ThreadSpecificData *tsdPtr;
-
- 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) {
- if (filePtr == NULL) {
- return;
- }
- if (filePtr->fd == fd) {
- break;
- }
- }
-
- /*
- * Find current max fd.
- */
-
- if (fd+1 == tsdPtr->numFdBits) {
- 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)) {
- 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.
- */
-
- if (prevPtr == NULL) {
- tsdPtr->firstFileHandlerPtr = filePtr->nextPtr;
- } else {
- prevPtr->nextPtr = filePtr->nextPtr;
- }
- ckfree(filePtr);
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * FileHandlerEventProc --
- *
- * This function is called by Tcl_ServiceEvent when a file event reaches
- * the front of the event queue. This function is responsible for
- * actually handling the event by invoking the callback for the file
- * handler.
- *
- * Results:
- * Returns 1 if the event was handled, meaning it should be removed from
- * the queue. Returns 0 if the event was not handled, meaning it should
- * stay on the queue. The only time the event isn't handled is if the
- * TCL_FILE_EVENTS flag bit isn't set.
- *
- * Side effects:
- * Whatever the file handler's callback function does.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-FileHandlerEventProc(
- Tcl_Event *evPtr, /* Event to service. */
- int flags) /* Flags that indicate what events to handle,
- * such as TCL_FILE_EVENTS. */
-{
- int mask;
- FileHandler *filePtr;
- FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr;
- ThreadSpecificData *tsdPtr;
-
- if (!(flags & TCL_FILE_EVENTS)) {
- return 0;
- }
-
- /*
- * Search through the file handlers to find the one whose handle matches
- * the event. We do this rather than keeping a pointer to the file handler
- * directly in the event, so that the handler can be deleted while the
- * event is queued without leaving a dangling pointer.
- */
-
- tsdPtr = TCL_TSD_INIT(&dataKey);
- for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
- filePtr = filePtr->nextPtr) {
- if (filePtr->fd != fileEvPtr->fd) {
- continue;
- }
-
- /*
- * The code is tricky for two reasons:
- * 1. The file handler's desired events could have changed since the
- * time when the event was queued, so AND the ready mask with the
- * desired mask.
- * 2. The file could have been closed and re-opened since the time
- * when the event was queued. This is why the ready mask is stored
- * in the file handler rather than the queued event: it will be
- * zeroed when a new file handler is created for the newly opened
- * file.
- */
-
- mask = filePtr->readyMask & filePtr->mask;
- filePtr->readyMask = 0;
- if (mask != 0) {
- 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;
- }
- return 1;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_WaitForEvent --
- *
- * This function is called by Tcl_DoOneEvent to wait for new events on
- * the message queue. If the block time is 0, then Tcl_WaitForEvent just
- * polls without blocking.
- *
- * Results:
- * Returns 0 if a tcl event or timeout ocurred and 1 if a non-tcl
- * CFRunLoop source was processed.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-int
-Tcl_WaitForEvent(
- const Tcl_Time *timePtr) /* Maximum block time, or NULL. */
-{
- int result, polling, runLoopRunning;
- CFTimeInterval waitTime;
- SInt32 runLoopStatus;
- ThreadSpecificData *tsdPtr;
-
- 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;
-
- /*
- * 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;
- }
- }
-
- 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;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * 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;
-
- /*
- * 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;
-
- for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL);
- filePtr = filePtr->nextPtr) {
- int mask = 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;
- }
-
- /*
- * Don't bother to queue an event if the mask was previously non-zero
- * since an event must still be on the queue.
- */
-
- if (filePtr->readyMask == 0) {
- FileHandlerEvent *fileEvPtr = ckalloc(sizeof(FileHandlerEvent));
-
- fileEvPtr->header.proc = FileHandlerEventProc;
- fileEvPtr->fd = filePtr->fd;
- Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
- }
- filePtr->readyMask = mask;
- }
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * UpdateWaitingListAndServiceEvents --
- *
- * CFRunLoopObserver callback for updating waitingList and
- * servicing Tcl events.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-UpdateWaitingListAndServiceEvents(
- CFRunLoopObserverRef observer,
- CFRunLoopActivity activity,
- void *info)
-{
- ThreadSpecificData *tsdPtr = info;
-
- 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;
- /* This call seems to simply force event processing through and prevents hangups that have long been observed with Tk-Cocoa. */
- Tcl_ServiceAll();
- tsdPtr->runLoopServicingEvents = 0;
- }
- break;
- default:
- break;
- }
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * OnOffWaitingList --
- *
- * Add/remove the specified thread to/from the global waitingList and
- * optionally signal the notifier.
- *
- * !!! Requires notifierLock to be held !!!
- *
- * Results:
- * Boolean indicating whether the waitingList was changed.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-OnOffWaitingList(
- ThreadSpecificData *tsdPtr,
- int onList,
- int signalNotifier)
-{
- int changeWaitingList;
-
-#ifdef TCL_MAC_DEBUG_NOTIFIER
- if (SpinLockTry(¬ifierLock)) {
- Tcl_Panic("OnOffWaitingList: notifierLock unlocked");
- }
-#endif
- changeWaitingList = (!onList ^ !tsdPtr->onList);
- if (changeWaitingList) {
- if (onList) {
- tsdPtr->nextPtr = waitingListPtr;
- if (waitingListPtr) {
- waitingListPtr->prevPtr = tsdPtr;
- }
- tsdPtr->prevPtr = NULL;
- waitingListPtr = tsdPtr;
- tsdPtr->onList = 1;
- } else {
- 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);
- }
- }
-
- return changeWaitingList;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * 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;
- }
-
- /*
- * 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;
- }
- }
- 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);
- }
- } else {
- struct timespec waitTime;
-
- waitTime.tv_sec = vdelay.sec;
- waitTime.tv_nsec = vdelay.usec * 1000;
- while (nanosleep(&waitTime, &waitTime));
- }
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * 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;
- }
-
- /*
- * 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;
- }
-
- /*
- * The select returned early, so we need to recompute the timeout.
- */
-
- Tcl_GetTime(&now);
- if ((abortTime.sec < now.sec)
- || (abortTime.sec==now.sec && abortTime.usec<=now.usec)) {
- break;
- }
- }
- return result;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * NotifierThreadProc --
- *
- * This routine is the initial (and only) function executed by the
- * special notifier thread. Its job is to wait for file descriptors to
- * become readable or writable or to have an exception condition and then
- * to notify other threads who are interested in this information by
- * signalling a condition variable. Other threads can signal this
- * notifier thread of a change in their interests by writing a single
- * byte to a special pipe that the notifier thread is monitoring.
- *
- * Result:
- * None. Once started, this routine never exits. It dies with the overall
- * process.
- *
- * Side effects:
- * The trigger pipe used to signal the notifier thread is created when
- * the notifier thread first starts.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-NotifierThreadProc(
- ClientData clientData) /* Not used. */
-{
- ThreadSpecificData *tsdPtr;
- fd_set readableMask, writableMask, exceptionalMask;
- int i, numFdBits = 0, polling;
- struct timeval poll = {0., 0.}, *timePtr;
- char buf[2];
-
- /*
- * Look for file events and report them to interested threads.
- */
-
- while (1) {
- FD_ZERO(&readableMask);
- FD_ZERO(&writableMask);
- FD_ZERO(&exceptionalMask);
-
- /*
- * Compute the logical OR of the select masks from all the waiting
- * notifiers.
- */
-
- 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);
- }
- if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) {
- FD_SET(i, &writableMask);
- }
- if (FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) {
- FD_SET(i, &exceptionalMask);
- }
- }
- if (tsdPtr->numFdBits > numFdBits) {
- numFdBits = tsdPtr->numFdBits;
- }
- polling = tsdPtr->polling;
- UNLOCK_NOTIFIER_TSD;
- if ((tsdPtr->polled = polling)) {
- timePtr = &poll;
- }
- }
- UNLOCK_NOTIFIER;
-
- /*
- * Set up the select mask to include the receive pipe.
- */
-
- if (receivePipe >= numFdBits) {
- numFdBits = receivePipe + 1;
- }
- FD_SET(receivePipe, &readableMask);
-
- if (select(numFdBits, &readableMask, &writableMask, &exceptionalMask,
- timePtr) == -1) {
- /*
- * Try again immediately on an error.
- */
-
- continue;
- }
-
- /*
- * Alert any threads that are waiting on a ready file descriptor.
- */
-
- LOCK_NOTIFIER;
- for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
- 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, &checkMasks.readable)
- && FD_ISSET(i, &readableMask)) {
- FD_SET(i, &readyMasks.readable);
- found = 1;
- }
- if (FD_ISSET(i, &checkMasks.writable)
- && FD_ISSET(i, &writableMask)) {
- FD_SET(i, &readyMasks.writable);
- found = 1;
- }
- if (FD_ISSET(i, &checkMasks.exceptional)
- && FD_ISSET(i, &exceptionalMask)) {
- FD_SET(i, &readyMasks.exceptional);
- found = 1;
- }
- }
-
- 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);
- }
- }
- }
- UNLOCK_NOTIFIER;
-
- /*
- * Consume the next byte from the notifier pipe if the pipe was
- * readable. Note that there may be multiple bytes pending, but to
- * avoid a race condition we only read one at a time.
- */
-
- if (FD_ISSET(receivePipe, &readableMask)) {
- i = read(receivePipe, buf, 1);
-
- if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) {
- /*
- * Someone closed the write end of the pipe or sent us a Quit
- * message [Bug: 4139] and then closed the write end of the
- * pipe so we need to shut down the notifier thread.
- */
-
- break;
- }
- }
- }
- pthread_exit(0);
-}
-\f
-#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;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * 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;
-}
-\f
-/*
- *----------------------------------------------------------------------
- *
- * 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 */
-\f
-/*
- * Local Variables:
- * mode: c
- * c-basic-offset: 4
- * fill-column: 78
- * End:
- */