From 3a59859d7ab015429c899f51da5ab7f456537a72 Mon Sep 17 00:00:00 2001 From: das Date: Fri, 10 Apr 2009 18:02:41 +0000 Subject: * macosx/tclMacOSXNotify.c: revise CoreFoundation notifier to allow * unix/tclUnixChan.c: embedding into applications that * unix/tclUnixEvent.c: already have a CFRunLoop running and want to run the tcl event loop via Tcl_ServiceModeHook(TCL_SERVICE_ALL). * macosx/tclMacOSXNotify.c: add CFRunLoop based Tcl_Sleep() and * unix/tclUnixChan.c: TclUnixWaitForFile() implementations * unix/tclUnixEvent.c: and disable select() based ones in CoreFoundation builds. * unix/tclUnixNotify.c: simplify, sync with tclMacOSXNotify.c. * generic/tclInt.decls: add TclMacOSXNotifierAddRunLoopMode() * generic/tclIntPlatDecls.h: internal API, regen. * generic/tclStubInit.c: --- ChangeLog | 28 +- generic/tclInt.decls | 5 +- generic/tclIntPlatDecls.h | 13 +- generic/tclStubInit.c | 3 +- macosx/tclMacOSXNotify.c | 1676 +++++++++++++++++++++++++++++++-------------- unix/tclUnixChan.c | 5 +- unix/tclUnixEvent.c | 5 +- unix/tclUnixNotfy.c | 50 +- 8 files changed, 1218 insertions(+), 567 deletions(-) diff --git a/ChangeLog b/ChangeLog index f80471d..1144093 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,8 +18,32 @@ 2008-04-10 Daniel Steffen - * macosx/tclMacOSXBundle.c: on Mac OS X 10.4 and later, replace - deprecated NSModule API by dlfcn API. + * macosx/tclMacOSXNotify.c: revise CoreFoundation notifier to allow + * unix/tclUnixChan.c: embedding into applications that + * unix/tclUnixEvent.c: already have a CFRunLoop running and + want to run the tcl event loop via + Tcl_ServiceModeHook(TCL_SERVICE_ALL). + + * macosx/tclMacOSXNotify.c: add CFRunLoop based Tcl_Sleep() and + * unix/tclUnixChan.c: TclUnixWaitForFile() implementations + * unix/tclUnixEvent.c: and disable select() based ones in + CoreFoundation builds. + + * unix/tclUnixNotify.c: simplify, sync with tclMacOSXNotify.c. + + * generic/tclInt.decls: add TclMacOSXNotifierAddRunLoopMode() + * generic/tclIntPlatDecls.h: internal API, regen. + * generic/tclStubInit.c: + + * unix/configure.in (Darwin): use Darwin SUSv3 extensions if + available; remove /Network locations + from default tcl package search path + (NFS mounted locations and thus slow). + * unix/configure: autoconf-2.59 + * unix/tclConfig.h.in: autoheader-2.59 + + * macosx/tclMacOSXBundle.c: on Mac OS X 10.4 and later, replace + deprecated NSModule API by dlfcn API. 2008-04-09 Kevin B. Kenny diff --git a/generic/tclInt.decls b/generic/tclInt.decls index ccc568f..ecd6196 100644 --- a/generic/tclInt.decls +++ b/generic/tclInt.decls @@ -13,7 +13,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: tclInt.decls,v 1.121 2008/01/23 17:31:42 dgp Exp $ +# RCS: @(#) $Id: tclInt.decls,v 1.121.2.1 2009/04/10 18:02:42 das Exp $ library tcl @@ -1145,3 +1145,6 @@ declare 18 macosx { CONST char *fileName, Tcl_StatBuf *statBufPtr, Tcl_GlobTypeData *types) } +declare 19 macosx { + void TclMacOSXNotifierAddRunLoopMode(CONST void *runLoopMode) +} diff --git a/generic/tclIntPlatDecls.h b/generic/tclIntPlatDecls.h index 38563b1..a89c8f0 100644 --- a/generic/tclIntPlatDecls.h +++ b/generic/tclIntPlatDecls.h @@ -9,7 +9,7 @@ * Copyright (c) 1998-1999 by Scriptics Corporation. * All rights reserved. * - * RCS: @(#) $Id: tclIntPlatDecls.h,v 1.32 2007/12/13 15:23:18 dgp Exp $ + * RCS: @(#) $Id: tclIntPlatDecls.h,v 1.32.2.1 2009/04/10 18:02:42 das Exp $ */ #ifndef _TCLINTPLATDECLS @@ -368,6 +368,12 @@ EXTERN int TclMacOSXMatchType (Tcl_Interp * interp, Tcl_StatBuf * statBufPtr, Tcl_GlobTypeData * types); #endif +#ifndef TclMacOSXNotifierAddRunLoopMode_TCL_DECLARED +#define TclMacOSXNotifierAddRunLoopMode_TCL_DECLARED +/* 19 */ +EXTERN void TclMacOSXNotifierAddRunLoopMode ( + CONST void * runLoopMode); +#endif #endif /* MACOSX */ typedef struct TclIntPlatStubs { @@ -443,6 +449,7 @@ typedef struct TclIntPlatStubs { int (*tclMacOSXSetFileAttribute) (Tcl_Interp * interp, int objIndex, Tcl_Obj * fileName, Tcl_Obj * attributePtr); /* 16 */ int (*tclMacOSXCopyFileAttributes) (CONST char * src, CONST char * dst, CONST Tcl_StatBuf * statBufPtr); /* 17 */ int (*tclMacOSXMatchType) (Tcl_Interp * interp, CONST char * pathName, CONST char * fileName, Tcl_StatBuf * statBufPtr, Tcl_GlobTypeData * types); /* 18 */ + void (*tclMacOSXNotifierAddRunLoopMode) (CONST void * runLoopMode); /* 19 */ #endif /* MACOSX */ } TclIntPlatStubs; @@ -697,6 +704,10 @@ extern TclIntPlatStubs *tclIntPlatStubsPtr; #define TclMacOSXMatchType \ (tclIntPlatStubsPtr->tclMacOSXMatchType) /* 18 */ #endif +#ifndef TclMacOSXNotifierAddRunLoopMode +#define TclMacOSXNotifierAddRunLoopMode \ + (tclIntPlatStubsPtr->tclMacOSXNotifierAddRunLoopMode) /* 19 */ +#endif #endif /* MACOSX */ #endif /* defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) */ diff --git a/generic/tclStubInit.c b/generic/tclStubInit.c index f663610..18c4f44 100644 --- a/generic/tclStubInit.c +++ b/generic/tclStubInit.c @@ -8,7 +8,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclStubInit.c,v 1.150 2008/01/23 17:31:42 dgp Exp $ + * RCS: @(#) $Id: tclStubInit.c,v 1.150.2.1 2009/04/10 18:02:42 das Exp $ */ #include "tclInt.h" @@ -409,6 +409,7 @@ TclIntPlatStubs tclIntPlatStubs = { TclMacOSXSetFileAttribute, /* 16 */ TclMacOSXCopyFileAttributes, /* 17 */ TclMacOSXMatchType, /* 18 */ + TclMacOSXNotifierAddRunLoopMode, /* 19 */ #endif /* MACOSX */ }; diff --git a/macosx/tclMacOSXNotify.c b/macosx/tclMacOSXNotify.c index 0cd8eac..a45741d 100644 --- a/macosx/tclMacOSXNotify.c +++ b/macosx/tclMacOSXNotify.c @@ -7,12 +7,12 @@ * * Copyright (c) 1995-1997 Sun Microsystems, Inc. * Copyright 2001, Apple Computer, Inc. - * Copyright (c) 2005-2008 Daniel A. Steffen + * Copyright (c) 2005-2009 Daniel A. Steffen * * 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.18 2008/03/11 22:24:17 das Exp $ + * RCS: @(#) $Id: tclMacOSXNotify.c,v 1.18.2.1 2009/04/10 18:02:42 das Exp $ */ #include "tclInt.h" @@ -21,133 +21,12 @@ #include #include +/* #define TCL_MAC_DEBUG_NOTIFIER 1 */ + extern TclStubs tclStubs; extern Tcl_NotifierProcs tclOriginalNotifier; /* - * 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. */ - 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 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. */ - 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. */ -} 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 */ - -/* * 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 @@ -236,77 +115,271 @@ static OSSpinLock notifierLock = SPINLOCK_INIT; #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)) { \ +#define SpinLockLockDbg(p) if (!SpinLockTry(p)) { \ Tcl_WideInt s = TclpGetWideClicks(), e; \ SpinLockLock(p); e = TclpGetWideClicks(); \ - fprintf(notifierLog, "tclMacOSXNotify.c:" \ - "%4d: thread %10p waited on %s for " \ - "%8llu ns\n", __LINE__, pthread_self(), \ + TclMacOSXNotifierDbgMsg("waited on %s for %8.0f ns", \ #p, TclpWideClicksToNanoseconds(e-s)); \ - fflush(notifierLog); \ } #undef LOCK_NOTIFIER_INIT #define LOCK_NOTIFIER_INIT SpinLockLockDbg(¬ifierInitLock) #undef LOCK_NOTIFIER #define LOCK_NOTIFIER SpinLockLockDbg(¬ifierLock) -static FILE *notifierLog = stderr; +#undef LOCK_NOTIFIER_TSD +#define LOCK_NOTIFIER_TSD SpinLockLockDbg(&tsdPtr->tsdLock) +#include +static FILE *notifierLog = NULL; #ifndef NOTIFIER_LOG #define NOTIFIER_LOG "/tmp/tclMacOSXNotify.log" #endif -#define OPEN_NOTIFIER_LOG if (notifierLog == stderr) { \ +#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 != stderr) { \ +#define CLOSE_NOTIFIER_LOG if (notifierLog) { \ + /*asl_remove_log_file(NULL, \ + fileno(notifierLog)); \ + TclMacOSXNotifierDbgMsg("close log");*/ \ fclose(notifierLog); \ - notifierLog = stderr; \ + notifierLog = NULL; \ } +#define ENABLE_ASL if (notifierLog) { \ + /*tsdPtr->asl = asl_open(NULL, "com.apple.console", ASL_OPT_NO_REMOTE); \ + asl_set_filter(tsdPtr->asl, \ + ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); \ + asl_add_log_file(tsdPtr->asl, \ + fileno(notifierLog));*/ \ + } +#define DISABLE_ASL /*if (tsdPtr->asl) { \ + if (notifierLog) { \ + asl_remove_log_file(tsdPtr->asl, \ + fileno(notifierLog)); \ + } \ + asl_close(tsdPtr->asl); \ + }*/ +#define ASLCLIENT /*aslclient asl*/ +#else +#define TclMacOSXNotifierDbgMsg(m, ...) +#define OPEN_NOTIFIER_LOG +#define CLOSE_NOTIFIER_LOG +#define ENABLE_ASL +#define DISABLE_ASL #endif /* TCL_MAC_DEBUG_NOTIFIER */ /* - * 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. + * 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 */ + /* 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; + /* Adds/removes this thread from waitingList + * when the CFRunLoop starts/stops. */ + CFRunLoopTimerRef runLoopTimer; + /* Wakes up CFRunLoop after given timeout when + * running embedded. */ + /* End tsdLock section */ + CFTimeInterval waitTime; /* runLoopTimer wait time when running + * embedded. */ +#ifdef TCL_MAC_DEBUG_NOTIFIER + ASLCLIENT; +#endif +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * 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. */ -#define POLL_WANT 0x1 -#define POLL_DONE 0x2 +static int notifierThreadRunning; /* * This is the thread ID of the notifier thread that does select. + * Only valid when notifierThreadRunning is non-zero. + * + * You must hold the notifierInitLock before accessing this variable. */ static pthread_t notifierThread; /* - * Custom run loop mode containing only the run loop source for the - * notifier thread. + * Custom runloop mode for running with only the runloop source for the + * notifier thread */ #ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE -#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" +#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" #endif #ifdef __CONSTANT_CFSTRINGS__ -#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) +#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) #else static CFStringRef tclEventsOnlyRunLoopMode = NULL; #endif /* + * CFTimeInterval to wait forever. + */ + +#define CF_TIMEINTERVAL_FOREVER 5.05e8 + +/* * Static routines defined in this file. */ +static void StartNotifierThread(void); static void NotifierThreadProc(ClientData clientData) - __attribute__ ((__noreturn__)); + __attribute__ ((__noreturn__)); static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); +static void TimerWakeUp(CFRunLoopTimerRef timer, void *info); +static void QueueFileEvents(void *info); +static void UpdateWaitingListAndServiceEvents(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); +static int OnOffWaitingList(ThreadSpecificData *tsdPtr, int onList, + int signalNotifier); #ifdef HAVE_PTHREAD_ATFORK static int atForkInit = 0; @@ -316,8 +389,8 @@ static void AtForkChild(void); #if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 /* Support for weakly importing pthread_atfork. */ #define WEAK_IMPORT_PTHREAD_ATFORK -extern int pthread_atfork(void (*prepare)(void), void (*parent)(void), - void (*child)(void)) WEAK_IMPORT_ATTRIBUTE; +extern int pthread_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void)) WEAK_IMPORT_ATTRIBUTE; #endif /* HAVE_WEAK_IMPORT */ /* * On Darwin 9 and later, it is not possible to call CoreFoundation after @@ -351,14 +424,15 @@ MODULE_SCOPE long tclMacOSXDarwinRelease; ClientData Tcl_InitNotifier(void) { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; - tsdPtr->eventReady = 0; + tsdPtr = TCL_TSD_INIT(&dataKey); #ifdef WEAK_IMPORT_SPINLOCKLOCK /* * Initialize support for weakly imported spinlock API. */ + if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) { Tcl_Panic("Tcl_InitNotifier: pthread_once failed"); } @@ -378,17 +452,40 @@ Tcl_InitNotifier(void) CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFRunLoopSourceRef runLoopSource; CFRunLoopSourceContext runLoopSourceContext; + CFRunLoopObserverContext runLoopObserverContext; + CFRunLoopObserverRef runLoopObserver; 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); + CFRunLoopAddObserver(runLoop, runLoopObserver, + tclEventsOnlyRunLoopMode); + tsdPtr->runLoop = runLoop; + tsdPtr->runLoopSource = runLoopSource; + tsdPtr->runLoopObserver = runLoopObserver; + tsdPtr->runLoopTimer = NULL; + tsdPtr->waitTime = CF_TIMEINTERVAL_FOREVER; + tsdPtr->tsdLock = SPINLOCK_INIT; } LOCK_NOTIFIER_INIT; @@ -404,6 +501,7 @@ Tcl_InitNotifier(void) #endif !atForkInit) { int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild); + if (result) { Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed"); } @@ -424,12 +522,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]; @@ -437,15 +537,14 @@ Tcl_InitNotifier(void) /* * Create notifier thread lazily in Tcl_WaitForEvent() to avoid - * interfering with fork() followed immediately by execve() - * (cannot execve() when more than one thread is present). + * interfering with fork() followed immediately by execve() (we cannot + * execve() when more than one thread is present). */ - notifierThread = 0; -#ifdef TCL_MAC_DEBUG_NOTIFIER + notifierThreadRunning = 0; OPEN_NOTIFIER_LOG; -#endif } + ENABLE_ASL; notifierCount++; UNLOCK_NOTIFIER_INIT; @@ -455,17 +554,93 @@ Tcl_InitNotifier(void) /* *---------------------------------------------------------------------- * - * Tcl_FinalizeNotifier -- + * TclMacOSXNotifierAddRunLoopMode -- * - * This function is called to cleanup the notifier state before a thread - * is terminated. + * Add the tcl notifier RunLoop source, observer and timer (if any) + * to the given RunLoop mode. * * Results: * None. * * Side effects: - * May terminate the background notifier thread if this is the last - * notifier instance. + * 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 + * is terminated. + * + * Results: + * None. + * + * Side effects: + * May terminate the background notifier thread if this is the last + * notifier instance. * *---------------------------------------------------------------------- */ @@ -474,10 +649,13 @@ void Tcl_FinalizeNotifier( ClientData clientData) /* Not used. */ { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; + + tsdPtr = TCL_TSD_INIT(&dataKey); LOCK_NOTIFIER_INIT; notifierCount--; + DISABLE_ASL; /* * If this is the last thread to use the notifier, close the notifier pipe @@ -485,55 +663,60 @@ 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; -#ifdef TCL_MAC_DEBUG_NOTIFIER + close(receivePipe); + triggerPipe = -1; + } CLOSE_NOTIFIER_LOG; -#endif } 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; + if (tsdPtr->runLoopTimer) { + CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer); + CFRelease(tsdPtr->runLoopTimer); + tsdPtr->runLoopTimer = NULL; + } } - UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_TSD; } /* @@ -559,15 +742,14 @@ void Tcl_AlertNotifier( ClientData clientData) { - ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; + ThreadSpecificData *tsdPtr = clientData; - LOCK_NOTIFIER; + LOCK_NOTIFIER_TSD; if (tsdPtr->runLoop) { - tsdPtr->eventReady = 1; CFRunLoopSourceSignal(tsdPtr->runLoopSource); CFRunLoopWakeUp(tsdPtr->runLoop); } - UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_TSD; } /* @@ -575,15 +757,13 @@ Tcl_AlertNotifier( * * Tcl_SetTimer -- * - * This function sets the current notifier timer value. This interface is - * not implemented in this notifier because we are always running inside - * of Tcl_DoOneEvent. + * This function sets the current notifier timer value. * * Results: * None. * * Side effects: - * None. + * Replaces any previous timer. * *---------------------------------------------------------------------- */ @@ -592,15 +772,58 @@ void Tcl_SetTimer( Tcl_Time *timePtr) /* Timeout value, may be NULL. */ { - /* - * The interval timer doesn't do anything in this implementation, because - * the only event loop is via Tcl_DoOneEvent, which passes timeout values - * to Tcl_WaitForEvent. - */ + ThreadSpecificData *tsdPtr; + CFRunLoopTimerRef runLoopTimer; + CFTimeInterval waitTime; if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { tclStubs.tcl_SetTimer(timePtr); + return; + } + + tsdPtr = TCL_TSD_INIT(&dataKey); + runLoopTimer = tsdPtr->runLoopTimer; + if (!runLoopTimer) { + return; + } + if (timePtr) { + Tcl_Time vTime = *timePtr; + + if (vTime.sec != 0 || vTime.usec != 0) { + tclScaleTimeProcPtr(&vTime, tclTimeClientData); + waitTime = vTime.sec + 1.0e-6 * vTime.usec; + } else { + waitTime = 0; + } + } else { + waitTime = CF_TIMEINTERVAL_FOREVER; } + tsdPtr->waitTime = waitTime; + CFRunLoopTimerSetNextFireDate(runLoopTimer, + CFAbsoluteTimeGetCurrent() + waitTime); +} + +/* + *---------------------------------------------------------------------- + * + * TimerWakeUp -- + * + * CFRunLoopTimer callback. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +TimerWakeUp( + CFRunLoopTimerRef timer, + void *info) +{ } /* @@ -624,6 +847,23 @@ Tcl_ServiceModeHook( int mode) /* Either TCL_SERVICE_ALL, or * TCL_SERVICE_NONE. */ { + ThreadSpecificData *tsdPtr; + + tsdPtr = TCL_TSD_INIT(&dataKey); + + if (mode == TCL_SERVICE_ALL && !tsdPtr->runLoopTimer) { + if (!tsdPtr->runLoop) { + Tcl_Panic("Tcl_ServiceModeHook: Notifier not initialized"); + } + tsdPtr->runLoopTimer = CFRunLoopTimerCreate(NULL, + CFAbsoluteTimeGetCurrent() + CF_TIMEINTERVAL_FOREVER, + CF_TIMEINTERVAL_FOREVER, 0, 0, TimerWakeUp, NULL); + if (tsdPtr->runLoopTimer) { + CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, + kCFRunLoopCommonModes); + StartNotifierThread(); + } + } } /* @@ -653,7 +893,7 @@ Tcl_CreateFileHandler( * event. */ ClientData clientData) /* Arbitrary data to pass to proc. */ { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; FileHandler *filePtr; if (tclStubs.tcl_CreateFileHandler != @@ -662,6 +902,8 @@ Tcl_CreateFileHandler( return; } + tsdPtr = TCL_TSD_INIT(&dataKey); + for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; filePtr = filePtr->nextPtr) { if (filePtr->fd == fd) { @@ -669,7 +911,7 @@ Tcl_CreateFileHandler( } } if (filePtr == NULL) { - filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); + filePtr = (FileHandler *) ckalloc(sizeof(FileHandler)); filePtr->fd = fd; filePtr->readyMask = 0; filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; @@ -683,6 +925,7 @@ Tcl_CreateFileHandler( * Update the check masks for this file. */ + LOCK_NOTIFIER_TSD; if (mask & TCL_READABLE) { FD_SET(fd, &(tsdPtr->checkMasks.readable)); } else { @@ -701,379 +944,730 @@ Tcl_CreateFileHandler( if (tsdPtr->numFdBits <= fd) { tsdPtr->numFdBits = fd+1; } + UNLOCK_NOTIFIER_TSD; +} + +/* + *---------------------------------------------------------------------- + * + * 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 (tclStubs.tcl_DeleteFileHandler != + tclOriginalNotifier.deleteFileHandlerProc) { + tclStubs.tcl_DeleteFileHandler(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((char *) filePtr); +} + +/* + *---------------------------------------------------------------------- + * + * 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; +} + +/* + *---------------------------------------------------------------------- + * + * 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( + Tcl_Time *timePtr) /* Maximum block time, or NULL. */ +{ + int result, polling; + CFTimeInterval waitTime; + CFStringRef runLoopMode; + SInt32 runLoopStatus; + ThreadSpecificData *tsdPtr; + + if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { + return tclStubs.tcl_WaitForEvent(timePtr); + } + result = -1; + polling = 0; + waitTime = CF_TIMEINTERVAL_FOREVER; + tsdPtr = TCL_TSD_INIT(&dataKey); + + 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 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 (tsdPtr->runLoopRunning) { + runLoopMode = tclEventsOnlyRunLoopMode; + } else { + runLoopMode = kCFRunLoopDefaultMode; + tsdPtr->runLoopRunning = 1; + } + runLoopStatus = CFRunLoopRunInMode(runLoopMode, waitTime, TRUE); + if (runLoopMode == kCFRunLoopDefaultMode) { + tsdPtr->runLoopRunning = 0; + } + + LOCK_NOTIFIER_TSD; + tsdPtr->polling = 0; + UNLOCK_NOTIFIER_TSD; + switch (runLoopStatus) { + case kCFRunLoopRunFinished: + Tcl_Panic("Tcl_WaitForEvent: CFRunLoop finished"); + break; + case kCFRunLoopRunTimedOut: + QueueFileEvents(tsdPtr); + result = 0; + break; + case kCFRunLoopRunStopped: + case kCFRunLoopRunHandledSource: + result = tsdPtr->runLoopSourcePerformed ? 0 : 1; + break; + } + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * QueueFileEvents -- + * + * CFRunLoopSource callback for queueing file events. + * + * Results: + * None. + * + * Side effects: + * Queues file events that are detected by the select. + * + *---------------------------------------------------------------------- + */ + +static void +QueueFileEvents( + void *info) +{ + SelectMasks readyMasks; + FileHandler *filePtr; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 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 = (FileHandlerEvent *) + ckalloc(sizeof(FileHandlerEvent)); + 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. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateWaitingListAndServiceEvents( + CFRunLoopObserverRef observer, + CFRunLoopActivity activity, + void *info) +{ + ThreadSpecificData *tsdPtr = (ThreadSpecificData*) info; + + switch (activity) { + case kCFRunLoopEntry: + tsdPtr->runLoopNestingLevel++; + if (tsdPtr->runLoopNestingLevel == 1 && !tsdPtr->sleeping && + (tsdPtr->numFdBits > 0 || tsdPtr->polling)) { + LOCK_NOTIFIER; + OnOffWaitingList(tsdPtr, 1, 1); + UNLOCK_NOTIFIER; + } + break; + case kCFRunLoopExit: + if (tsdPtr->runLoopNestingLevel == 1 && !tsdPtr->sleeping && + (tsdPtr->numFdBits > 0 || tsdPtr->polling)) { + LOCK_NOTIFIER; + OnOffWaitingList(tsdPtr, 0, 1); + UNLOCK_NOTIFIER; + } + tsdPtr->runLoopNestingLevel--; + break; + case kCFRunLoopBeforeWaiting: + if (!tsdPtr->sleeping && tsdPtr->runLoopTimer && + (tsdPtr->runLoopNestingLevel > 1 || !tsdPtr->runLoopRunning)) { + while (Tcl_ServiceAll() && tsdPtr->waitTime == 0) {} + } + break; + } } /* *---------------------------------------------------------------------- * - * Tcl_DeleteFileHandler -- + * OnOffWaitingList -- * - * Cancel a previously-arranged callback arrangement for a file. + * Add/remove the specified thread to/from the global waitingList + * and optionally signal the notifier. + * + * !!! Requires notifierLock to be held !!! * * Results: - * None. + * Boolean indicating whether the waitingList was changed. * * Side effects: - * If a callback was previously registered on file, remove it. + * None. * *---------------------------------------------------------------------- */ -void -Tcl_DeleteFileHandler( - int fd) /* Stream id for which to remove callback - * function. */ +static int +OnOffWaitingList( + ThreadSpecificData *tsdPtr, + int onList, + int signalNotifier) { - FileHandler *filePtr, *prevPtr; - int i; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - if (tclStubs.tcl_DeleteFileHandler != - tclOriginalNotifier.deleteFileHandlerProc) { - tclStubs.tcl_DeleteFileHandler(fd); - return; - } - - /* - * 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; - } - } - - /* - * 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)); + int changeWaitingList; +#ifdef TCL_MAC_DEBUG_NOTIFIER + if(SpinLockTry(¬ifierLock)) { + Tcl_Panic("OnOffWaitingList: notifierLock unlocked"); } - - /* - * Find current max fd. - */ - - if (fd+1 == tsdPtr->numFdBits) { - tsdPtr->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; - break; +#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); } } - /* - * Clean up information in the callback record. - */ - - if (prevPtr == NULL) { - tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; - } else { - prevPtr->nextPtr = filePtr->nextPtr; - } - ckfree((char *) filePtr); + return changeWaitingList; } /* *---------------------------------------------------------------------- * - * FileHandlerEventProc -- + * Tcl_Sleep -- * - * 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. + * Delay execution for the specified number of milliseconds. * * 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. + * None. * * Side effects: - * Whatever the file handler's callback function does. + * Time passes. * *---------------------------------------------------------------------- */ -static int -FileHandlerEventProc( - Tcl_Event *evPtr, /* Event to service. */ - int flags) /* Flags that indicate what events to handle, - * such as TCL_FILE_EVENTS. */ +void +Tcl_Sleep( + int ms) /* Number of milliseconds to sleep. */ { - int mask; - FileHandler *filePtr; - FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; - ThreadSpecificData *tsdPtr; + Tcl_Time vdelay; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - if (!(flags & TCL_FILE_EVENTS)) { - return 0; + if (ms <= 0) { + return; } /* - * 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. + * TIP #233: Scale from virtual time to real-time. */ - tsdPtr = TCL_TSD_INIT(&dataKey); - for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; - filePtr = filePtr->nextPtr) { - if (filePtr->fd != fileEvPtr->fd) { - continue; - } + vdelay.sec = ms / 1000; + vdelay.usec = (ms % 1000) * 1000; + tclScaleTimeProcPtr(&vdelay, tclTimeClientData); - /* - * 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) { - (*filePtr->proc)(filePtr->clientData, mask); + 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; + } } - break; + 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)); } - return 1; } /* *---------------------------------------------------------------------- * - * Tcl_WaitForEvent -- + * TclUnixWaitForFile -- * - * 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. + * This function waits synchronously for a file to become readable or + * writable, with an optional timeout. * * Results: - * Returns -1 if the select would block forever, otherwise returns 0. + * 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: - * Queues file events that are detected by the select. + * Time passes. * *---------------------------------------------------------------------- */ int -Tcl_WaitForEvent( - Tcl_Time *timePtr) /* Maximum block time, or NULL. */ +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. */ { - FileHandler *filePtr; - FileHandlerEvent *fileEvPtr; - int mask; - Tcl_Time myTime; - int waitForFiles; - Tcl_Time *myTimePtr; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { - return tclStubs.tcl_WaitForEvent(timePtr); - } + Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */ + struct timeval blockTime, *timeoutPtr; + int index, numFound, result = 0; + fd_mask bit; + fd_mask readyMasks[3*MASK_SIZE]; + fd_mask *maskp[3]; /* This array reflects the readable/writable + * conditions that were found to exist by the + * last call to select. */ - if (timePtr != NULL) { - /* - * TIP #233 (Virtualized Time). Is virtual time in effect? And do we - * actually have something to scale? If yes to both then we call the - * handler to do this scaling. - */ +#define SET_BITS(var, bits) ((var) |= (bits)) +#define CLEAR_BITS(var, bits) ((var) &= ~(bits)) - myTime.sec = timePtr->sec; - myTime.usec = timePtr->usec; + /* + * If there is a non-zero finite timeout, compute the time when we give + * up. + */ - if (myTime.sec != 0 || myTime.usec != 0) { - (*tclScaleTimeProcPtr) (&myTime, tclTimeClientData); + 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; } - - myTimePtr = &myTime; + timeoutPtr = &blockTime; + } else if (timeout == 0) { + timeoutPtr = &blockTime; + blockTime.tv_sec = 0; + blockTime.tv_usec = 0; } else { - myTimePtr = NULL; + timeoutPtr = NULL; } /* - * Start notifier thread if necessary. + * Initialize the ready masks and compute the mask offsets. */ - LOCK_NOTIFIER_INIT; - if (!notifierCount) { - Tcl_Panic("Tcl_WaitForEvent: notifier not initialized"); - } - if (!notifierThread) { - int result; - pthread_attr_t attr; - - pthread_attr_init(&attr); - pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_attr_setstacksize(&attr, 60 * 1024); - result = pthread_create(¬ifierThread, &attr, - (void * (*)(void *))NotifierThreadProc, NULL); - pthread_attr_destroy(&attr); - if (result || !notifierThread) { - Tcl_Panic("Tcl_WaitForEvent: unable to start notifier thread"); - } + if (fd >= FD_SETSIZE) { + Tcl_Panic("TclWaitForFile can't handle file id %d", fd); + /* must never get here, or readyMasks overrun will occur below */ } - UNLOCK_NOTIFIER_INIT; + memset(readyMasks, 0, 3*MASK_SIZE*sizeof(fd_mask)); + index = fd / (NBBY*sizeof(fd_mask)); + bit = ((fd_mask)1) << (fd % (NBBY*sizeof(fd_mask))); /* - * Place this thread on the list of interested threads, signal the - * notifier thread, and wait for a response or a timeout. + * Loop in a mini-event loop of our own, waiting for either the file to + * become ready or a timeout to occur. */ - LOCK_NOTIFIER; - if (!tsdPtr->runLoop) { - Tcl_Panic("Tcl_WaitForEvent: CFRunLoop not initialized"); - } - 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. - */ - - waitForFiles = 1; - tsdPtr->pollState = POLL_WANT; - myTimePtr = NULL; - } else { - tsdPtr->pollState = 0; - } + 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 (waitForFiles) { /* - * Add the ThreadSpecificData structure of this thread to the list of - * ThreadSpecificData structures of all threads that are waiting on - * file events. + * Set the appropriate bit in the ready masks for the fd. */ - tsdPtr->nextPtr = waitingListPtr; - if (waitingListPtr) { - waitingListPtr->prevPtr = tsdPtr; + if (mask & TCL_READABLE) { + readyMasks[index] |= bit; } - tsdPtr->prevPtr = 0; - waitingListPtr = tsdPtr; - tsdPtr->onList = 1; - - write(triggerPipe, "", 1); - } - - FD_ZERO(&(tsdPtr->readyMasks.readable)); - FD_ZERO(&(tsdPtr->readyMasks.writable)); - FD_ZERO(&(tsdPtr->readyMasks.exceptional)); - - if (!tsdPtr->eventReady) { - CFTimeInterval waitTime; - CFStringRef runLoopMode; - - if (myTimePtr == NULL) { - waitTime = 1.0e10; /* Wait forever, as per CFRunLoop.c */ - } else { - waitTime = myTimePtr->sec + 1.0e-6 * myTimePtr->usec; + if (mask & TCL_WRITABLE) { + (readyMasks+MASK_SIZE)[index] |= bit; } - /* - * 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 (mask & TCL_EXCEPTION) { + (readyMasks+2*(MASK_SIZE))[index] |= bit; } - 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. + * Wait for the event or a timeout. */ - 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; - write(triggerPipe, "", 1); - } - - /* - * Queue all detected file events before returning. - */ - - for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); - filePtr = filePtr->nextPtr) { + /* + * This is needed to satisfy GCC 3.3's strict aliasing rules. + */ - mask = 0; - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.readable))) { - mask |= TCL_READABLE; - } - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.writable))) { - mask |= TCL_WRITABLE; + maskp[0] = &readyMasks[0]; + maskp[1] = &readyMasks[MASK_SIZE]; + maskp[2] = &readyMasks[2*MASK_SIZE]; + numFound = select(fd+1, (SELECT_MASK *) maskp[0], + (SELECT_MASK *) maskp[1], + (SELECT_MASK *) maskp[2], timeoutPtr); + if (numFound == 1) { + if (readyMasks[index] & bit) { + SET_BITS(result, TCL_READABLE); + } + if ((readyMasks+MASK_SIZE)[index] & bit) { + SET_BITS(result, TCL_WRITABLE); + } + if ((readyMasks+2*(MASK_SIZE))[index] & bit) { + SET_BITS(result, TCL_EXCEPTION); + } + result &= mask; + if (result) { + break; + } } - if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.exceptional))) { - mask |= TCL_EXCEPTION; + if (timeout == 0) { + break; } - - if (!mask) { + 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; } /* @@ -1105,11 +1699,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]; @@ -1127,9 +1718,10 @@ NotifierThreadProc( * notifiers. */ - LOCK_NOTIFIER; timePtr = NULL; + LOCK_NOTIFIER; for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { + LOCK_NOTIFIER_TSD; for (i = tsdPtr->numFdBits-1; i >= 0; --i) { if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))) { FD_SET(i, &readableMask); @@ -1144,13 +1736,9 @@ NotifierThreadProc( if (tsdPtr->numFdBits > numFdBits) { numFdBits = tsdPtr->numFdBits; } - if (tsdPtr->pollState & POLL_WANT) { - /* - * Here we make sure we go through select() with the same mask - * bits that were present when the thread tried to poll. - */ - - tsdPtr->pollState |= POLL_DONE; + polling = tsdPtr->polling; + UNLOCK_NOTIFIER_TSD; + if ((tsdPtr->polled = polling)) { timePtr = &poll; } } @@ -1180,48 +1768,53 @@ NotifierThreadProc( LOCK_NOTIFIER; for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { - found = 0; + int found = 0; + SelectMasks readyMasks, checkMasks; + + LOCK_NOTIFIER_TSD; + FD_COPY(&(tsdPtr->checkMasks.readable), &checkMasks.readable); + FD_COPY(&(tsdPtr->checkMasks.writable), &checkMasks.writable); + FD_COPY(&(tsdPtr->checkMasks.exceptional), &checkMasks.exceptional); + UNLOCK_NOTIFIER_TSD; + found = tsdPtr->polled; + FD_ZERO(&readyMasks.readable); + FD_ZERO(&readyMasks.writable); + FD_ZERO(&readyMasks.exceptional); for (i = tsdPtr->numFdBits-1; i >= 0; --i) { - if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) + if (FD_ISSET(i, &checkMasks.readable) && FD_ISSET(i, &readableMask)) { - FD_SET(i, &(tsdPtr->readyMasks.readable)); + FD_SET(i, &readyMasks.readable); found = 1; } - if (FD_ISSET(i, &(tsdPtr->checkMasks.writable)) + if (FD_ISSET(i, &checkMasks.writable) && FD_ISSET(i, &writableMask)) { - FD_SET(i, &(tsdPtr->readyMasks.writable)); + FD_SET(i, &readyMasks.writable); found = 1; } - if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional)) + if (FD_ISSET(i, &checkMasks.exceptional) && FD_ISSET(i, &exceptionalMask)) { - FD_SET(i, &(tsdPtr->readyMasks.exceptional)); + FD_SET(i, &readyMasks.exceptional); found = 1; } } - if (found || (tsdPtr->pollState & POLL_DONE)) { - tsdPtr->eventReady = 1; - if (tsdPtr->onList) { - /* - * Remove the ThreadSpecificData structure of this thread - * from the waiting list. This prevents us from - * continuously spining on select until the other threads - * runs and services the file event. - */ - - if (tsdPtr->prevPtr) { - tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; - } else { - waitingListPtr = tsdPtr->nextPtr; - } - if (tsdPtr->nextPtr) { - tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; - } - tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; - tsdPtr->onList = 0; - tsdPtr->pollState = 0; - } + if (found) { + /* + * Remove the ThreadSpecificData structure of this thread from + * the waiting list. This prevents us from spinning + * continuously on select until the other threads runs and + * services the file event. + */ + + OnOffWaitingList(tsdPtr, 0, 0); + + LOCK_NOTIFIER_TSD; + FD_COPY(&readyMasks.readable, &(tsdPtr->readyMasks.readable)); + FD_COPY(&readyMasks.writable, &(tsdPtr->readyMasks.writable)); + FD_COPY(&readyMasks.exceptional, &(tsdPtr->readyMasks.exceptional)); + UNLOCK_NOTIFIER_TSD; + tsdPtr->polled = 0; if (tsdPtr->runLoop) { CFRunLoopSourceSignal(tsdPtr->runLoopSource); CFRunLoopWakeUp(tsdPtr->runLoop); @@ -1273,8 +1866,11 @@ NotifierThreadProc( static void AtForkPrepare(void) { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + LOCK_NOTIFIER_INIT; LOCK_NOTIFIER; + LOCK_NOTIFIER_TSD; } /* @@ -1296,6 +1892,9 @@ AtForkPrepare(void) static void AtForkParent(void) { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + UNLOCK_NOTIFIER_TSD; UNLOCK_NOTIFIER; UNLOCK_NOTIFIER_INIT; } @@ -1321,18 +1920,25 @@ AtForkChild(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + UNLOCK_NOTIFIER_TSD; UNLOCK_NOTIFIER; UNLOCK_NOTIFIER_INIT; if (tsdPtr->runLoop) { tsdPtr->runLoop = NULL; if (!noCFafterFork) { CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); + CFRelease(tsdPtr->runLoopSource); + if (tsdPtr->runLoopTimer) { + CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer); + CFRelease(tsdPtr->runLoopTimer); + } } - CFRelease(tsdPtr->runLoopSource); tsdPtr->runLoopSource = NULL; + tsdPtr->runLoopTimer = NULL; } if (notifierCount > 0) { - notifierCount = 0; + notifierCount = 1; + notifierThreadRunning = 0; /* * Assume that the return value of Tcl_InitNotifier in the child will @@ -1350,6 +1956,16 @@ AtForkChild(void) } #endif /* HAVE_PTHREAD_ATFORK */ +#else /* HAVE_COREFOUNDATION */ + +void +TclMacOSXNotifierAddRunLoopMode( + CONST void *runLoopMode) +{ + Tcl_Panic("TclMacOSXNotifierAddRunLoopMode: " + "Tcl not built with CoreFoundation support"); +} + #endif /* HAVE_COREFOUNDATION */ /* diff --git a/unix/tclUnixChan.c b/unix/tclUnixChan.c index 629f9d0..cb3ba17 100644 --- a/unix/tclUnixChan.c +++ b/unix/tclUnixChan.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclUnixChan.c,v 1.93 2008/03/03 14:54:43 rmax Exp $ + * RCS: @(#) $Id: tclUnixChan.c,v 1.93.2.1 2009/04/10 18:02:42 das Exp $ */ #include "tclInt.h" /* Internal definitions for Tcl. */ @@ -2986,6 +2986,8 @@ Tcl_GetOpenFile( return TCL_ERROR; } +#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is + * in tclMacOSXNotify.c */ /* *---------------------------------------------------------------------- * @@ -3145,6 +3147,7 @@ TclUnixWaitForFile( } return result; } +#endif /* HAVE_COREFOUNDATION */ /* *---------------------------------------------------------------------- diff --git a/unix/tclUnixEvent.c b/unix/tclUnixEvent.c index 214bf4c..bc6aade 100644 --- a/unix/tclUnixEvent.c +++ b/unix/tclUnixEvent.c @@ -8,10 +8,12 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclUnixEvent.c,v 1.9 2005/11/02 23:26:50 dkf Exp $ + * RCS: @(#) $Id: tclUnixEvent.c,v 1.9.10.1 2009/04/10 18:02:42 das Exp $ */ #include "tclInt.h" +#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is + * in tclMacOSXNotify.c */ /* *---------------------------------------------------------------------- @@ -85,6 +87,7 @@ Tcl_Sleep( } } +#endif /* HAVE_COREFOUNDATION */ /* * Local Variables: * mode: c diff --git a/unix/tclUnixNotfy.c b/unix/tclUnixNotfy.c index a78cabd..916951f 100644 --- a/unix/tclUnixNotfy.c +++ b/unix/tclUnixNotfy.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclUnixNotfy.c,v 1.34 2008/03/11 22:23:50 das Exp $ + * RCS: @(#) $Id: tclUnixNotfy.c,v 1.34.2.1 2009/04/10 18:02:42 das Exp $ */ #include "tclInt.h" @@ -143,7 +143,7 @@ static ThreadSpecificData *waitingListPtr = NULL; * 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 notifierMutex lock before accessing this list. + * You must hold the notifierMutex lock before writing to the pipe. */ static int triggerPipe = -1; @@ -199,7 +199,7 @@ static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); * Initializes the platform specific notifier state. * * Results: - * Returns a handle to the notifier state for this thread.. + * Returns a handle to the notifier state for this thread. * * Side effects: * None. @@ -543,15 +543,17 @@ Tcl_DeleteFileHandler( */ if (fd+1 == tsdPtr->numFdBits) { - tsdPtr->numFdBits = 0; + int numFdBits = 0; + for (i = fd-1; i >= 0; i--) { if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) || FD_ISSET(i, &(tsdPtr->checkMasks.writable)) || FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) { - tsdPtr->numFdBits = i+1; + numFdBits = i+1; break; } } + tsdPtr->numFdBits = numFdBits; } /* @@ -664,10 +666,9 @@ Tcl_WaitForEvent( FileHandler *filePtr; FileHandlerEvent *fileEvPtr; int mask; - Tcl_Time myTime; + Tcl_Time vTime; #ifdef TCL_THREADS int waitForFiles; - Tcl_Time *myTimePtr; #else /* * Impl. notes: timeout & timeoutPtr are used if, and only if threads are @@ -696,22 +697,15 @@ Tcl_WaitForEvent( * handler to do this scaling. */ - myTime.sec = timePtr->sec; - myTime.usec = timePtr->usec; - - if (myTime.sec != 0 || myTime.usec != 0) { - (*tclScaleTimeProcPtr) (&myTime, tclTimeClientData); + if (timePtr->sec != 0 || timePtr->usec != 0) { + vTime = *timePtr; + (*tclScaleTimeProcPtr) (&vTime, tclTimeClientData); + timePtr = &vTime; } - -#ifdef TCL_THREADS - myTimePtr = &myTime; -#else - timeout.tv_sec = myTime.sec; - timeout.tv_usec = myTime.usec; - timeoutPtr = &timeout; -#endif /* TCL_THREADS */ - #ifndef TCL_THREADS + timeout.tv_sec = timePtr->sec; + timeout.tv_usec = timePtr->usec; + timeoutPtr = &timeout; } else if (tsdPtr->numFdBits == 0) { /* * If there are no threads, no timeout, and no fds registered, then @@ -722,11 +716,7 @@ Tcl_WaitForEvent( */ return -1; -#endif /* !TCL_THREADS */ } else { -#ifdef TCL_THREADS - myTimePtr = NULL; -#else timeoutPtr = NULL; #endif /* TCL_THREADS */ } @@ -739,8 +729,7 @@ Tcl_WaitForEvent( Tcl_MutexLock(¬ifierMutex); - waitForFiles = (tsdPtr->numFdBits > 0); - if (myTimePtr != NULL && myTimePtr->sec == 0 && (myTimePtr->usec == 0 + if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0 #if defined(__APPLE__) && defined(__LP64__) /* * On 64-bit Darwin, pthread_cond_timedwait() appears to have a bug @@ -748,7 +737,7 @@ Tcl_WaitForEvent( * has already been exceeded by the system time; as a workaround, * when given a very brief timeout, just do a poll. [Bug 1457797] */ - || myTimePtr->usec < 10 + || timePtr->usec < 10 #endif )) { /* @@ -761,8 +750,9 @@ Tcl_WaitForEvent( waitForFiles = 1; tsdPtr->pollState = POLL_WANT; - myTimePtr = NULL; + timePtr = NULL; } else { + waitForFiles = (tsdPtr->numFdBits > 0); tsdPtr->pollState = 0; } @@ -789,7 +779,7 @@ Tcl_WaitForEvent( FD_ZERO(&(tsdPtr->readyMasks.exceptional)); if (!tsdPtr->eventReady) { - Tcl_ConditionWait(&tsdPtr->waitCV, ¬ifierMutex, myTimePtr); + Tcl_ConditionWait(&tsdPtr->waitCV, ¬ifierMutex, timePtr); } tsdPtr->eventReady = 0; -- cgit v0.12