diff options
Diffstat (limited to 'macosx/tclMacOSXNotify.c')
| -rw-r--r-- | macosx/tclMacOSXNotify.c | 1534 | 
1 files changed, 1143 insertions, 391 deletions
| diff --git a/macosx/tclMacOSXNotify.c b/macosx/tclMacOSXNotify.c index 23b858b..ef80192 100644 --- a/macosx/tclMacOSXNotify.c +++ b/macosx/tclMacOSXNotify.c @@ -6,14 +6,11 @@   *	This file works together with generic/tclNotify.c.   *   * Copyright (c) 1995-1997 Sun Microsystems, Inc. - * Copyright 2001, Apple Computer, Inc. - * Copyright (c) 2005 Tcl Core Team. - * Copyright (c) 2005-2006 Daniel A. Steffen <das@users.sourceforge.net> + * Copyright 2001-2009, Apple Inc. + * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>   *   * See the file "license.terms" for information on usage and redistribution of   * this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * RCS: @(#) $Id: tclMacOSXNotify.c,v 1.11 2006/08/21 03:50:50 das Exp $   */  #include "tclInt.h" @@ -22,8 +19,180 @@  #include <CoreFoundation/CoreFoundation.h>  #include <pthread.h> -extern TclStubs tclStubs; -extern Tcl_NotifierProcs tclOriginalNotifier; +/* #define TCL_MAC_DEBUG_NOTIFIER 1 */ + +/* + * We use the Darwin-native spinlock API rather than pthread mutexes for + * notifier locking: this radically simplifies the implementation and lowers + * overhead. Note that these are not pure spinlocks, they employ various + * strategies to back off and relinquish the processor, making them immune to + * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin + * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s). + */ + +#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) +/* + * Use OSSpinLock API where available (Tiger or later). + */ + +#include <libkern/OSAtomic.h> + +#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 +/* + * Support for weakly importing spinlock API. + */ +#define WEAK_IMPORT_SPINLOCKLOCK +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 +#define VOLATILE volatile +#else +#define VOLATILE +#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 */ +#ifndef bool +#define bool int +#endif +extern void		OSSpinLockLock(VOLATILE OSSpinLock *lock) +			    WEAK_IMPORT_ATTRIBUTE; +extern void		OSSpinLockUnlock(VOLATILE OSSpinLock *lock) +			    WEAK_IMPORT_ATTRIBUTE; +extern bool		OSSpinLockTry(VOLATILE OSSpinLock *lock) +			    WEAK_IMPORT_ATTRIBUTE; +extern void		_spin_lock(VOLATILE OSSpinLock *lock) +			    WEAK_IMPORT_ATTRIBUTE; +extern void		_spin_unlock(VOLATILE OSSpinLock *lock) +			    WEAK_IMPORT_ATTRIBUTE; +extern bool		_spin_lock_try(VOLATILE OSSpinLock *lock) +			    WEAK_IMPORT_ATTRIBUTE; +static void (* lockLock)(VOLATILE OSSpinLock *lock) = NULL; +static void (* lockUnlock)(VOLATILE OSSpinLock *lock) = NULL; +static bool (* lockTry)(VOLATILE OSSpinLock *lock) = NULL; +#undef VOLATILE +static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT; +static void +SpinLockLockInit(void) +{ +    lockLock   = OSSpinLockLock   != NULL ? OSSpinLockLock   : _spin_lock; +    lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock; +    lockTry    = OSSpinLockTry    != NULL ? OSSpinLockTry    : _spin_lock_try; +    if (lockLock == NULL || lockUnlock == NULL) { +	Tcl_Panic("SpinLockLockInit: no spinlock API available"); +    } +} +#define SpinLockLock(p) 	lockLock(p) +#define SpinLockUnlock(p)	lockUnlock(p) +#define SpinLockTry(p)		lockTry(p) +#else +#define SpinLockLock(p) 	OSSpinLockLock(p) +#define SpinLockUnlock(p)	OSSpinLockUnlock(p) +#define SpinLockTry(p)		OSSpinLockTry(p) +#endif /* HAVE_WEAK_IMPORT */ +#define SPINLOCK_INIT		OS_SPINLOCK_INIT + +#else +/* + * Otherwise, use commpage spinlock SPI directly. + */ + +typedef uint32_t OSSpinLock; +extern void		_spin_lock(OSSpinLock *lock); +extern void		_spin_unlock(OSSpinLock *lock); +extern int		_spin_lock_try(OSSpinLock *lock); +#define SpinLockLock(p) 	_spin_lock(p) +#define SpinLockUnlock(p)	_spin_unlock(p) +#define SpinLockTry(p)		_spin_lock_try(p) +#define SPINLOCK_INIT		0 + +#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ + +/* + * These spinlocks lock access to the global notifier state. + */ + +static OSSpinLock notifierInitLock = SPINLOCK_INIT; +static OSSpinLock notifierLock     = SPINLOCK_INIT; + +/* + * Macros abstracting notifier locking/unlocking + */ + +#define LOCK_NOTIFIER_INIT	SpinLockLock(¬ifierInitLock) +#define UNLOCK_NOTIFIER_INIT	SpinLockUnlock(¬ifierInitLock) +#define LOCK_NOTIFIER		SpinLockLock(¬ifierLock) +#define UNLOCK_NOTIFIER		SpinLockUnlock(¬ifierLock) +#define LOCK_NOTIFIER_TSD	SpinLockLock(&tsdPtr->tsdLock) +#define UNLOCK_NOTIFIER_TSD	SpinLockUnlock(&tsdPtr->tsdLock) + +#ifdef TCL_MAC_DEBUG_NOTIFIER +#define TclMacOSXNotifierDbgMsg(m, ...) \ +    do {								\ +	fprintf(notifierLog?notifierLog:stderr, "tclMacOSXNotify.c:%d: " \ +		"%s() pid %5d thread %10p: " m "\n", __LINE__, __func__, \ +		getpid(), pthread_self(), ##__VA_ARGS__);		\ +	fflush(notifierLog?notifierLog:stderr);				\ +    } while (0) + +/* + * Debug version of SpinLockLock that logs the time spent waiting for the lock + */ + +#define SpinLockLockDbg(p) \ +    if (!SpinLockTry(p)) {						\ +	Tcl_WideInt s = TclpGetWideClicks(), e;				\ +									\ +	SpinLockLock(p);						\ +	e = TclpGetWideClicks();					\ +	TclMacOSXNotifierDbgMsg("waited on %s for %8.0f ns",		\ +		#p, TclpWideClicksToNanoseconds(e-s));			\ +    } +#undef LOCK_NOTIFIER_INIT +#define LOCK_NOTIFIER_INIT	SpinLockLockDbg(¬ifierInitLock) +#undef LOCK_NOTIFIER +#define LOCK_NOTIFIER		SpinLockLockDbg(¬ifierLock) +#undef LOCK_NOTIFIER_TSD +#define LOCK_NOTIFIER_TSD	SpinLockLockDbg(&tsdPtr->tsdLock) +#include <asl.h> +static FILE *notifierLog = NULL; +#ifndef NOTIFIER_LOG +#define NOTIFIER_LOG "/tmp/tclMacOSXNotify.log" +#endif +#define OPEN_NOTIFIER_LOG \ +    if (!notifierLog) {							\ +	notifierLog = fopen(NOTIFIER_LOG, "a");				\ +	/*TclMacOSXNotifierDbgMsg("open log");				\ +	 *asl_set_filter(NULL,						\ +	 *	ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));			\ +	 *asl_add_log_file(NULL, fileno(notifierLog));*/		\ +    } +#define CLOSE_NOTIFIER_LOG \ +    if (notifierLog) {							\ +	/*asl_remove_log_file(NULL, fileno(notifierLog));		\ +	 *TclMacOSXNotifierDbgMsg("close log");*/			\ +	fclose(notifierLog);						\ +	notifierLog = NULL;						\ +    } +#define ENABLE_ASL \ +    if (notifierLog) {							\ +	/*tsdPtr->asl = asl_open(NULL, "com.apple.console",		\ +	 *	ASL_OPT_NO_REMOTE);					\ +	 *asl_set_filter(tsdPtr->asl,					\ +	 *	ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));			\ +	 *asl_add_log_file(tsdPtr->asl, fileno(notifierLog));*/		\ +    } +#define DISABLE_ASL \ +    /*if (tsdPtr->asl) {						\ +     *	if (notifierLog) {						\ +     *	    asl_remove_log_file(tsdPtr->asl, fileno(notifierLog));	\ +     *	}								\ +     *	asl_close(tsdPtr->asl);						\ +     *}*/ +#define ASLCLIENT_DECL		/*aslclient asl*/ +#else +#define TclMacOSXNotifierDbgMsg(m, ...) +#define OPEN_NOTIFIER_LOG +#define CLOSE_NOTIFIER_LOG +#define ENABLE_ASL +#define DISABLE_ASL +#define ASLCLIENT_DECL +#endif /* TCL_MAC_DEBUG_NOTIFIER */  /*   * This structure is used to keep track of the notifier info for a registered @@ -78,6 +247,32 @@ typedef struct SelectMasks {  typedef struct ThreadSpecificData {      FileHandler *firstFileHandlerPtr;  				/* Pointer to head of file handler list. */ +    int polled;			/* True if the notifier thread has polled for +				 * this thread. */ +    int sleeping;		/* True if runloop is inside Tcl_Sleep. */ +    int runLoopSourcePerformed;	/* True after the runLoopSource callack was +				 * performed. */ +    int runLoopRunning;		/* True if this thread's Tcl runLoop is +				 * running. */ +    int runLoopNestingLevel;	/* Level of nested runLoop invocations. */ +    int runLoopServicingEvents;	/* True if this thread's runLoop is servicing +				 * Tcl events. */ + +    /* Must hold the notifierLock before accessing the following fields: */ +    /* Start notifierLock section */ +    int onList;			/* True if this thread is on the +				 * waitingList */ +    struct ThreadSpecificData *nextPtr, *prevPtr; +				/* All threads that are currently waiting on +				 * an event have their ThreadSpecificData +				 * structure on a doubly-linked listed formed +				 * from these pointers. */ +    /* End notifierLock section */ + +    OSSpinLock tsdLock;		/* Must hold this lock before acessing the +				 * following fields from more than one +				 * thread. */ +    /* Start tsdLock section */      SelectMasks checkMasks;	/* This structure is used to build up the  				 * masks to be used in the next call to  				 * select. Bits are set in response to calls @@ -88,26 +283,26 @@ typedef struct ThreadSpecificData {      int numFdBits;		/* Number of valid bits in checkMasks (one  				 * more than highest fd for which  				 * Tcl_WatchFile has been called). */ -    int onList;			/* True if it is in this list */ -    unsigned int pollState;	/* pollState is used to implement a polling -				 * handshake between each thread and the -				 * notifier thread. Bits defined below. */ -    struct ThreadSpecificData *nextPtr, *prevPtr; -				/* All threads that are currently waiting on -				 * an event have their ThreadSpecificData -				 * structure on a doubly-linked listed formed -				 * from these pointers. You must hold the -				 * notifierLock before accessing these -				 * fields. */ +    int polling;		/* True if this thread is polling for +				 * events. */ +    CFRunLoopRef runLoop;	/* This thread's CFRunLoop, needs to be woken +				 * up whenever the runLoopSource is +				 * signaled. */      CFRunLoopSourceRef runLoopSource;  				/* Any other thread alerts a notifier that an  				 * event is ready to be processed by signaling  				 * this CFRunLoopSource. */ -    CFRunLoopRef runLoop;	/* This thread's CFRunLoop, needs to be woken -				 * up whenever the runLoopSource is -				 * signaled. */ -    int eventReady;		/* True if an event is ready to be -				 * processed. */ +    CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; +				/* Adds/removes this thread from waitingList +				 * when the CFRunLoop starts/stops. */ +    CFRunLoopTimerRef runLoopTimer; +				/* Wakes up CFRunLoop after given timeout when +				 * running embedded. */ +    /* End tsdLock section */ + +    CFTimeInterval waitTime;	/* runLoopTimer wait time when running +				 * embedded. */ +    ASLCLIENT_DECL;  } ThreadSpecificData;  static Tcl_ThreadDataKey dataKey; @@ -149,125 +344,86 @@ static int triggerPipe = -1;  static int receivePipe = -1; /* Output end of triggerPipe */  /* - * We use Darwin-native spinlocks instead of pthread mutexes for notifier - * locking: this radically simplifies the implementation and lowers overhead. - * Note that these are not pure spinlocks, they employ various strategies to - * back off, making them immune to most priority-inversion livelocks (c.f. man - * 3 OSSpinLockLock). - */ - -#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) -/* - * Use OSSpinLock API where available (Tiger or later). - */ - -#include <libkern/OSAtomic.h> - -#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 -/* - * Support for weakly importing spinlock API. - */ -#define WEAK_IMPORT_SPINLOCKLOCK -extern void	OSSpinLockLock(OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern void	OSSpinLockUnlock(OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern void	_spin_lock(OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -extern void	_spin_unlock(OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; -static void (* lockLock)(OSSpinLock *lock) = NULL; -static void (* lockUnlock)(OSSpinLock *lock) = NULL; -static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT; -static void SpinLockLockInit(void) { -    lockLock   = OSSpinLockLock   != NULL ? OSSpinLockLock   : _spin_lock; -    lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock; -    if (lockLock == NULL || lockUnlock == NULL) { -	Tcl_Panic("SpinLockLockInit: no spinlock API available"); -    } -} -#define SpinLockLock(p) 	lockLock(p) -#define SpinLockUnlock(p)	lockUnlock(p) -#else -#define SpinLockLock(p) 	OSSpinLockLock(p) -#define SpinLockUnlock(p)	OSSpinLockUnlock(p) -#endif /* HAVE_WEAK_IMPORT */ - -#else -/* - * Otherwise, use commpage spinlock SPI directly. - */ - -typedef uint32_t OSSpinLock; -extern void	_spin_lock(OSSpinLock *lock); -extern void	_spin_unlock(OSSpinLock *lock); -#define SpinLockLock(p) 	_spin_lock(p) -#define SpinLockUnlock(p)	_spin_unlock(p) - -#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ - -/* - * These spinlocks lock access to the global notifier state. - */ - -static OSSpinLock notifierInitLock = 0; -static OSSpinLock notifierLock = 0; - -/* - * Macros abstracting notifier locking/unlocking - */ - -#define LOCK_NOTIFIER_INIT	SpinLockLock(¬ifierInitLock) -#define UNLOCK_NOTIFIER_INIT	SpinLockUnlock(¬ifierInitLock) -#define LOCK_NOTIFIER		SpinLockLock(¬ifierLock) -#define UNLOCK_NOTIFIER		SpinLockUnlock(¬ifierLock) - -/* - * The pollState bits - *	POLL_WANT is set by each thread before it waits on its condition - *		variable. It is checked by the notifier before it does select. - *	POLL_DONE is set by the notifier if it goes into select after seeing - *		POLL_WANT. The idea is to ensure it tries a select with the - *		same bits the initial thread had set. + * The following static indicates if the notifier thread is running. + * + * You must hold the notifierInitLock before accessing this variable.   */ -#define POLL_WANT	0x1 -#define POLL_DONE	0x2 +static int notifierThreadRunning;  /* - * This is the thread ID of the notifier thread that does select. + * 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 + * 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	NotifierThreadProc(ClientData clientData); -static int	FileHandlerEventProc(Tcl_Event *evPtr, int flags); +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); +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; +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 */  /* @@ -289,14 +445,19 @@ extern int pthread_atfork(void (*prepare)(void), void (*parent)(void),  ClientData  Tcl_InitNotifier(void)  { -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    ThreadSpecificData *tsdPtr; + +    if (tclNotifierHooks.initNotifierProc) { +	return tclNotifierHooks.initNotifierProc(); +    } -    tsdPtr->eventReady = 0; +    tsdPtr = TCL_TSD_INIT(&dataKey);  #ifdef WEAK_IMPORT_SPINLOCKLOCK      /*       * Initialize support for weakly imported spinlock API.       */ +      if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) {  	Tcl_Panic("Tcl_InitNotifier: pthread_once failed");      } @@ -316,17 +477,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; @@ -336,18 +538,15 @@ Tcl_InitNotifier(void)       * child of a fork.       */ -    if ( -#ifdef WEAK_IMPORT_PTHREAD_ATFORK -	    pthread_atfork != NULL && -#endif -	    !atForkInit) { +    if (MayUsePthreadAtfork() && !atForkInit) {  	int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild); +  	if (result) {  	    Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed");  	}  	atForkInit = 1;      } -#endif +#endif /* HAVE_PTHREAD_ATFORK */      if (notifierCount == 0) {  	int fds[2], status; @@ -362,12 +561,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]; @@ -375,21 +576,99 @@ 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; -    return (ClientData) tsdPtr; +    return tsdPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TclMacOSXNotifierAddRunLoopMode -- + * + *	Add the tcl notifier RunLoop source, observer and timer (if any) + *	to the given RunLoop mode. + * + * Results: + *	None. + * + * Side effects: + *	None. + * + *---------------------------------------------------------------------- + */ + +void +TclMacOSXNotifierAddRunLoopMode( +    const void *runLoopMode) +{ +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    CFStringRef mode = (CFStringRef) runLoopMode; + +    if (tsdPtr->runLoop) { +	CFRunLoopAddSource(tsdPtr->runLoop, tsdPtr->runLoopSource, mode); +	CFRunLoopAddObserver(tsdPtr->runLoop, tsdPtr->runLoopObserver, mode); +	if (tsdPtr->runLoopTimer) { +	    CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, mode); +	} +    }  }  /*   *----------------------------------------------------------------------   * + * StartNotifierThread -- + * + *	Start notifier thread if necessary. + * + * Results: + *	None. + * + * Side effects: + *	None. + * + *---------------------------------------------------------------------- + */ + +static void +StartNotifierThread(void) +{ +    LOCK_NOTIFIER_INIT; +    if (!notifierCount) { +	Tcl_Panic("StartNotifierThread: notifier not initialized"); +    } +    if (!notifierThreadRunning) { +	int result; +	pthread_attr_t attr; + +	pthread_attr_init(&attr); +	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); +	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); +	pthread_attr_setstacksize(&attr, 60 * 1024); +	result = pthread_create(¬ifierThread, &attr, +		(void * (*)(void *))NotifierThreadProc, NULL); +	pthread_attr_destroy(&attr); +	if (result) { +	    Tcl_Panic("StartNotifierThread: unable to start notifier thread"); +	} +	notifierThreadRunning = 1; +    } +    UNLOCK_NOTIFIER_INIT; +} + + +/* + *---------------------------------------------------------------------- + *   * Tcl_FinalizeNotifier --   *   *	This function is called to cleanup the notifier state before a thread @@ -409,10 +688,18 @@ void  Tcl_FinalizeNotifier(      ClientData clientData)		/* Not used. */  { -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    ThreadSpecificData *tsdPtr; + +    if (tclNotifierHooks.finalizeNotifierProc) { +	tclNotifierHooks.finalizeNotifierProc(clientData); +	return; +    } + +    tsdPtr = TCL_TSD_INIT(&dataKey);      LOCK_NOTIFIER_INIT;      notifierCount--; +    DISABLE_ASL;      /*       * If this is the last thread to use the notifier, close the notifier pipe @@ -420,52 +707,63 @@ Tcl_FinalizeNotifier(       */      if (notifierCount == 0) { -	int result; +	if (triggerPipe != -1) { +	    /* +	     * Send "q" message to the notifier thread so that it will +	     * terminate. The notifier will return from its call to select() +	     * and notice that a "q" message has arrived, it will then close +	     * its side of the pipe and terminate its thread. Note the we can +	     * not just close the pipe and check for EOF in the notifier +	     * thread because if a background child process was created with +	     * exec, select() would not register the EOF on the pipe until the +	     * child processes had terminated. [Bug: 4139] [Bug 1222872] +	     */ -	if (triggerPipe < 0) { -	    Tcl_Panic("Tcl_FinalizeNotifier: notifier pipe not initialized"); -	} +	    write(triggerPipe, "q", 1); +	    close(triggerPipe); -	/* -	 * Send "q" message to the notifier thread so that it will terminate. -	 * The notifier will return from its call to select() and notice that -	 * a "q" message has arrived, it will then close its side of the pipe -	 * and terminate its thread. Note the we can not just close the pipe -	 * and check for EOF in the notifier thread because if a background -	 * child process was created with exec, select() would not register -	 * the EOF on the pipe until the child processes had terminated. [Bug: -	 * 4139] [Bug: 1222872] -	 */ - -	write(triggerPipe, "q", 1); -	close(triggerPipe); +	    if (notifierThreadRunning) { +		int result = pthread_join(notifierThread, NULL); -	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;  }  /* @@ -491,15 +789,19 @@ void  Tcl_AlertNotifier(      ClientData clientData)  { -    ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; +    ThreadSpecificData *tsdPtr = clientData; -    LOCK_NOTIFIER; +    if (tclNotifierHooks.alertNotifierProc) { +	tclNotifierHooks.alertNotifierProc(clientData); +	return; +    } + +    LOCK_NOTIFIER_TSD;      if (tsdPtr->runLoop) { -	tsdPtr->eventReady = 1;  	CFRunLoopSourceSignal(tsdPtr->runLoopSource);  	CFRunLoopWakeUp(tsdPtr->runLoop);      } -    UNLOCK_NOTIFIER; +    UNLOCK_NOTIFIER_TSD;  }  /* @@ -507,32 +809,73 @@ Tcl_AlertNotifier(   *   * Tcl_SetTimer --   * - *	This function sets the current notifier timer value. This interface is - *	not implemented in this notifier because we are always running inside - *	of Tcl_DoOneEvent. + *	This function sets the current notifier timer value.   *   * Results:   *	None.   *   * Side effects: - *	None. + *	Replaces any previous timer.   *   *----------------------------------------------------------------------   */  void  Tcl_SetTimer( -    Tcl_Time *timePtr)		/* Timeout value, may be NULL. */ +    const Tcl_Time *timePtr)		/* Timeout value, may be NULL. */  { -    /* -     * The interval timer doesn't do anything in this implementation, because -     * the only event loop is via Tcl_DoOneEvent, which passes timeout values -     * to Tcl_WaitForEvent. -     */ +    ThreadSpecificData *tsdPtr; +    CFRunLoopTimerRef runLoopTimer; +    CFTimeInterval waitTime; -    if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { -	tclStubs.tcl_SetTimer(timePtr); +    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); +} + +/* + *---------------------------------------------------------------------- + * + * TimerWakeUp -- + * + *	CFRunLoopTimer callback. + * + * Results: + *	None. + * + * Side effects: + *	None. + * + *---------------------------------------------------------------------- + */ + +static void +TimerWakeUp( +    CFRunLoopTimerRef timer, +    void *info) +{  }  /* @@ -556,6 +899,28 @@ Tcl_ServiceModeHook(      int mode)			/* Either TCL_SERVICE_ALL, or  				 * TCL_SERVICE_NONE. */  { +    ThreadSpecificData *tsdPtr; + +    if (tclNotifierHooks.serviceModeHookProc) { +	tclNotifierHooks.serviceModeHookProc(mode); +	return; +    } + +    tsdPtr = TCL_TSD_INIT(&dataKey); + +    if (mode == TCL_SERVICE_ALL && !tsdPtr->runLoopTimer) { +	if (!tsdPtr->runLoop) { +	    Tcl_Panic("Tcl_ServiceModeHook: Notifier not initialized"); +	} +	tsdPtr->runLoopTimer = CFRunLoopTimerCreate(NULL, +		CFAbsoluteTimeGetCurrent() + CF_TIMEINTERVAL_FOREVER, +		CF_TIMEINTERVAL_FOREVER, 0, 0, TimerWakeUp, NULL); +	if (tsdPtr->runLoopTimer) { +	    CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, +		    kCFRunLoopCommonModes); +	    StartNotifierThread(); +	} +    }  }  /* @@ -585,15 +950,16 @@ Tcl_CreateFileHandler(  				 * event. */      ClientData clientData)	/* Arbitrary data to pass to proc. */  { -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    ThreadSpecificData *tsdPtr;      FileHandler *filePtr; -    if (tclStubs.tcl_CreateFileHandler != -	    tclOriginalNotifier.createFileHandlerProc) { -	tclStubs.tcl_CreateFileHandler(fd, mask, proc, clientData); +    if (tclNotifierHooks.createFileHandlerProc) { +	tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData);  	return;      } +    tsdPtr = TCL_TSD_INIT(&dataKey); +      for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;  	    filePtr = filePtr->nextPtr) {  	if (filePtr->fd == fd) { @@ -601,7 +967,7 @@ Tcl_CreateFileHandler(  	}      }      if (filePtr == NULL) { -	filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); +	filePtr = ckalloc(sizeof(FileHandler));  	filePtr->fd = fd;  	filePtr->readyMask = 0;  	filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; @@ -615,24 +981,26 @@ Tcl_CreateFileHandler(       * Update the check masks for this file.       */ +    LOCK_NOTIFIER_TSD;      if (mask & TCL_READABLE) { -	FD_SET(fd, &(tsdPtr->checkMasks.readable)); +	FD_SET(fd, &tsdPtr->checkMasks.readable);      } else { -	FD_CLR(fd, &(tsdPtr->checkMasks.readable)); +	FD_CLR(fd, &tsdPtr->checkMasks.readable);      }      if (mask & TCL_WRITABLE) { -	FD_SET(fd, &(tsdPtr->checkMasks.writable)); +	FD_SET(fd, &tsdPtr->checkMasks.writable);      } else { -	FD_CLR(fd, &(tsdPtr->checkMasks.writable)); +	FD_CLR(fd, &tsdPtr->checkMasks.writable);      }      if (mask & TCL_EXCEPTION) { -	FD_SET(fd, &(tsdPtr->checkMasks.exceptional)); +	FD_SET(fd, &tsdPtr->checkMasks.exceptional);      } else { -	FD_CLR(fd, &(tsdPtr->checkMasks.exceptional)); +	FD_CLR(fd, &tsdPtr->checkMasks.exceptional);      }      if (tsdPtr->numFdBits <= fd) {  	tsdPtr->numFdBits = fd+1;      } +    UNLOCK_NOTIFIER_TSD;  }  /* @@ -657,21 +1025,23 @@ Tcl_DeleteFileHandler(  				 * function. */  {      FileHandler *filePtr, *prevPtr; -    int i; -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    int i, numFdBits; +    ThreadSpecificData *tsdPtr; -    if (tclStubs.tcl_DeleteFileHandler != -	    tclOriginalNotifier.deleteFileHandlerProc) { -	tclStubs.tcl_DeleteFileHandler(fd); +    if (tclNotifierHooks.deleteFileHandlerProc) { +	tclNotifierHooks.deleteFileHandlerProc(fd);  	return;      } +    tsdPtr = TCL_TSD_INIT(&dataKey); +    numFdBits = -1; +      /*       * Find the entry for the given file (and return if there isn't one).       */      for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; -	 prevPtr = filePtr, filePtr = filePtr->nextPtr) { +	    prevPtr = filePtr, filePtr = filePtr->nextPtr) {  	if (filePtr == NULL) {  	    return;  	} @@ -681,35 +1051,41 @@ Tcl_DeleteFileHandler(      }      /* -     * Update the check masks for this file. -     */ - -    if (filePtr->mask & TCL_READABLE) { -	FD_CLR(fd, &(tsdPtr->checkMasks.readable)); -    } -    if (filePtr->mask & TCL_WRITABLE) { -	FD_CLR(fd, &(tsdPtr->checkMasks.writable)); -    } -    if (filePtr->mask & TCL_EXCEPTION) { -	FD_CLR(fd, &(tsdPtr->checkMasks.exceptional)); -    } - -    /*       * Find current max fd.       */      if (fd+1 == tsdPtr->numFdBits) { -	tsdPtr->numFdBits = 0; +	numFdBits = 0;  	for (i = fd-1; i >= 0; i--) { -	    if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) -		    || FD_ISSET(i, &(tsdPtr->checkMasks.writable)) -		    || FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) { -		tsdPtr->numFdBits = i+1; +	    if (FD_ISSET(i, &tsdPtr->checkMasks.readable) +		    || FD_ISSET(i, &tsdPtr->checkMasks.writable) +		    || FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) { +		numFdBits = i+1;  		break;  	    }  	}      } +    LOCK_NOTIFIER_TSD; +    if (numFdBits != -1) { +	tsdPtr->numFdBits = numFdBits; +    } + +    /* +     * Update the check masks for this file. +     */ + +    if (filePtr->mask & TCL_READABLE) { +	FD_CLR(fd, &tsdPtr->checkMasks.readable); +    } +    if (filePtr->mask & TCL_WRITABLE) { +	FD_CLR(fd, &tsdPtr->checkMasks.writable); +    } +    if (filePtr->mask & TCL_EXCEPTION) { +	FD_CLR(fd, &tsdPtr->checkMasks.exceptional); +    } +    UNLOCK_NOTIFIER_TSD; +      /*       * Clean up information in the callback record.       */ @@ -719,7 +1095,7 @@ Tcl_DeleteFileHandler(      } else {  	prevPtr->nextPtr = filePtr->nextPtr;      } -    ckfree((char *) filePtr); +    ckfree(filePtr);  }  /* @@ -788,7 +1164,18 @@ FileHandlerEventProc(  	mask = filePtr->readyMask & filePtr->mask;  	filePtr->readyMask = 0;  	if (mask != 0) { -	    (*filePtr->proc)(filePtr->clientData, mask); +	    LOCK_NOTIFIER_TSD; +	    if (mask & TCL_READABLE) { +		FD_CLR(filePtr->fd, &tsdPtr->readyMasks.readable); +	    } +	    if (mask & TCL_WRITABLE) { +		FD_CLR(filePtr->fd, &tsdPtr->readyMasks.writable); +	    } +	    if (mask & TCL_EXCEPTION) { +		FD_CLR(filePtr->fd, &tsdPtr->readyMasks.exceptional); +	    } +	    UNLOCK_NOTIFIER_TSD; +	    filePtr->proc(filePtr->clientData, mask);  	}  	break;      } @@ -805,202 +1192,540 @@ FileHandlerEventProc(   *	polls without blocking.   *   * Results: - *	Returns -1 if the select would block forever, otherwise returns 0. + *	Returns 0 if a tcl event or timeout ocurred and 1 if a non-tcl + *	CFRunLoop source was processed.   *   * Side effects: - *	Queues file events that are detected by the select. + *	None.   *   *----------------------------------------------------------------------   */  int  Tcl_WaitForEvent( -    Tcl_Time *timePtr)		/* Maximum block time, or NULL. */ +    const Tcl_Time *timePtr)		/* Maximum block time, or NULL. */  { -    FileHandler *filePtr; -    FileHandlerEvent *fileEvPtr; -    int mask; -    Tcl_Time myTime; -    int waitForFiles; -    Tcl_Time *myTimePtr; -    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    int result, polling, runLoopRunning; +    CFTimeInterval waitTime; +    SInt32 runLoopStatus; +    ThreadSpecificData *tsdPtr; + +    if (tclNotifierHooks.waitForEventProc) { +	return tclNotifierHooks.waitForEventProc(timePtr); +    } +    result = -1; +    polling = 0; +    waitTime = CF_TIMEINTERVAL_FOREVER; +    tsdPtr = TCL_TSD_INIT(&dataKey); -    if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { -	return tclStubs.tcl_WaitForEvent(timePtr); +    if (!tsdPtr->runLoop) { +	Tcl_Panic("Tcl_WaitForEvent: Notifier not initialized");      } -    if (timePtr != NULL) { +    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.  	 */ -	myTime.sec  = timePtr->sec; -	myTime.usec = timePtr->usec; +	if (vTime.sec != 0 || vTime.usec != 0) { +	    tclScaleTimeProcPtr(&vTime, tclTimeClientData); +	    waitTime = vTime.sec + 1.0e-6 * vTime.usec; +	} else { +	    /* +	     * Polling: pretend to wait for files and tell the notifier thread +	     * what we are doing. The notifier thread makes sure it goes +	     * through select with its select mask in the same state as ours +	     * currently is. We block until that happens. +	     */ -	if (myTime.sec != 0 || myTime.usec != 0) { -	    (*tclScaleTimeProcPtr) (&myTime, tclTimeClientData); +	    polling = 1;  	} - -	myTimePtr = &myTime; -    } else { -	myTimePtr = NULL;      } +    StartNotifierThread(); + +    LOCK_NOTIFIER_TSD; +    tsdPtr->polling = polling; +    UNLOCK_NOTIFIER_TSD; +    tsdPtr->runLoopSourcePerformed = 0; +      /* -     * Start notifier thread if necessary. +     * 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_INIT; -    if (!notifierThread) { -	int result; -	pthread_attr_t attr; - -	pthread_attr_init(&attr); -	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); -	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); -	pthread_attr_setstacksize(&attr, 60 * 1024); -	result = pthread_create(¬ifierThread, &attr, -		(void * (*)(void *))NotifierThreadProc, NULL); -	pthread_attr_destroy(&attr); -	if (result || !notifierThread) { -	    Tcl_Panic("Tcl_WaitForEvent: unable to start notifier thread"); -	} +    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;      } -    UNLOCK_NOTIFIER_INIT; + +    return result; +} + +/* + *---------------------------------------------------------------------- + * + * QueueFileEvents -- + * + *	CFRunLoopSource callback for queueing file events. + * + * Results: + *	None. + * + * Side effects: + *	Queues file events that are detected by the select. + * + *---------------------------------------------------------------------- + */ + +static void +QueueFileEvents( +    void *info) +{ +    SelectMasks readyMasks; +    FileHandler *filePtr; +    ThreadSpecificData *tsdPtr = info;      /* -     * Place this thread on the list of interested threads, signal the -     * notifier thread, and wait for a response or a timeout. +     * Queue all detected file events.       */ -    LOCK_NOTIFIER; +    LOCK_NOTIFIER_TSD; +    FD_COPY(&tsdPtr->readyMasks.readable, &readyMasks.readable); +    FD_COPY(&tsdPtr->readyMasks.writable, &readyMasks.writable); +    FD_COPY(&tsdPtr->readyMasks.exceptional, &readyMasks.exceptional); +    FD_ZERO(&tsdPtr->readyMasks.readable); +    FD_ZERO(&tsdPtr->readyMasks.writable); +    FD_ZERO(&tsdPtr->readyMasks.exceptional); +    UNLOCK_NOTIFIER_TSD; +    tsdPtr->runLoopSourcePerformed = 1; -    waitForFiles = (tsdPtr->numFdBits > 0); -    if (myTimePtr != NULL && myTimePtr->sec == 0 && myTimePtr->usec == 0) { -	/* -	 * Cannot emulate a polling select with a polling condition variable. -	 * Instead, pretend to wait for files and tell the notifier thread -	 * what we are doing. The notifier thread makes sure it goes through -	 * select with its select mask in the same state as ours currently is. -	 * We block until that happens. -	 */ +    for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); +	    filePtr = filePtr->nextPtr) { +	int mask = 0; -	waitForFiles = 1; -	tsdPtr->pollState = POLL_WANT; -	myTimePtr = NULL; -    } else { -	tsdPtr->pollState = 0; -    } +	if (FD_ISSET(filePtr->fd, &readyMasks.readable)) { +	    mask |= TCL_READABLE; +	} +	if (FD_ISSET(filePtr->fd, &readyMasks.writable)) { +	    mask |= TCL_WRITABLE; +	} +	if (FD_ISSET(filePtr->fd, &readyMasks.exceptional)) { +	    mask |= TCL_EXCEPTION; +	} +	if (!mask) { +	    continue; +	} -    if (waitForFiles) {  	/* -	 * Add the ThreadSpecificData structure of this thread to the list of -	 * ThreadSpecificData structures of all threads that are waiting on -	 * file events. +	 * Don't bother to queue an event if the mask was previously non-zero +	 * since an event must still be on the queue.  	 */ -	tsdPtr->nextPtr = waitingListPtr; -	if (waitingListPtr) { -	    waitingListPtr->prevPtr = tsdPtr; -	} -	tsdPtr->prevPtr = 0; -	waitingListPtr = tsdPtr; -	tsdPtr->onList = 1; +	if (filePtr->readyMask == 0) { +	    FileHandlerEvent *fileEvPtr = ckalloc(sizeof(FileHandlerEvent)); -	write(triggerPipe, "", 1); +	    fileEvPtr->header.proc = FileHandlerEventProc; +	    fileEvPtr->fd = filePtr->fd; +	    Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); +	} +	filePtr->readyMask = mask;      } +} + +/* + *---------------------------------------------------------------------- + * + * UpdateWaitingListAndServiceEvents -- + * + *	CFRunLoopObserver callback for updating waitingList and + *	servicing Tcl events. + * + * Results: + *	None. + * + * Side effects: + *	None. + * + *---------------------------------------------------------------------- + */ -    FD_ZERO(&(tsdPtr->readyMasks.readable)); -    FD_ZERO(&(tsdPtr->readyMasks.writable)); -    FD_ZERO(&(tsdPtr->readyMasks.exceptional)); +static void +UpdateWaitingListAndServiceEvents( +    CFRunLoopObserverRef observer, +    CFRunLoopActivity activity, +    void *info) +{ +    ThreadSpecificData *tsdPtr = info; -    if (!tsdPtr->eventReady) { -	CFTimeInterval waitTime; -	CFStringRef runLoopMode; +    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; -	if (myTimePtr == NULL) { -	    waitTime = 1.0e10; /* Wait forever, as per CFRunLoop.c */ +#ifdef TCL_MAC_DEBUG_NOTIFIER +    if (SpinLockTry(¬ifierLock)) { +	Tcl_Panic("OnOffWaitingList: notifierLock unlocked"); +    } +#endif +    changeWaitingList = (!onList ^ !tsdPtr->onList); +    if (changeWaitingList) { +	if (onList) { +	    tsdPtr->nextPtr = waitingListPtr; +	    if (waitingListPtr) { +		waitingListPtr->prevPtr = tsdPtr; +	    } +	    tsdPtr->prevPtr = NULL; +	    waitingListPtr = tsdPtr; +	    tsdPtr->onList = 1;  	} else { -	    waitTime = myTimePtr->sec + 1.0e-6 * myTimePtr->usec; +	    if (tsdPtr->prevPtr) { +		tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; +	    } else { +		waitingListPtr = tsdPtr->nextPtr; +	    } +	    if (tsdPtr->nextPtr) { +		tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; +	    } +	    tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; +	    tsdPtr->onList = 0;  	} -	/* -	 * If 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. -	 */ -	if ((runLoopMode = CFRunLoopCopyCurrentMode(tsdPtr->runLoop))) { -	    CFRelease(runLoopMode); -	    runLoopMode = tclEventsOnlyRunLoopMode; -	} else { -	    runLoopMode = kCFRunLoopDefaultMode; +	if (signalNotifier) { +	    write(triggerPipe, "", 1);  	} -	UNLOCK_NOTIFIER; -	CFRunLoopRunInMode(runLoopMode, waitTime, TRUE); -	LOCK_NOTIFIER;      } -    tsdPtr->eventReady = 0; -    if (waitForFiles && tsdPtr->onList) { -	/* -	 * Remove the ThreadSpecificData structure of this thread from the -	 * waiting list. Alert the notifier thread to recompute its select -	 * masks - skipping this caused a hang when trying to close a pipe -	 * which the notifier thread was still doing a select on. -	 */ +    return changeWaitingList; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_Sleep -- + * + *	Delay execution for the specified number of milliseconds. + * + * Results: + *	None. + * + * Side effects: + *	Time passes. + * + *---------------------------------------------------------------------- + */ -	if (tsdPtr->prevPtr) { -	    tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; -	} else { -	    waitingListPtr = tsdPtr->nextPtr; +void +Tcl_Sleep( +    int ms)			/* Number of milliseconds to sleep. */ +{ +    Tcl_Time vdelay; +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + +    if (ms <= 0) { +	return; +    } + +    /* +     * TIP #233: Scale from virtual time to real-time. +     */ + +    vdelay.sec = ms / 1000; +    vdelay.usec = (ms % 1000) * 1000; +    tclScaleTimeProcPtr(&vdelay, tclTimeClientData); + + +    if (tsdPtr->runLoop) { +	CFTimeInterval waitTime; +	CFRunLoopTimerRef runLoopTimer = tsdPtr->runLoopTimer; +	CFAbsoluteTime nextTimerFire = 0, waitEnd, now; +	SInt32 runLoopStatus; + +	waitTime = vdelay.sec + 1.0e-6 * vdelay.usec; + 	now = CFAbsoluteTimeGetCurrent(); +	waitEnd = now + waitTime; + +	if (runLoopTimer) { +	    nextTimerFire = CFRunLoopTimerGetNextFireDate(runLoopTimer); +	    if (nextTimerFire < waitEnd) { +		CFRunLoopTimerSetNextFireDate(runLoopTimer, now + +			CF_TIMEINTERVAL_FOREVER); +	    } else { +		runLoopTimer = NULL; +	    }  	} -	if (tsdPtr->nextPtr) { -	    tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; +	tsdPtr->sleeping = 1; +	do { +	    runLoopStatus = CFRunLoopRunInMode(kCFRunLoopDefaultMode, +		    waitTime, FALSE); +	    switch (runLoopStatus) { +	    case kCFRunLoopRunFinished: +		Tcl_Panic("Tcl_Sleep: CFRunLoop finished"); +		break; +	    case kCFRunLoopRunStopped: +		TclMacOSXNotifierDbgMsg("CFRunLoop stopped"); +		waitTime = waitEnd - CFAbsoluteTimeGetCurrent(); +		break; +	    case kCFRunLoopRunTimedOut: +		waitTime = 0; +		break; +	    } +	} while (waitTime > 0); +	tsdPtr->sleeping = 0; + 	if (runLoopTimer) { +	    CFRunLoopTimerSetNextFireDate(runLoopTimer, nextTimerFire);  	} -	tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; -	tsdPtr->onList = 0; -	write(triggerPipe, "", 1); +    } else { +	struct timespec waitTime; + +	waitTime.tv_sec = vdelay.sec; +	waitTime.tv_nsec = vdelay.usec * 1000; +	while (nanosleep(&waitTime, &waitTime));      } +} + +/* + *---------------------------------------------------------------------- + * + * TclUnixWaitForFile -- + * + *	This function waits synchronously for a file to become readable or + *	writable, with an optional timeout. + * + * Results: + *	The return value is an OR'ed combination of TCL_READABLE, + *	TCL_WRITABLE, and TCL_EXCEPTION, indicating the conditions that are + *	present on file at the time of the return. This function will not + *	return until either "timeout" milliseconds have elapsed or at least + *	one of the conditions given by mask has occurred for file (a return + *	value of 0 means that a timeout occurred). No normal events will be + *	serviced during the execution of this function. + * + * Side effects: + *	Time passes. + * + *---------------------------------------------------------------------- + */ +int +TclUnixWaitForFile( +    int fd,			/* Handle for file on which to wait. */ +    int mask,			/* What to wait for: OR'ed combination of +				 * TCL_READABLE, TCL_WRITABLE, and +				 * TCL_EXCEPTION. */ +    int timeout)		/* Maximum amount of time to wait for one of +				 * the conditions in mask to occur, in +				 * milliseconds. A value of 0 means don't wait +				 * at all, and a value of -1 means wait +				 * forever. */ +{ +    Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */ +    struct timeval blockTime, *timeoutPtr; +    int numFound, result = 0; +    fd_set readableMask; +    fd_set writableMask; +    fd_set exceptionalMask; + +#define SET_BITS(var, bits)	((var) |= (bits)) +#define CLEAR_BITS(var, bits)	((var) &= ~(bits)) + +#ifndef _DARWIN_C_SOURCE      /* -     * Queue all detected file events before returning. +     * Sanity check fd.       */ -    for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); -	    filePtr = filePtr->nextPtr) { +    if (fd >= FD_SETSIZE) { +	Tcl_Panic("TclUnixWaitForFile can't handle file id %d", fd); +	/* must never get here, or select masks overrun will occur below */ +    } +#endif -	mask = 0; -	if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.readable))) { -	    mask |= TCL_READABLE; +    /* +     * If there is a non-zero finite timeout, compute the time when we give +     * up. +     */ + +    if (timeout > 0) { +	Tcl_GetTime(&now); +	abortTime.sec = now.sec + timeout/1000; +	abortTime.usec = now.usec + (timeout%1000)*1000; +	if (abortTime.usec >= 1000000) { +	    abortTime.usec -= 1000000; +	    abortTime.sec += 1;  	} -	if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.writable))) { -	    mask |= TCL_WRITABLE; +	timeoutPtr = &blockTime; +    } else if (timeout == 0) { +	timeoutPtr = &blockTime; +	blockTime.tv_sec = 0; +	blockTime.tv_usec = 0; +    } else { +	timeoutPtr = NULL; +    } + +    /* +     * Initialize the select masks. +     */ + +    FD_ZERO(&readableMask); +    FD_ZERO(&writableMask); +    FD_ZERO(&exceptionalMask); + +    /* +     * Loop in a mini-event loop of our own, waiting for either the file to +     * become ready or a timeout to occur. +     */ + +    while (1) { +	if (timeout > 0) { +	    blockTime.tv_sec = abortTime.sec - now.sec; +	    blockTime.tv_usec = abortTime.usec - now.usec; +	    if (blockTime.tv_usec < 0) { +		blockTime.tv_sec -= 1; +		blockTime.tv_usec += 1000000; +	    } +	    if (blockTime.tv_sec < 0) { +		blockTime.tv_sec = 0; +		blockTime.tv_usec = 0; +	    }  	} -	if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.exceptional))) { -	    mask |= TCL_EXCEPTION; + +	/* +	 * Setup the select masks for the fd. +	 */ + +	if (mask & TCL_READABLE) { +	    FD_SET(fd, &readableMask); +	} +	if (mask & TCL_WRITABLE) { +	    FD_SET(fd, &writableMask); +	} +	if (mask & TCL_EXCEPTION) { +	    FD_SET(fd, &exceptionalMask);  	} -	if (!mask) { +	/* +	 * Wait for the event or a timeout. +	 */ + +	numFound = select(fd + 1, &readableMask, &writableMask, +		&exceptionalMask, timeoutPtr); +	if (numFound == 1) { +	    if (FD_ISSET(fd, &readableMask)) { +		SET_BITS(result, TCL_READABLE); +	    } +	    if (FD_ISSET(fd, &writableMask)) { +		SET_BITS(result, TCL_WRITABLE); +	    } +	    if (FD_ISSET(fd, &exceptionalMask)) { +		SET_BITS(result, TCL_EXCEPTION); +	    } +	    result &= mask; +	    if (result) { +		break; +	    } +	} +	if (timeout == 0) { +	    break; +	} +	if (timeout < 0) {  	    continue;  	}  	/* -	 * Don't bother to queue an event if the mask was previously non-zero -	 * since an event must still be on the queue. +	 * The select returned early, so we need to recompute the timeout.  	 */ -	if (filePtr->readyMask == 0) { -	    fileEvPtr = (FileHandlerEvent *) ckalloc(sizeof(FileHandlerEvent)); -	    fileEvPtr->header.proc = FileHandlerEventProc; -	    fileEvPtr->fd = filePtr->fd; -	    Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); +	Tcl_GetTime(&now); +	if ((abortTime.sec < now.sec) +		|| (abortTime.sec==now.sec && abortTime.usec<=now.usec)) { +	    break;  	} -	filePtr->readyMask = mask;      } -    UNLOCK_NOTIFIER; -    return 0; +    return result;  }  /* @@ -1032,11 +1757,8 @@ NotifierThreadProc(      ClientData clientData)	/* Not used. */  {      ThreadSpecificData *tsdPtr; -    fd_set readableMask; -    fd_set writableMask; -    fd_set exceptionalMask; -    int i, numFdBits = 0; -    long found; +    fd_set readableMask, writableMask, exceptionalMask; +    int i, numFdBits = 0, polling;      struct timeval poll = {0., 0.}, *timePtr;      char buf[2]; @@ -1054,30 +1776,27 @@ NotifierThreadProc(  	 * notifiers.  	 */ -	LOCK_NOTIFIER;  	timePtr = NULL; +	LOCK_NOTIFIER;  	for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { +	    LOCK_NOTIFIER_TSD;  	    for (i = tsdPtr->numFdBits-1; i >= 0; --i) { -		if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))) { +		if (FD_ISSET(i, &tsdPtr->checkMasks.readable)) {  		    FD_SET(i, &readableMask);  		} -		if (FD_ISSET(i, &(tsdPtr->checkMasks.writable))) { +		if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) {  		    FD_SET(i, &writableMask);  		} -		if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) { +		if (FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) {  		    FD_SET(i, &exceptionalMask);  		}  	    }  	    if (tsdPtr->numFdBits > numFdBits) {  		numFdBits = tsdPtr->numFdBits;  	    } -	    if (tsdPtr->pollState & POLL_WANT) { -		/* -		 * Here we make sure we go through select() with the same mask -		 * bits that were present when the thread tried to poll. -		 */ - -		tsdPtr->pollState |= POLL_DONE; +	    polling = tsdPtr->polling; +	    UNLOCK_NOTIFIER_TSD; +	    if ((tsdPtr->polled = polling)) {  		timePtr = &poll;  	    }  	} @@ -1107,48 +1826,54 @@ NotifierThreadProc(  	LOCK_NOTIFIER;  	for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { -	    found = 0; +	    int found = 0; +	    SelectMasks readyMasks, checkMasks; + +	    LOCK_NOTIFIER_TSD; +	    FD_COPY(&tsdPtr->checkMasks.readable, &checkMasks.readable); +	    FD_COPY(&tsdPtr->checkMasks.writable, &checkMasks.writable); +	    FD_COPY(&tsdPtr->checkMasks.exceptional, &checkMasks.exceptional); +	    UNLOCK_NOTIFIER_TSD; +	    found = tsdPtr->polled; +	    FD_ZERO(&readyMasks.readable); +	    FD_ZERO(&readyMasks.writable); +	    FD_ZERO(&readyMasks.exceptional);  	    for (i = tsdPtr->numFdBits-1; i >= 0; --i) { -		if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) +		if (FD_ISSET(i, &checkMasks.readable)  			&& FD_ISSET(i, &readableMask)) { -		    FD_SET(i, &(tsdPtr->readyMasks.readable)); +		    FD_SET(i, &readyMasks.readable);  		    found = 1;  		} -		if (FD_ISSET(i, &(tsdPtr->checkMasks.writable)) +		if (FD_ISSET(i, &checkMasks.writable)  			&& FD_ISSET(i, &writableMask)) { -		    FD_SET(i, &(tsdPtr->readyMasks.writable)); +		    FD_SET(i, &readyMasks.writable);  		    found = 1;  		} -		if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional)) +		if (FD_ISSET(i, &checkMasks.exceptional)  			&& FD_ISSET(i, &exceptionalMask)) { -		    FD_SET(i, &(tsdPtr->readyMasks.exceptional)); +		    FD_SET(i, &readyMasks.exceptional);  		    found = 1;  		}  	    } -	    if (found || (tsdPtr->pollState & POLL_DONE)) { -		tsdPtr->eventReady = 1; -		if (tsdPtr->onList) { -		    /* -		     * Remove the ThreadSpecificData structure of this thread -		     * from the waiting list. This prevents us from -		     * continuously spining on select until the other threads -		     * runs and services the file event. -		     */ - -		    if (tsdPtr->prevPtr) { -			tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; -		    } else { -			waitingListPtr = tsdPtr->nextPtr; -		    } -		    if (tsdPtr->nextPtr) { -			tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; -		    } -		    tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; -		    tsdPtr->onList = 0; -		    tsdPtr->pollState = 0; -		} +	    if (found) { +		/* +		 * Remove the ThreadSpecificData structure of this thread from +		 * the waiting list. This prevents us from spinning +		 * continuously on select until the other threads runs and +		 * services the file event. +		 */ + +		OnOffWaitingList(tsdPtr, 0, 0); + +		LOCK_NOTIFIER_TSD; +		FD_COPY(&readyMasks.readable, &tsdPtr->readyMasks.readable); +		FD_COPY(&readyMasks.writable, &tsdPtr->readyMasks.writable); +		FD_COPY(&readyMasks.exceptional, +			&tsdPtr->readyMasks.exceptional); +		UNLOCK_NOTIFIER_TSD; +		tsdPtr->polled = 0;  		if (tsdPtr->runLoop) {  		    CFRunLoopSourceSignal(tsdPtr->runLoopSource);  		    CFRunLoopWakeUp(tsdPtr->runLoop); @@ -1177,7 +1902,7 @@ NotifierThreadProc(  	    }  	}      } -    pthread_exit (0); +    pthread_exit(0);  }  #ifdef HAVE_PTHREAD_ATFORK @@ -1200,8 +1925,11 @@ NotifierThreadProc(  static void  AtForkPrepare(void)  { +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +      LOCK_NOTIFIER_INIT;      LOCK_NOTIFIER; +    LOCK_NOTIFIER_TSD;  }  /* @@ -1223,6 +1951,9 @@ AtForkPrepare(void)  static void  AtForkParent(void)  { +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + +    UNLOCK_NOTIFIER_TSD;      UNLOCK_NOTIFIER;      UNLOCK_NOTIFIER_INIT;  } @@ -1248,16 +1979,25 @@ AtForkChild(void)  {      ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    UNLOCK_NOTIFIER_TSD;      UNLOCK_NOTIFIER;      UNLOCK_NOTIFIER_INIT;      if (tsdPtr->runLoop) {  	tsdPtr->runLoop = NULL; -	CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); -	CFRelease(tsdPtr->runLoopSource); +	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 = 0; +	notifierCount = 1; +	notifierThreadRunning = 0;  	/*  	 * Assume that the return value of Tcl_InitNotifier in the child will @@ -1268,11 +2008,23 @@ AtForkChild(void)  	 * Tcl_AlertNotifier may break in the child.  	 */ -	Tcl_InitNotifier(); +	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 */  /* | 
