sl@0: /* sl@0: * tclMacOSXNotify.c -- sl@0: * sl@0: * This file contains the implementation of a merged CFRunLoop/select() sl@0: * based notifier, which is the lowest-level part of the Tcl event loop. sl@0: * This file works together with generic/tclNotify.c. sl@0: * sl@0: * Copyright (c) 1995-1997 Sun Microsystems, Inc. sl@0: * Copyright 2001, Apple Computer, Inc. sl@0: * Copyright (c) 2005-2007 Daniel A. Steffen sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution of sl@0: * this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclMacOSXNotify.c,v 1.1.2.12 2007/04/29 02:21:33 das Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: #ifdef HAVE_COREFOUNDATION /* Traditional unix select-based notifier is sl@0: * in tclUnixNotfy.c */ sl@0: #include sl@0: #include sl@0: sl@0: extern TclStubs tclStubs; sl@0: extern Tcl_NotifierProcs tclOriginalNotifier; sl@0: sl@0: /* sl@0: * This structure is used to keep track of the notifier info for a registered sl@0: * file. sl@0: */ sl@0: sl@0: typedef struct FileHandler { sl@0: int fd; sl@0: int mask; /* Mask of desired events: TCL_READABLE, sl@0: * etc. */ sl@0: int readyMask; /* Mask of events that have been seen since sl@0: * the last time file handlers were invoked sl@0: * for this file. */ sl@0: Tcl_FileProc *proc; /* Function to call, in the style of sl@0: * Tcl_CreateFileHandler. */ sl@0: ClientData clientData; /* Argument to pass to proc. */ sl@0: struct FileHandler *nextPtr;/* Next in list of all files we care about. */ sl@0: } FileHandler; sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when file sl@0: * handlers are ready to fire. sl@0: */ sl@0: sl@0: typedef struct FileHandlerEvent { sl@0: Tcl_Event header; /* Information that is standard for all sl@0: * events. */ sl@0: int fd; /* File descriptor that is ready. Used to find sl@0: * the FileHandler structure for the file sl@0: * (can't point directly to the FileHandler sl@0: * structure because it could go away while sl@0: * the event is queued). */ sl@0: } FileHandlerEvent; sl@0: sl@0: /* sl@0: * The following structure contains a set of select() masks to track readable, sl@0: * writable, and exceptional conditions. sl@0: */ sl@0: sl@0: typedef struct SelectMasks { sl@0: fd_set readable; sl@0: fd_set writable; sl@0: fd_set exceptional; sl@0: } SelectMasks; sl@0: sl@0: /* sl@0: * The following static structure contains the state information for the sl@0: * select based implementation of the Tcl notifier. One of these structures is sl@0: * created for each thread that is using the notifier. sl@0: */ sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: FileHandler *firstFileHandlerPtr; sl@0: /* Pointer to head of file handler list. */ sl@0: SelectMasks checkMasks; /* This structure is used to build up the sl@0: * masks to be used in the next call to sl@0: * select. Bits are set in response to calls sl@0: * to Tcl_CreateFileHandler. */ sl@0: SelectMasks readyMasks; /* This array reflects the readable/writable sl@0: * conditions that were found to exist by the sl@0: * last call to select. */ sl@0: int numFdBits; /* Number of valid bits in checkMasks (one sl@0: * more than highest fd for which sl@0: * Tcl_WatchFile has been called). */ sl@0: int onList; /* True if it is in this list */ sl@0: unsigned int pollState; /* pollState is used to implement a polling sl@0: * handshake between each thread and the sl@0: * notifier thread. Bits defined below. */ sl@0: struct ThreadSpecificData *nextPtr, *prevPtr; sl@0: /* All threads that are currently waiting on sl@0: * an event have their ThreadSpecificData sl@0: * structure on a doubly-linked listed formed sl@0: * from these pointers. You must hold the sl@0: * notifierLock before accessing these sl@0: * fields. */ sl@0: CFRunLoopSourceRef runLoopSource; sl@0: /* Any other thread alerts a notifier that an sl@0: * event is ready to be processed by signaling sl@0: * this CFRunLoopSource. */ sl@0: CFRunLoopRef runLoop; /* This thread's CFRunLoop, needs to be woken sl@0: * up whenever the runLoopSource is sl@0: * signaled. */ sl@0: int eventReady; /* True if an event is ready to be sl@0: * processed. */ sl@0: } ThreadSpecificData; sl@0: sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: /* sl@0: * The following static indicates the number of threads that have initialized sl@0: * notifiers. sl@0: * sl@0: * You must hold the notifierInitLock before accessing this variable. sl@0: */ sl@0: sl@0: static int notifierCount = 0; sl@0: sl@0: /* sl@0: * The following variable points to the head of a doubly-linked list of sl@0: * ThreadSpecificData structures for all threads that are currently waiting on sl@0: * an event. sl@0: * sl@0: * You must hold the notifierLock before accessing this list. sl@0: */ sl@0: sl@0: static ThreadSpecificData *waitingListPtr = NULL; sl@0: sl@0: /* sl@0: * The notifier thread spends all its time in select() waiting for a file sl@0: * descriptor associated with one of the threads on the waitingListPtr list to sl@0: * do something interesting. But if the contents of the waitingListPtr list sl@0: * ever changes, we need to wake up and restart the select() system call. You sl@0: * can wake up the notifier thread by writing a single byte to the file sl@0: * descriptor defined below. This file descriptor is the input-end of a pipe sl@0: * and the notifier thread is listening for data on the output-end of the same sl@0: * pipe. Hence writing to this file descriptor will cause the select() system sl@0: * call to return and wake up the notifier thread. sl@0: * sl@0: * You must hold the notifierLock lock before writing to the pipe. sl@0: */ sl@0: sl@0: static int triggerPipe = -1; sl@0: static int receivePipe = -1; /* Output end of triggerPipe */ sl@0: sl@0: /* sl@0: * We use the Darwin-native spinlock API rather than pthread mutexes for sl@0: * notifier locking: this radically simplifies the implementation and lowers sl@0: * overhead. Note that these are not pure spinlocks, they employ various sl@0: * strategies to back off and relinquish the processor, making them immune to sl@0: * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin sl@0: * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s). sl@0: */ sl@0: sl@0: #if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) sl@0: /* sl@0: * Use OSSpinLock API where available (Tiger or later). sl@0: */ sl@0: sl@0: #include sl@0: sl@0: #if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 sl@0: /* sl@0: * Support for weakly importing spinlock API. sl@0: */ sl@0: #define WEAK_IMPORT_SPINLOCKLOCK sl@0: #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 sl@0: #define VOLATILE volatile sl@0: #else sl@0: #define VOLATILE sl@0: #endif sl@0: #ifndef bool sl@0: #define bool int sl@0: #endif sl@0: extern void OSSpinLockLock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; sl@0: extern void OSSpinLockUnlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; sl@0: extern bool OSSpinLockTry(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; sl@0: extern void _spin_lock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; sl@0: extern void _spin_unlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; sl@0: extern bool _spin_lock_try(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE; sl@0: static void (* lockLock)(VOLATILE OSSpinLock *lock) = NULL; sl@0: static void (* lockUnlock)(VOLATILE OSSpinLock *lock) = NULL; sl@0: static bool (* lockTry)(VOLATILE OSSpinLock *lock) = NULL; sl@0: #undef VOLATILE sl@0: static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT; sl@0: static void SpinLockLockInit(void) { sl@0: lockLock = OSSpinLockLock != NULL ? OSSpinLockLock : _spin_lock; sl@0: lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock; sl@0: lockTry = OSSpinLockTry != NULL ? OSSpinLockTry : _spin_lock_try; sl@0: if (lockLock == NULL || lockUnlock == NULL) { sl@0: Tcl_Panic("SpinLockLockInit: no spinlock API available"); sl@0: } sl@0: } sl@0: #define SpinLockLock(p) lockLock(p) sl@0: #define SpinLockUnlock(p) lockUnlock(p) sl@0: #define SpinLockTry(p) lockTry(p) sl@0: #else sl@0: #define SpinLockLock(p) OSSpinLockLock(p) sl@0: #define SpinLockUnlock(p) OSSpinLockUnlock(p) sl@0: #define SpinLockTry(p) OSSpinLockTry(p) sl@0: #endif /* HAVE_WEAK_IMPORT */ sl@0: #define SPINLOCK_INIT OS_SPINLOCK_INIT sl@0: sl@0: #else sl@0: /* sl@0: * Otherwise, use commpage spinlock SPI directly. sl@0: */ sl@0: sl@0: typedef uint32_t OSSpinLock; sl@0: extern void _spin_lock(OSSpinLock *lock); sl@0: extern void _spin_unlock(OSSpinLock *lock); sl@0: extern int _spin_lock_try(OSSpinLock *lock); sl@0: #define SpinLockLock(p) _spin_lock(p) sl@0: #define SpinLockUnlock(p) _spin_unlock(p) sl@0: #define SpinLockTry(p) _spin_lock_try(p) sl@0: #define SPINLOCK_INIT 0 sl@0: sl@0: #endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ sl@0: sl@0: /* sl@0: * These spinlocks lock access to the global notifier state. sl@0: */ sl@0: sl@0: static OSSpinLock notifierInitLock = SPINLOCK_INIT; sl@0: static OSSpinLock notifierLock = SPINLOCK_INIT; sl@0: sl@0: /* sl@0: * Macros abstracting notifier locking/unlocking sl@0: */ sl@0: sl@0: #define LOCK_NOTIFIER_INIT SpinLockLock(¬ifierInitLock) sl@0: #define UNLOCK_NOTIFIER_INIT SpinLockUnlock(¬ifierInitLock) sl@0: #define LOCK_NOTIFIER SpinLockLock(¬ifierLock) sl@0: #define UNLOCK_NOTIFIER SpinLockUnlock(¬ifierLock) sl@0: sl@0: /* sl@0: * The pollState bits sl@0: * POLL_WANT is set by each thread before it waits on its condition sl@0: * variable. It is checked by the notifier before it does select. sl@0: * POLL_DONE is set by the notifier if it goes into select after seeing sl@0: * POLL_WANT. The idea is to ensure it tries a select with the sl@0: * same bits the initial thread had set. sl@0: */ sl@0: sl@0: #define POLL_WANT 0x1 sl@0: #define POLL_DONE 0x2 sl@0: sl@0: /* sl@0: * This is the thread ID of the notifier thread that does select. sl@0: */ sl@0: sl@0: static pthread_t notifierThread; sl@0: sl@0: /* sl@0: * Custom run loop mode containing only the run loop source for the sl@0: * notifier thread. sl@0: */ sl@0: sl@0: #ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE sl@0: #define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" sl@0: #endif sl@0: #ifdef __CONSTANT_CFSTRINGS__ sl@0: #define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) sl@0: #else sl@0: static CFStringRef tclEventsOnlyRunLoopMode = NULL; sl@0: #endif sl@0: sl@0: /* sl@0: * Static routines defined in this file. sl@0: */ sl@0: sl@0: static void NotifierThreadProc(ClientData clientData) sl@0: __attribute__ ((__noreturn__)); sl@0: static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); sl@0: sl@0: #ifdef HAVE_PTHREAD_ATFORK sl@0: static int atForkInit = 0; sl@0: static void AtForkPrepare(void); sl@0: static void AtForkParent(void); sl@0: static void AtForkChild(void); sl@0: #if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040 sl@0: /* Support for weakly importing pthread_atfork. */ sl@0: #define WEAK_IMPORT_PTHREAD_ATFORK sl@0: extern int pthread_atfork(void (*prepare)(void), void (*parent)(void), sl@0: void (*child)(void)) WEAK_IMPORT_ATTRIBUTE; sl@0: #endif /* HAVE_WEAK_IMPORT */ sl@0: #endif /* HAVE_PTHREAD_ATFORK */ sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_InitNotifier -- sl@0: * sl@0: * Initializes the platform specific notifier state. sl@0: * sl@0: * Results: sl@0: * Returns a handle to the notifier state for this thread. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: ClientData sl@0: Tcl_InitNotifier(void) sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: tsdPtr->eventReady = 0; sl@0: sl@0: #ifdef WEAK_IMPORT_SPINLOCKLOCK sl@0: /* sl@0: * Initialize support for weakly imported spinlock API. sl@0: */ sl@0: if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) { sl@0: Tcl_Panic("Tcl_InitNotifier: pthread_once failed"); sl@0: } sl@0: #endif sl@0: sl@0: #ifndef __CONSTANT_CFSTRINGS__ sl@0: if (!tclEventsOnlyRunLoopMode) { sl@0: tclEventsOnlyRunLoopMode = CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE); sl@0: } sl@0: #endif sl@0: sl@0: /* sl@0: * Initialize CFRunLoopSource and add it to CFRunLoop of this thread. sl@0: */ sl@0: sl@0: if (!tsdPtr->runLoop) { sl@0: CFRunLoopRef runLoop = CFRunLoopGetCurrent(); sl@0: CFRunLoopSourceRef runLoopSource; sl@0: CFRunLoopSourceContext runLoopSourceContext; sl@0: sl@0: bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext)); sl@0: runLoopSourceContext.info = tsdPtr; sl@0: runLoopSource = CFRunLoopSourceCreate(NULL, 0, &runLoopSourceContext); sl@0: if (!runLoopSource) { sl@0: Tcl_Panic("Tcl_InitNotifier: could not create CFRunLoopSource"); sl@0: } sl@0: CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopCommonModes); sl@0: CFRunLoopAddSource(runLoop, runLoopSource, tclEventsOnlyRunLoopMode); sl@0: tsdPtr->runLoopSource = runLoopSource; sl@0: tsdPtr->runLoop = runLoop; sl@0: } sl@0: sl@0: LOCK_NOTIFIER_INIT; sl@0: #ifdef HAVE_PTHREAD_ATFORK sl@0: /* sl@0: * Install pthread_atfork handlers to reinitialize the notifier in the sl@0: * child of a fork. sl@0: */ sl@0: sl@0: if ( sl@0: #ifdef WEAK_IMPORT_PTHREAD_ATFORK sl@0: pthread_atfork != NULL && sl@0: #endif sl@0: !atForkInit) { sl@0: int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild); sl@0: if (result) { sl@0: Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed"); sl@0: } sl@0: atForkInit = 1; sl@0: } sl@0: #endif sl@0: if (notifierCount == 0) { sl@0: int fds[2], status; sl@0: sl@0: /* sl@0: * Initialize trigger pipe. sl@0: */ sl@0: sl@0: if (pipe(fds) != 0) { sl@0: Tcl_Panic("Tcl_InitNotifier: could not create trigger pipe"); sl@0: } sl@0: sl@0: status = fcntl(fds[0], F_GETFL); sl@0: status |= O_NONBLOCK; sl@0: if (fcntl(fds[0], F_SETFL, status) < 0) { sl@0: Tcl_Panic("Tcl_InitNotifier: could not make receive pipe non blocking"); sl@0: } sl@0: status = fcntl(fds[1], F_GETFL); sl@0: status |= O_NONBLOCK; sl@0: if (fcntl(fds[1], F_SETFL, status) < 0) { sl@0: Tcl_Panic("Tcl_InitNotifier: could not make trigger pipe non blocking"); sl@0: } sl@0: sl@0: receivePipe = fds[0]; sl@0: triggerPipe = fds[1]; sl@0: sl@0: /* sl@0: * Create notifier thread lazily in Tcl_WaitForEvent() to avoid sl@0: * interfering with fork() followed immediately by execve() sl@0: * (cannot execve() when more than one thread is present). sl@0: */ sl@0: sl@0: notifierThread = 0; sl@0: } sl@0: notifierCount++; sl@0: UNLOCK_NOTIFIER_INIT; sl@0: sl@0: return (ClientData) tsdPtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_FinalizeNotifier -- sl@0: * sl@0: * This function is called to cleanup the notifier state before a thread sl@0: * is terminated. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May terminate the background notifier thread if this is the last sl@0: * notifier instance. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_FinalizeNotifier( sl@0: ClientData clientData) /* Not used. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: LOCK_NOTIFIER_INIT; sl@0: notifierCount--; sl@0: sl@0: /* sl@0: * If this is the last thread to use the notifier, close the notifier pipe sl@0: * and wait for the background thread to terminate. sl@0: */ sl@0: sl@0: if (notifierCount == 0) { sl@0: int result; sl@0: sl@0: if (triggerPipe < 0) { sl@0: Tcl_Panic("Tcl_FinalizeNotifier: notifier pipe not initialized"); sl@0: } sl@0: sl@0: /* sl@0: * Send "q" message to the notifier thread so that it will terminate. sl@0: * The notifier will return from its call to select() and notice that sl@0: * a "q" message has arrived, it will then close its side of the pipe sl@0: * and terminate its thread. Note the we can not just close the pipe sl@0: * and check for EOF in the notifier thread because if a background sl@0: * child process was created with exec, select() would not register sl@0: * the EOF on the pipe until the child processes had terminated. [Bug: sl@0: * 4139] [Bug: 1222872] sl@0: */ sl@0: sl@0: write(triggerPipe, "q", 1); sl@0: close(triggerPipe); sl@0: sl@0: if (notifierThread) { sl@0: result = pthread_join(notifierThread, NULL); sl@0: if (result) { sl@0: Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier thread"); sl@0: } sl@0: notifierThread = 0; sl@0: } sl@0: sl@0: close(receivePipe); sl@0: triggerPipe = -1; sl@0: } sl@0: UNLOCK_NOTIFIER_INIT; sl@0: sl@0: LOCK_NOTIFIER; /* for concurrency with Tcl_AlertNotifier */ sl@0: if (tsdPtr->runLoop) { sl@0: tsdPtr->runLoop = NULL; sl@0: sl@0: /* sl@0: * Remove runLoopSource from all CFRunLoops and release it. sl@0: */ sl@0: sl@0: CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); sl@0: CFRelease(tsdPtr->runLoopSource); sl@0: tsdPtr->runLoopSource = NULL; sl@0: } sl@0: UNLOCK_NOTIFIER; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_AlertNotifier -- sl@0: * sl@0: * Wake up the specified notifier from any thread. This routine is called sl@0: * by the platform independent notifier code whenever the Tcl_ThreadAlert sl@0: * routine is called. This routine is guaranteed not to be called on a sl@0: * given notifier after Tcl_FinalizeNotifier is called for that notifier. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Signals the notifier condition variable for the specified notifier. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_AlertNotifier( sl@0: ClientData clientData) sl@0: { sl@0: ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; sl@0: sl@0: LOCK_NOTIFIER; sl@0: if (tsdPtr->runLoop) { sl@0: tsdPtr->eventReady = 1; sl@0: CFRunLoopSourceSignal(tsdPtr->runLoopSource); sl@0: CFRunLoopWakeUp(tsdPtr->runLoop); sl@0: } sl@0: UNLOCK_NOTIFIER; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_SetTimer -- sl@0: * sl@0: * This function sets the current notifier timer value. This interface is sl@0: * not implemented in this notifier because we are always running inside sl@0: * of Tcl_DoOneEvent. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_SetTimer( sl@0: Tcl_Time *timePtr) /* Timeout value, may be NULL. */ sl@0: { sl@0: /* sl@0: * The interval timer doesn't do anything in this implementation, because sl@0: * the only event loop is via Tcl_DoOneEvent, which passes timeout values sl@0: * to Tcl_WaitForEvent. sl@0: */ sl@0: sl@0: if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { sl@0: tclStubs.tcl_SetTimer(timePtr); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_ServiceModeHook -- sl@0: * sl@0: * This function is invoked whenever the service mode changes. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_ServiceModeHook( sl@0: int mode) /* Either TCL_SERVICE_ALL, or sl@0: * TCL_SERVICE_NONE. */ sl@0: { sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_CreateFileHandler -- sl@0: * sl@0: * This function registers a file handler with the select notifier. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new file handler structure. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_CreateFileHandler( sl@0: int fd, /* Handle of stream to watch. */ sl@0: int mask, /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, and TCL_EXCEPTION: indicates sl@0: * conditions under which proc should be sl@0: * called. */ sl@0: Tcl_FileProc *proc, /* Function to call for each selected sl@0: * event. */ sl@0: ClientData clientData) /* Arbitrary data to pass to proc. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: FileHandler *filePtr; sl@0: sl@0: if (tclStubs.tcl_CreateFileHandler != sl@0: tclOriginalNotifier.createFileHandlerProc) { sl@0: tclStubs.tcl_CreateFileHandler(fd, mask, proc, clientData); sl@0: return; sl@0: } sl@0: sl@0: for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; sl@0: filePtr = filePtr->nextPtr) { sl@0: if (filePtr->fd == fd) { sl@0: break; sl@0: } sl@0: } sl@0: if (filePtr == NULL) { sl@0: filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); sl@0: filePtr->fd = fd; sl@0: filePtr->readyMask = 0; sl@0: filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; sl@0: tsdPtr->firstFileHandlerPtr = filePtr; sl@0: } sl@0: filePtr->proc = proc; sl@0: filePtr->clientData = clientData; sl@0: filePtr->mask = mask; sl@0: sl@0: /* sl@0: * Update the check masks for this file. sl@0: */ sl@0: sl@0: if (mask & TCL_READABLE) { sl@0: FD_SET(fd, &(tsdPtr->checkMasks.readable)); sl@0: } else { sl@0: FD_CLR(fd, &(tsdPtr->checkMasks.readable)); sl@0: } sl@0: if (mask & TCL_WRITABLE) { sl@0: FD_SET(fd, &(tsdPtr->checkMasks.writable)); sl@0: } else { sl@0: FD_CLR(fd, &(tsdPtr->checkMasks.writable)); sl@0: } sl@0: if (mask & TCL_EXCEPTION) { sl@0: FD_SET(fd, &(tsdPtr->checkMasks.exceptional)); sl@0: } else { sl@0: FD_CLR(fd, &(tsdPtr->checkMasks.exceptional)); sl@0: } sl@0: if (tsdPtr->numFdBits <= fd) { sl@0: tsdPtr->numFdBits = fd+1; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_DeleteFileHandler -- sl@0: * sl@0: * Cancel a previously-arranged callback arrangement for a file. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * If a callback was previously registered on file, remove it. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: Tcl_DeleteFileHandler( sl@0: int fd) /* Stream id for which to remove callback sl@0: * function. */ sl@0: { sl@0: FileHandler *filePtr, *prevPtr; sl@0: int i; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (tclStubs.tcl_DeleteFileHandler != sl@0: tclOriginalNotifier.deleteFileHandlerProc) { sl@0: tclStubs.tcl_DeleteFileHandler(fd); sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Find the entry for the given file (and return if there isn't one). sl@0: */ sl@0: sl@0: for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; sl@0: prevPtr = filePtr, filePtr = filePtr->nextPtr) { sl@0: if (filePtr == NULL) { sl@0: return; sl@0: } sl@0: if (filePtr->fd == fd) { sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Update the check masks for this file. sl@0: */ sl@0: sl@0: if (filePtr->mask & TCL_READABLE) { sl@0: FD_CLR(fd, &(tsdPtr->checkMasks.readable)); sl@0: } sl@0: if (filePtr->mask & TCL_WRITABLE) { sl@0: FD_CLR(fd, &(tsdPtr->checkMasks.writable)); sl@0: } sl@0: if (filePtr->mask & TCL_EXCEPTION) { sl@0: FD_CLR(fd, &(tsdPtr->checkMasks.exceptional)); sl@0: } sl@0: sl@0: /* sl@0: * Find current max fd. sl@0: */ sl@0: sl@0: if (fd+1 == tsdPtr->numFdBits) { sl@0: tsdPtr->numFdBits = 0; sl@0: for (i = fd-1; i >= 0; i--) { sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) sl@0: || FD_ISSET(i, &(tsdPtr->checkMasks.writable)) sl@0: || FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) { sl@0: tsdPtr->numFdBits = i+1; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Clean up information in the callback record. sl@0: */ sl@0: sl@0: if (prevPtr == NULL) { sl@0: tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; sl@0: } else { sl@0: prevPtr->nextPtr = filePtr->nextPtr; sl@0: } sl@0: ckfree((char *) filePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * FileHandlerEventProc -- sl@0: * sl@0: * This function is called by Tcl_ServiceEvent when a file event reaches sl@0: * the front of the event queue. This function is responsible for sl@0: * actually handling the event by invoking the callback for the file sl@0: * handler. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed from sl@0: * the queue. Returns 0 if the event was not handled, meaning it should sl@0: * stay on the queue. The only time the event isn't handled is if the sl@0: * TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the file handler's callback function does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: FileHandlerEventProc( sl@0: Tcl_Event *evPtr, /* Event to service. */ sl@0: int flags) /* Flags that indicate what events to handle, sl@0: * such as TCL_FILE_EVENTS. */ sl@0: { sl@0: int mask; sl@0: FileHandler *filePtr; sl@0: FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Search through the file handlers to find the one whose handle matches sl@0: * the event. We do this rather than keeping a pointer to the file handler sl@0: * directly in the event, so that the handler can be deleted while the sl@0: * event is queued without leaving a dangling pointer. sl@0: */ sl@0: sl@0: tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL; sl@0: filePtr = filePtr->nextPtr) { sl@0: if (filePtr->fd != fileEvPtr->fd) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * The code is tricky for two reasons: sl@0: * 1. The file handler's desired events could have changed since the sl@0: * time when the event was queued, so AND the ready mask with the sl@0: * desired mask. sl@0: * 2. The file could have been closed and re-opened since the time sl@0: * when the event was queued. This is why the ready mask is stored sl@0: * in the file handler rather than the queued event: it will be sl@0: * zeroed when a new file handler is created for the newly opened sl@0: * file. sl@0: */ sl@0: sl@0: mask = filePtr->readyMask & filePtr->mask; sl@0: filePtr->readyMask = 0; sl@0: if (mask != 0) { sl@0: (*filePtr->proc)(filePtr->clientData, mask); sl@0: } sl@0: break; sl@0: } sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_WaitForEvent -- sl@0: * sl@0: * This function is called by Tcl_DoOneEvent to wait for new events on sl@0: * the message queue. If the block time is 0, then Tcl_WaitForEvent just sl@0: * polls without blocking. sl@0: * sl@0: * Results: sl@0: * Returns -1 if the select would block forever, otherwise returns 0. sl@0: * sl@0: * Side effects: sl@0: * Queues file events that are detected by the select. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: Tcl_WaitForEvent( sl@0: Tcl_Time *timePtr) /* Maximum block time, or NULL. */ sl@0: { sl@0: FileHandler *filePtr; sl@0: FileHandlerEvent *fileEvPtr; sl@0: int mask; sl@0: int waitForFiles; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { sl@0: return tclStubs.tcl_WaitForEvent(timePtr); sl@0: } sl@0: sl@0: /* sl@0: * Start notifier thread if necessary. sl@0: */ sl@0: sl@0: LOCK_NOTIFIER_INIT; sl@0: if (!notifierCount) { sl@0: Tcl_Panic("Tcl_WaitForEvent: notifier not initialized"); sl@0: } sl@0: if (!notifierThread) { sl@0: int result; sl@0: pthread_attr_t attr; sl@0: sl@0: pthread_attr_init(&attr); sl@0: pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); sl@0: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); sl@0: pthread_attr_setstacksize(&attr, 60 * 1024); sl@0: result = pthread_create(¬ifierThread, &attr, sl@0: (void * (*)(void *))NotifierThreadProc, NULL); sl@0: pthread_attr_destroy(&attr); sl@0: if (result || !notifierThread) { sl@0: Tcl_Panic("Tcl_WaitForEvent: unable to start notifier thread"); sl@0: } sl@0: } sl@0: UNLOCK_NOTIFIER_INIT; sl@0: sl@0: /* sl@0: * Place this thread on the list of interested threads, signal the sl@0: * notifier thread, and wait for a response or a timeout. sl@0: */ sl@0: sl@0: LOCK_NOTIFIER; sl@0: if (!tsdPtr->runLoop) { sl@0: Tcl_Panic("Tcl_WaitForEvent: CFRunLoop not initialized"); sl@0: } sl@0: waitForFiles = (tsdPtr->numFdBits > 0); sl@0: if (timePtr != NULL && timePtr->sec == 0 && timePtr->usec == 0) { sl@0: /* sl@0: * Cannot emulate a polling select with a polling condition variable. sl@0: * Instead, pretend to wait for files and tell the notifier thread sl@0: * what we are doing. The notifier thread makes sure it goes through sl@0: * select with its select mask in the same state as ours currently is. sl@0: * We block until that happens. sl@0: */ sl@0: sl@0: waitForFiles = 1; sl@0: tsdPtr->pollState = POLL_WANT; sl@0: timePtr = NULL; sl@0: } else { sl@0: tsdPtr->pollState = 0; sl@0: } sl@0: sl@0: if (waitForFiles) { sl@0: /* sl@0: * Add the ThreadSpecificData structure of this thread to the list of sl@0: * ThreadSpecificData structures of all threads that are waiting on sl@0: * file events. sl@0: */ sl@0: sl@0: tsdPtr->nextPtr = waitingListPtr; sl@0: if (waitingListPtr) { sl@0: waitingListPtr->prevPtr = tsdPtr; sl@0: } sl@0: tsdPtr->prevPtr = 0; sl@0: waitingListPtr = tsdPtr; sl@0: tsdPtr->onList = 1; sl@0: sl@0: write(triggerPipe, "", 1); sl@0: } sl@0: sl@0: FD_ZERO(&(tsdPtr->readyMasks.readable)); sl@0: FD_ZERO(&(tsdPtr->readyMasks.writable)); sl@0: FD_ZERO(&(tsdPtr->readyMasks.exceptional)); sl@0: sl@0: if (!tsdPtr->eventReady) { sl@0: CFTimeInterval waitTime; sl@0: CFStringRef runLoopMode; sl@0: sl@0: if (timePtr == NULL) { sl@0: waitTime = 1.0e10; /* Wait forever, as per CFRunLoop.c */ sl@0: } else { sl@0: waitTime = timePtr->sec + 1.0e-6 * timePtr->usec; sl@0: } sl@0: /* sl@0: * If the run loop is already running (e.g. if Tcl_WaitForEvent was sl@0: * called recursively), re-run it in a custom run loop mode containing sl@0: * only the source for the notifier thread, otherwise wakeups from other sl@0: * sources added to the common run loop modes might get lost. sl@0: */ sl@0: if ((runLoopMode = CFRunLoopCopyCurrentMode(tsdPtr->runLoop))) { sl@0: CFRelease(runLoopMode); sl@0: runLoopMode = tclEventsOnlyRunLoopMode; sl@0: } else { sl@0: runLoopMode = kCFRunLoopDefaultMode; sl@0: } sl@0: UNLOCK_NOTIFIER; sl@0: CFRunLoopRunInMode(runLoopMode, waitTime, TRUE); sl@0: LOCK_NOTIFIER; sl@0: } sl@0: tsdPtr->eventReady = 0; sl@0: sl@0: if (waitForFiles && tsdPtr->onList) { sl@0: /* sl@0: * Remove the ThreadSpecificData structure of this thread from the sl@0: * waiting list. Alert the notifier thread to recompute its select sl@0: * masks - skipping this caused a hang when trying to close a pipe sl@0: * which the notifier thread was still doing a select on. sl@0: */ sl@0: sl@0: if (tsdPtr->prevPtr) { sl@0: tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; sl@0: } else { sl@0: waitingListPtr = tsdPtr->nextPtr; sl@0: } sl@0: if (tsdPtr->nextPtr) { sl@0: tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; sl@0: } sl@0: tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; sl@0: tsdPtr->onList = 0; sl@0: write(triggerPipe, "", 1); sl@0: } sl@0: sl@0: /* sl@0: * Queue all detected file events before returning. sl@0: */ sl@0: sl@0: for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); sl@0: filePtr = filePtr->nextPtr) { sl@0: sl@0: mask = 0; sl@0: if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.readable))) { sl@0: mask |= TCL_READABLE; sl@0: } sl@0: if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.writable))) { sl@0: mask |= TCL_WRITABLE; sl@0: } sl@0: if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.exceptional))) { sl@0: mask |= TCL_EXCEPTION; sl@0: } sl@0: sl@0: if (!mask) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Don't bother to queue an event if the mask was previously non-zero sl@0: * since an event must still be on the queue. sl@0: */ sl@0: sl@0: if (filePtr->readyMask == 0) { sl@0: fileEvPtr = (FileHandlerEvent *) ckalloc(sizeof(FileHandlerEvent)); sl@0: fileEvPtr->header.proc = FileHandlerEventProc; sl@0: fileEvPtr->fd = filePtr->fd; sl@0: Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); sl@0: } sl@0: filePtr->readyMask = mask; sl@0: } sl@0: UNLOCK_NOTIFIER; sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * NotifierThreadProc -- sl@0: * sl@0: * This routine is the initial (and only) function executed by the sl@0: * special notifier thread. Its job is to wait for file descriptors to sl@0: * become readable or writable or to have an exception condition and then sl@0: * to notify other threads who are interested in this information by sl@0: * signalling a condition variable. Other threads can signal this sl@0: * notifier thread of a change in their interests by writing a single sl@0: * byte to a special pipe that the notifier thread is monitoring. sl@0: * sl@0: * Result: sl@0: * None. Once started, this routine never exits. It dies with the overall sl@0: * process. sl@0: * sl@0: * Side effects: sl@0: * The trigger pipe used to signal the notifier thread is created when sl@0: * the notifier thread first starts. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: NotifierThreadProc( sl@0: ClientData clientData) /* Not used. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: fd_set readableMask; sl@0: fd_set writableMask; sl@0: fd_set exceptionalMask; sl@0: int i, numFdBits = 0; sl@0: long found; sl@0: struct timeval poll = {0., 0.}, *timePtr; sl@0: char buf[2]; sl@0: sl@0: /* sl@0: * Look for file events and report them to interested threads. sl@0: */ sl@0: sl@0: while (1) { sl@0: FD_ZERO(&readableMask); sl@0: FD_ZERO(&writableMask); sl@0: FD_ZERO(&exceptionalMask); sl@0: sl@0: /* sl@0: * Compute the logical OR of the select masks from all the waiting sl@0: * notifiers. sl@0: */ sl@0: sl@0: LOCK_NOTIFIER; sl@0: timePtr = NULL; sl@0: for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { sl@0: for (i = tsdPtr->numFdBits-1; i >= 0; --i) { sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))) { sl@0: FD_SET(i, &readableMask); sl@0: } sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.writable))) { sl@0: FD_SET(i, &writableMask); sl@0: } sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) { sl@0: FD_SET(i, &exceptionalMask); sl@0: } sl@0: } sl@0: if (tsdPtr->numFdBits > numFdBits) { sl@0: numFdBits = tsdPtr->numFdBits; sl@0: } sl@0: if (tsdPtr->pollState & POLL_WANT) { sl@0: /* sl@0: * Here we make sure we go through select() with the same mask sl@0: * bits that were present when the thread tried to poll. sl@0: */ sl@0: sl@0: tsdPtr->pollState |= POLL_DONE; sl@0: timePtr = &poll; sl@0: } sl@0: } sl@0: UNLOCK_NOTIFIER; sl@0: sl@0: /* sl@0: * Set up the select mask to include the receive pipe. sl@0: */ sl@0: sl@0: if (receivePipe >= numFdBits) { sl@0: numFdBits = receivePipe + 1; sl@0: } sl@0: FD_SET(receivePipe, &readableMask); sl@0: sl@0: if (select(numFdBits, &readableMask, &writableMask, &exceptionalMask, sl@0: timePtr) == -1) { sl@0: /* sl@0: * Try again immediately on an error. sl@0: */ sl@0: sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Alert any threads that are waiting on a ready file descriptor. sl@0: */ sl@0: sl@0: LOCK_NOTIFIER; sl@0: for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { sl@0: found = 0; sl@0: sl@0: for (i = tsdPtr->numFdBits-1; i >= 0; --i) { sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.readable)) sl@0: && FD_ISSET(i, &readableMask)) { sl@0: FD_SET(i, &(tsdPtr->readyMasks.readable)); sl@0: found = 1; sl@0: } sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.writable)) sl@0: && FD_ISSET(i, &writableMask)) { sl@0: FD_SET(i, &(tsdPtr->readyMasks.writable)); sl@0: found = 1; sl@0: } sl@0: if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional)) sl@0: && FD_ISSET(i, &exceptionalMask)) { sl@0: FD_SET(i, &(tsdPtr->readyMasks.exceptional)); sl@0: found = 1; sl@0: } sl@0: } sl@0: sl@0: if (found || (tsdPtr->pollState & POLL_DONE)) { sl@0: tsdPtr->eventReady = 1; sl@0: if (tsdPtr->onList) { sl@0: /* sl@0: * Remove the ThreadSpecificData structure of this thread sl@0: * from the waiting list. This prevents us from sl@0: * continuously spining on select until the other threads sl@0: * runs and services the file event. sl@0: */ sl@0: sl@0: if (tsdPtr->prevPtr) { sl@0: tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; sl@0: } else { sl@0: waitingListPtr = tsdPtr->nextPtr; sl@0: } sl@0: if (tsdPtr->nextPtr) { sl@0: tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; sl@0: } sl@0: tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; sl@0: tsdPtr->onList = 0; sl@0: tsdPtr->pollState = 0; sl@0: } sl@0: if (tsdPtr->runLoop) { sl@0: CFRunLoopSourceSignal(tsdPtr->runLoopSource); sl@0: CFRunLoopWakeUp(tsdPtr->runLoop); sl@0: } sl@0: } sl@0: } sl@0: UNLOCK_NOTIFIER; sl@0: sl@0: /* sl@0: * Consume the next byte from the notifier pipe if the pipe was sl@0: * readable. Note that there may be multiple bytes pending, but to sl@0: * avoid a race condition we only read one at a time. sl@0: */ sl@0: sl@0: if (FD_ISSET(receivePipe, &readableMask)) { sl@0: i = read(receivePipe, buf, 1); sl@0: sl@0: if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) { sl@0: /* sl@0: * Someone closed the write end of the pipe or sent us a Quit sl@0: * message [Bug: 4139] and then closed the write end of the sl@0: * pipe so we need to shut down the notifier thread. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: pthread_exit(0); sl@0: } sl@0: sl@0: #ifdef HAVE_PTHREAD_ATFORK sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * AtForkPrepare -- sl@0: * sl@0: * Lock the notifier in preparation for a fork. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: AtForkPrepare(void) sl@0: { sl@0: LOCK_NOTIFIER_INIT; sl@0: LOCK_NOTIFIER; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * AtForkParent -- sl@0: * sl@0: * Unlock the notifier in the parent after a fork. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: AtForkParent(void) sl@0: { sl@0: UNLOCK_NOTIFIER; sl@0: UNLOCK_NOTIFIER_INIT; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * AtForkChild -- sl@0: * sl@0: * Unlock and reinstall the notifier in the child after a fork. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: AtForkChild(void) sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: UNLOCK_NOTIFIER; sl@0: UNLOCK_NOTIFIER_INIT; sl@0: if (tsdPtr->runLoop) { sl@0: tsdPtr->runLoop = NULL; sl@0: CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); sl@0: CFRelease(tsdPtr->runLoopSource); sl@0: tsdPtr->runLoopSource = NULL; sl@0: } sl@0: if (notifierCount > 0) { sl@0: notifierCount = 0; sl@0: sl@0: /* sl@0: * Assume that the return value of Tcl_InitNotifier in the child will sl@0: * be identical to the one stored as clientData in tclNotify.c's sl@0: * ThreadSpecificData by the parent's TclInitNotifier, so discard the sl@0: * return value here. This assumption may require the fork() to be sl@0: * executed in the main thread of the parent, otherwise sl@0: * Tcl_AlertNotifier may break in the child. sl@0: */ sl@0: sl@0: Tcl_InitNotifier(); sl@0: } sl@0: } sl@0: #endif /* HAVE_PTHREAD_ATFORK */ sl@0: sl@0: #endif /* HAVE_COREFOUNDATION */