1 /*
2  * tclMacOSXNotify.c --
3  *
4  *	This file contains the implementation of a merged CFRunLoop/select()
5  *	based notifier, which is the lowest-level part of the Tcl event loop.
6  *	This file works together with generic/tclNotify.c.
7  *
8  * Copyright (c) 1995-1997 Sun Microsystems, Inc.
9  * Copyright 2001-2009, Apple Inc.
10  * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
11  *
12  * See the file "license.terms" for information on usage and redistribution of
13  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15 
16 #include "tclInt.h"
17 #ifdef HAVE_COREFOUNDATION	/* Traditional unix select-based notifier is
18 				 * in tclUnixNotfy.c */
19 #include <CoreFoundation/CoreFoundation.h>
20 #include <pthread.h>
21 
22 /* #define TCL_MAC_DEBUG_NOTIFIER 1 */
23 
24 extern TclStubs tclStubs;
25 extern Tcl_NotifierProcs tclOriginalNotifier;
26 
27 /*
28  * We use the Darwin-native spinlock API rather than pthread mutexes for
29  * notifier locking: this radically simplifies the implementation and lowers
30  * overhead. Note that these are not pure spinlocks, they employ various
31  * strategies to back off and relinquish the processor, making them immune to
32  * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin
33  * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s).
34  */
35 
36 #if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK)
37 /*
38  * Use OSSpinLock API where available (Tiger or later).
39  */
40 
41 #include <libkern/OSAtomic.h>
42 
43 #if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040
44 /*
45  * Support for weakly importing spinlock API.
46  */
47 #define WEAK_IMPORT_SPINLOCKLOCK
48 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
49 #define VOLATILE volatile
50 #else
51 #define VOLATILE
52 #endif
53 #ifndef bool
54 #define bool int
55 #endif
56 extern void OSSpinLockLock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
57 extern void OSSpinLockUnlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
58 extern bool OSSpinLockTry(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
59 extern void _spin_lock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
60 extern void _spin_unlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
61 extern bool _spin_lock_try(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
62 static void (* lockLock)(VOLATILE OSSpinLock *lock) = NULL;
63 static void (* lockUnlock)(VOLATILE OSSpinLock *lock) = NULL;
64 static bool (* lockTry)(VOLATILE OSSpinLock *lock) = NULL;
65 #undef VOLATILE
66 static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT;
SpinLockLockInit(void)67 static void SpinLockLockInit(void) {
68     lockLock   = OSSpinLockLock   != NULL ? OSSpinLockLock   : _spin_lock;
69     lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock;
70     lockTry    = OSSpinLockTry    != NULL ? OSSpinLockTry    : _spin_lock_try;
71     if (lockLock == NULL || lockUnlock == NULL) {
72 	Tcl_Panic("SpinLockLockInit: no spinlock API available");
73     }
74 }
75 #define SpinLockLock(p) 	lockLock(p)
76 #define SpinLockUnlock(p)	lockUnlock(p)
77 #define SpinLockTry(p)  	lockTry(p)
78 #else
79 #define SpinLockLock(p) 	OSSpinLockLock(p)
80 #define SpinLockUnlock(p)	OSSpinLockUnlock(p)
81 #define SpinLockTry(p)  	OSSpinLockTry(p)
82 #endif /* HAVE_WEAK_IMPORT */
83 #define SPINLOCK_INIT   	OS_SPINLOCK_INIT
84 
85 #else
86 /*
87  * Otherwise, use commpage spinlock SPI directly.
88  */
89 
90 typedef uint32_t OSSpinLock;
91 extern void _spin_lock(OSSpinLock *lock);
92 extern void _spin_unlock(OSSpinLock *lock);
93 extern int  _spin_lock_try(OSSpinLock *lock);
94 #define SpinLockLock(p) 	_spin_lock(p)
95 #define SpinLockUnlock(p)	_spin_unlock(p)
96 #define SpinLockTry(p)  	_spin_lock_try(p)
97 #define SPINLOCK_INIT   	0
98 
99 #endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */
100 
101 /*
102  * These spinlocks lock access to the global notifier state.
103  */
104 
105 static OSSpinLock notifierInitLock = SPINLOCK_INIT;
106 static OSSpinLock notifierLock     = SPINLOCK_INIT;
107 
108 /*
109  * Macros abstracting notifier locking/unlocking
110  */
111 
112 #define LOCK_NOTIFIER_INIT	SpinLockLock(&notifierInitLock)
113 #define UNLOCK_NOTIFIER_INIT	SpinLockUnlock(&notifierInitLock)
114 #define LOCK_NOTIFIER		SpinLockLock(&notifierLock)
115 #define UNLOCK_NOTIFIER		SpinLockUnlock(&notifierLock)
116 #define LOCK_NOTIFIER_TSD	SpinLockLock(&tsdPtr->tsdLock)
117 #define UNLOCK_NOTIFIER_TSD	SpinLockUnlock(&tsdPtr->tsdLock)
118 
119 #ifdef TCL_MAC_DEBUG_NOTIFIER
120 #define TclMacOSXNotifierDbgMsg(m, ...) do { \
121 	    fprintf(notifierLog?notifierLog:stderr, "tclMacOSXNotify.c:%d: " \
122 	    "%s() pid %5d thread %10p: " m "\n", __LINE__, __func__, \
123 	    getpid(), pthread_self(), ##__VA_ARGS__); \
124 	    fflush(notifierLog?notifierLog:stderr); \
125 	} while (0)
126 
127 /*
128  * Debug version of SpinLockLock that logs the time spent waiting for the lock
129  */
130 
131 #define SpinLockLockDbg(p)	if (!SpinLockTry(p)) { \
132 				    Tcl_WideInt s = TclpGetWideClicks(), e; \
133 				    SpinLockLock(p); e = TclpGetWideClicks(); \
134 				    TclMacOSXNotifierDbgMsg("waited on %s for %8.0f ns", \
135 				    #p, TclpWideClicksToNanoseconds(e-s)); \
136 				}
137 #undef LOCK_NOTIFIER_INIT
138 #define LOCK_NOTIFIER_INIT	SpinLockLockDbg(&notifierInitLock)
139 #undef LOCK_NOTIFIER
140 #define LOCK_NOTIFIER		SpinLockLockDbg(&notifierLock)
141 #undef LOCK_NOTIFIER_TSD
142 #define LOCK_NOTIFIER_TSD	SpinLockLockDbg(&tsdPtr->tsdLock)
143 #include <asl.h>
144 static FILE *notifierLog = NULL;
145 #ifndef NOTIFIER_LOG
146 #define NOTIFIER_LOG "/tmp/tclMacOSXNotify.log"
147 #endif
148 #define OPEN_NOTIFIER_LOG	if (!notifierLog) { \
149 				    notifierLog = fopen(NOTIFIER_LOG, "a"); \
150 				    /*TclMacOSXNotifierDbgMsg("open log"); \
151 				    asl_set_filter(NULL, \
152 				    ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); \
153 				    asl_add_log_file(NULL, \
154 					    fileno(notifierLog));*/ \
155 				}
156 #define CLOSE_NOTIFIER_LOG	if (notifierLog) { \
157 				    /*asl_remove_log_file(NULL, \
158 					    fileno(notifierLog)); \
159 				    TclMacOSXNotifierDbgMsg("close log");*/ \
160 				    fclose(notifierLog); \
161 				    notifierLog = NULL; \
162 				}
163 #define ENABLE_ASL		if (notifierLog) { \
164 				    /*tsdPtr->asl = asl_open(NULL, "com.apple.console", ASL_OPT_NO_REMOTE); \
165 				    asl_set_filter(tsdPtr->asl, \
166 				    ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); \
167 				    asl_add_log_file(tsdPtr->asl, \
168 					    fileno(notifierLog));*/ \
169 				}
170 #define DISABLE_ASL		/*if (tsdPtr->asl) { \
171 				    if (notifierLog) { \
172 					asl_remove_log_file(tsdPtr->asl, \
173 						fileno(notifierLog)); \
174 				    } \
175 				    asl_close(tsdPtr->asl); \
176 				}*/
177 #define ASLCLIENT		/*aslclient asl*/
178 #else
179 #define TclMacOSXNotifierDbgMsg(m, ...)
180 #define OPEN_NOTIFIER_LOG
181 #define CLOSE_NOTIFIER_LOG
182 #define ENABLE_ASL
183 #define DISABLE_ASL
184 #endif /* TCL_MAC_DEBUG_NOTIFIER */
185 
186 /*
187  * This structure is used to keep track of the notifier info for a registered
188  * file.
189  */
190 
191 typedef struct FileHandler {
192     int fd;
193     int mask;			/* Mask of desired events: TCL_READABLE,
194 				 * etc. */
195     int readyMask;		/* Mask of events that have been seen since
196 				 * the last time file handlers were invoked
197 				 * for this file. */
198     Tcl_FileProc *proc;		/* Function to call, in the style of
199 				 * Tcl_CreateFileHandler. */
200     ClientData clientData;	/* Argument to pass to proc. */
201     struct FileHandler *nextPtr;/* Next in list of all files we care about. */
202 } FileHandler;
203 
204 /*
205  * The following structure is what is added to the Tcl event queue when file
206  * handlers are ready to fire.
207  */
208 
209 typedef struct FileHandlerEvent {
210     Tcl_Event header;		/* Information that is standard for all
211 				 * events. */
212     int fd;			/* File descriptor that is ready. Used to find
213 				 * the FileHandler structure for the file
214 				 * (can't point directly to the FileHandler
215 				 * structure because it could go away while
216 				 * the event is queued). */
217 } FileHandlerEvent;
218 
219 /*
220  * The following structure contains a set of select() masks to track readable,
221  * writable, and exceptional conditions.
222  */
223 
224 typedef struct SelectMasks {
225     fd_set readable;
226     fd_set writable;
227     fd_set exceptional;
228 } SelectMasks;
229 
230 /*
231  * The following static structure contains the state information for the
232  * select based implementation of the Tcl notifier. One of these structures is
233  * created for each thread that is using the notifier.
234  */
235 
236 typedef struct ThreadSpecificData {
237     FileHandler *firstFileHandlerPtr;
238 				/* Pointer to head of file handler list. */
239     int polled;			/* True if the notifier thread has polled for
240 				 * this thread.
241 				 */
242     int sleeping;		/* True if runloop is inside Tcl_Sleep. */
243     int runLoopSourcePerformed;	/* True after the runLoopSource callack was
244 				 * performed. */
245     int runLoopRunning;		/* True if this thread's Tcl runLoop is running */
246     int runLoopNestingLevel;	/* Level of nested runLoop invocations */
247     int runLoopServicingEvents;	/* True if this thread's runLoop is servicing
248 				 * tcl events */
249     /* Must hold the notifierLock before accessing the following fields: */
250     /* Start notifierLock section */
251     int onList;			/* True if this thread is on the waitingList */
252     struct ThreadSpecificData *nextPtr, *prevPtr;
253 				/* All threads that are currently waiting on
254 				 * an event have their ThreadSpecificData
255 				 * structure on a doubly-linked listed formed
256 				 * from these pointers.
257 				 */
258     /* End notifierLock section */
259     OSSpinLock tsdLock;		/* Must hold this lock before acessing the
260 				 * following fields from more than one thread.
261 				 */
262     /* Start tsdLock section */
263     SelectMasks checkMasks;	/* This structure is used to build up the
264 				 * masks to be used in the next call to
265 				 * select. Bits are set in response to calls
266 				 * to Tcl_CreateFileHandler. */
267     SelectMasks readyMasks;	/* This array reflects the readable/writable
268 				 * conditions that were found to exist by the
269 				 * last call to select. */
270     int numFdBits;		/* Number of valid bits in checkMasks (one
271 				 * more than highest fd for which
272 				 * Tcl_WatchFile has been called). */
273     int polling;		/* True if this thread is polling for events */
274     CFRunLoopRef runLoop;	/* This thread's CFRunLoop, needs to be woken
275 				 * up whenever the runLoopSource is signaled */
276     CFRunLoopSourceRef runLoopSource;
277 				/* Any other thread alerts a notifier that an
278 				 * event is ready to be processed by signaling
279 				 * this CFRunLoopSource. */
280     CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl;
281 				/* Adds/removes this thread from waitingList
282 				 * when the CFRunLoop starts/stops. */
283     CFRunLoopTimerRef runLoopTimer;
284 				/* Wakes up CFRunLoop after given timeout when
285 				 * running embedded. */
286     /* End tsdLock section */
287     CFTimeInterval waitTime;	/* runLoopTimer wait time when running
288 				 * embedded. */
289 #ifdef TCL_MAC_DEBUG_NOTIFIER
290     ASLCLIENT;
291 #endif
292 } ThreadSpecificData;
293 
294 static Tcl_ThreadDataKey dataKey;
295 
296 /*
297  * The following static indicates the number of threads that have initialized
298  * notifiers.
299  *
300  * You must hold the notifierInitLock before accessing this variable.
301  */
302 
303 static int notifierCount = 0;
304 
305 /*
306  * The following variable points to the head of a doubly-linked list of
307  * ThreadSpecificData structures for all threads that are currently waiting on
308  * an event.
309  *
310  * You must hold the notifierLock before accessing this list.
311  */
312 
313 static ThreadSpecificData *waitingListPtr = NULL;
314 
315 /*
316  * The notifier thread spends all its time in select() waiting for a file
317  * descriptor associated with one of the threads on the waitingListPtr list to
318  * do something interesting. But if the contents of the waitingListPtr list
319  * ever changes, we need to wake up and restart the select() system call. You
320  * can wake up the notifier thread by writing a single byte to the file
321  * descriptor defined below. This file descriptor is the input-end of a pipe
322  * and the notifier thread is listening for data on the output-end of the same
323  * pipe. Hence writing to this file descriptor will cause the select() system
324  * call to return and wake up the notifier thread.
325  *
326  * You must hold the notifierLock lock before writing to the pipe.
327  */
328 
329 static int triggerPipe = -1;
330 static int receivePipe = -1; /* Output end of triggerPipe */
331 
332 /*
333  * The following static indicates if the notifier thread is running.
334  *
335  * You must hold the notifierInitLock before accessing this variable.
336  */
337 
338 static int notifierThreadRunning;
339 
340 /*
341  * This is the thread ID of the notifier thread that does select.
342  * Only valid when notifierThreadRunning is non-zero.
343  *
344  * You must hold the notifierInitLock before accessing this variable.
345  */
346 
347 static pthread_t notifierThread;
348 
349 /*
350  * Custom runloop mode for running with only the runloop source for the
351  * notifier thread
352  */
353 
354 #ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE
355 #define TCL_EVENTS_ONLY_RUN_LOOP_MODE	"com.tcltk.tclEventsOnlyRunLoopMode"
356 #endif
357 #ifdef __CONSTANT_CFSTRINGS__
358 #define tclEventsOnlyRunLoopMode	CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE)
359 #else
360 static CFStringRef tclEventsOnlyRunLoopMode = NULL;
361 #endif
362 
363 /*
364  * CFTimeInterval to wait forever.
365  */
366 
367 #define CF_TIMEINTERVAL_FOREVER 5.05e8
368 
369 /*
370  * Static routines defined in this file.
371  */
372 
373 static void	StartNotifierThread(void);
374 static void	NotifierThreadProc(ClientData clientData)
375 			__attribute__ ((__noreturn__));
376 static int	FileHandlerEventProc(Tcl_Event *evPtr, int flags);
377 static void	TimerWakeUp(CFRunLoopTimerRef timer, void *info);
378 static void	QueueFileEvents(void *info);
379 static void	UpdateWaitingListAndServiceEvents(CFRunLoopObserverRef observer,
380 			CFRunLoopActivity activity, void *info);
381 static int	OnOffWaitingList(ThreadSpecificData *tsdPtr, int onList,
382 			int signalNotifier);
383 
384 #ifdef HAVE_PTHREAD_ATFORK
385 static int	atForkInit = 0;
386 static void	AtForkPrepare(void);
387 static void	AtForkParent(void);
388 static void	AtForkChild(void);
389 #if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040
390 /* Support for weakly importing pthread_atfork. */
391 #define WEAK_IMPORT_PTHREAD_ATFORK
392 extern int	pthread_atfork(void (*prepare)(void), void (*parent)(void),
393 		    void (*child)(void)) WEAK_IMPORT_ATTRIBUTE;
394 #endif /* HAVE_WEAK_IMPORT */
395 /*
396  * On Darwin 9 and later, it is not possible to call CoreFoundation after
397  * a fork.
398  */
399 #if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \
400 	MAC_OS_X_VERSION_MIN_REQUIRED < 1050
401 MODULE_SCOPE long tclMacOSXDarwinRelease;
402 #define noCFafterFork (tclMacOSXDarwinRelease >= 9)
403 #else /* MAC_OS_X_VERSION_MIN_REQUIRED */
404 #define noCFafterFork 1
405 #endif /* MAC_OS_X_VERSION_MIN_REQUIRED */
406 #endif /* HAVE_PTHREAD_ATFORK */
407 
408 /*
409  *----------------------------------------------------------------------
410  *
411  * Tcl_InitNotifier --
412  *
413  *	Initializes the platform specific notifier state.
414  *
415  * Results:
416  *	Returns a handle to the notifier state for this thread.
417  *
418  * Side effects:
419  *	None.
420  *
421  *----------------------------------------------------------------------
422  */
423 
424 ClientData
Tcl_InitNotifier(void)425 Tcl_InitNotifier(void)
426 {
427     ThreadSpecificData *tsdPtr;
428 
429     tsdPtr = TCL_TSD_INIT(&dataKey);
430 
431 #ifdef WEAK_IMPORT_SPINLOCKLOCK
432     /*
433      * Initialize support for weakly imported spinlock API.
434      */
435 
436     if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) {
437 	Tcl_Panic("Tcl_InitNotifier: pthread_once failed");
438     }
439 #endif
440 
441 #ifndef __CONSTANT_CFSTRINGS__
442     if (!tclEventsOnlyRunLoopMode) {
443 	tclEventsOnlyRunLoopMode = CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE);
444     }
445 #endif
446 
447     /*
448      * Initialize CFRunLoopSource and add it to CFRunLoop of this thread.
449      */
450 
451     if (!tsdPtr->runLoop) {
452 	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
453 	CFRunLoopSourceRef runLoopSource;
454 	CFRunLoopSourceContext runLoopSourceContext;
455 	CFRunLoopObserverContext runLoopObserverContext;
456 	CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl;
457 
458 	bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext));
459 	runLoopSourceContext.info = tsdPtr;
460 	runLoopSourceContext.perform = QueueFileEvents;
461 	runLoopSource = CFRunLoopSourceCreate(NULL, LONG_MIN,
462 		&runLoopSourceContext);
463 	if (!runLoopSource) {
464 	    Tcl_Panic("Tcl_InitNotifier: could not create CFRunLoopSource");
465 	}
466 	CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopCommonModes);
467 	CFRunLoopAddSource(runLoop, runLoopSource, tclEventsOnlyRunLoopMode);
468 
469 	bzero(&runLoopObserverContext, sizeof(CFRunLoopObserverContext));
470 	runLoopObserverContext.info = tsdPtr;
471 	runLoopObserver = CFRunLoopObserverCreate(NULL,
472 		kCFRunLoopEntry|kCFRunLoopExit|kCFRunLoopBeforeWaiting, TRUE,
473 		LONG_MIN, UpdateWaitingListAndServiceEvents,
474 		&runLoopObserverContext);
475 	if (!runLoopObserver) {
476 	    Tcl_Panic("Tcl_InitNotifier: could not create "
477 		    "CFRunLoopObserver");
478 	}
479 	CFRunLoopAddObserver(runLoop, runLoopObserver, kCFRunLoopCommonModes);
480 
481 	/*
482 	 * Create a second CFRunLoopObserver with the same callback as above
483 	 * for the tclEventsOnlyRunLoopMode to ensure that the callback can be
484 	 * re-entered via Tcl_ServiceAll() in the kCFRunLoopBeforeWaiting case
485 	 * (CFRunLoop prevents observer callback re-entry of a given observer
486 	 * instance).
487 	 */
488 
489 	runLoopObserverTcl = CFRunLoopObserverCreate(NULL,
490 		kCFRunLoopEntry|kCFRunLoopExit|kCFRunLoopBeforeWaiting, TRUE,
491 		LONG_MIN, UpdateWaitingListAndServiceEvents,
492 		&runLoopObserverContext);
493 	if (!runLoopObserverTcl) {
494 	    Tcl_Panic("Tcl_InitNotifier: could not create "
495 		    "CFRunLoopObserver");
496 	}
497 	CFRunLoopAddObserver(runLoop, runLoopObserverTcl,
498 		tclEventsOnlyRunLoopMode);
499 
500 	tsdPtr->runLoop = runLoop;
501 	tsdPtr->runLoopSource = runLoopSource;
502 	tsdPtr->runLoopObserver = runLoopObserver;
503 	tsdPtr->runLoopObserverTcl = runLoopObserverTcl;
504 	tsdPtr->runLoopTimer = NULL;
505 	tsdPtr->waitTime = CF_TIMEINTERVAL_FOREVER;
506 	tsdPtr->tsdLock = SPINLOCK_INIT;
507     }
508 
509     LOCK_NOTIFIER_INIT;
510 #ifdef HAVE_PTHREAD_ATFORK
511     /*
512      * Install pthread_atfork handlers to reinitialize the notifier in the
513      * child of a fork.
514      */
515 
516     if (
517 #ifdef WEAK_IMPORT_PTHREAD_ATFORK
518 	    pthread_atfork != NULL &&
519 #endif
520 	    !atForkInit) {
521 	int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild);
522 
523 	if (result) {
524 	    Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed");
525 	}
526 	atForkInit = 1;
527     }
528 #endif
529     if (notifierCount == 0) {
530 	int fds[2], status;
531 
532 	/*
533 	 * Initialize trigger pipe.
534 	 */
535 
536 	if (pipe(fds) != 0) {
537 	    Tcl_Panic("Tcl_InitNotifier: could not create trigger pipe");
538 	}
539 
540 	status = fcntl(fds[0], F_GETFL);
541 	status |= O_NONBLOCK;
542 	if (fcntl(fds[0], F_SETFL, status) < 0) {
543 	    Tcl_Panic("Tcl_InitNotifier: could not make receive pipe non "
544 		    "blocking");
545 	}
546 	status = fcntl(fds[1], F_GETFL);
547 	status |= O_NONBLOCK;
548 	if (fcntl(fds[1], F_SETFL, status) < 0) {
549 	    Tcl_Panic("Tcl_InitNotifier: could not make trigger pipe non "
550 		    "blocking");
551 	}
552 
553 	receivePipe = fds[0];
554 	triggerPipe = fds[1];
555 
556 	/*
557 	 * Create notifier thread lazily in Tcl_WaitForEvent() to avoid
558 	 * interfering with fork() followed immediately by execve() (we cannot
559 	 * execve() when more than one thread is present).
560 	 */
561 
562 	notifierThreadRunning = 0;
563 	OPEN_NOTIFIER_LOG;
564     }
565     ENABLE_ASL;
566     notifierCount++;
567     UNLOCK_NOTIFIER_INIT;
568 
569     return (ClientData) tsdPtr;
570 }
571 
572 /*
573  *----------------------------------------------------------------------
574  *
575  * TclMacOSXNotifierAddRunLoopMode --
576  *
577  *	Add the tcl notifier RunLoop source, observer and timer (if any)
578  *	to the given RunLoop mode.
579  *
580  * Results:
581  *	None.
582  *
583  * Side effects:
584  *	None.
585  *
586  *----------------------------------------------------------------------
587  */
588 
589 void
TclMacOSXNotifierAddRunLoopMode(CONST void * runLoopMode)590 TclMacOSXNotifierAddRunLoopMode(
591     CONST void *runLoopMode)
592 {
593     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
594     CFStringRef mode = (CFStringRef) runLoopMode;
595 
596     if (tsdPtr->runLoop) {
597 	CFRunLoopAddSource(tsdPtr->runLoop, tsdPtr->runLoopSource, mode);
598 	CFRunLoopAddObserver(tsdPtr->runLoop, tsdPtr->runLoopObserver, mode);
599 	if (tsdPtr->runLoopTimer) {
600 	    CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, mode);
601 	}
602     }
603 }
604 
605 /*
606  *----------------------------------------------------------------------
607  *
608  * StartNotifierThread --
609  *
610  *	Start notifier thread if necessary.
611  *
612  * Results:
613  *	None.
614  *
615  * Side effects:
616  *	None.
617  *
618  *----------------------------------------------------------------------
619  */
620 
621 static void
StartNotifierThread(void)622 StartNotifierThread(void)
623 {
624     LOCK_NOTIFIER_INIT;
625     if (!notifierCount) {
626 	Tcl_Panic("StartNotifierThread: notifier not initialized");
627     }
628     if (!notifierThreadRunning) {
629 	int result;
630 	pthread_attr_t attr;
631 
632 	pthread_attr_init(&attr);
633 	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
634 	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
635 	pthread_attr_setstacksize(&attr, 60 * 1024);
636 	result = pthread_create(&notifierThread, &attr,
637 		(void * (*)(void *))NotifierThreadProc, NULL);
638 	pthread_attr_destroy(&attr);
639 	if (result) {
640 	    Tcl_Panic("StartNotifierThread: unable to start notifier thread");
641 	}
642 	notifierThreadRunning = 1;
643     }
644     UNLOCK_NOTIFIER_INIT;
645 }
646 
647 
648 /*
649  *----------------------------------------------------------------------
650  *
651  * Tcl_FinalizeNotifier --
652  *
653  *	This function is called to cleanup the notifier state before a thread
654  *	is terminated.
655  *
656  * Results:
657  *	None.
658  *
659  * Side effects:
660  *	May terminate the background notifier thread if this is the last
661  *	notifier instance.
662  *
663  *----------------------------------------------------------------------
664  */
665 
666 void
Tcl_FinalizeNotifier(ClientData clientData)667 Tcl_FinalizeNotifier(
668     ClientData clientData)		/* Not used. */
669 {
670     ThreadSpecificData *tsdPtr;
671 
672     tsdPtr = TCL_TSD_INIT(&dataKey);
673 
674     LOCK_NOTIFIER_INIT;
675     notifierCount--;
676     DISABLE_ASL;
677 
678     /*
679      * If this is the last thread to use the notifier, close the notifier pipe
680      * and wait for the background thread to terminate.
681      */
682 
683     if (notifierCount == 0) {
684 	if (triggerPipe != -1) {
685 	    /*
686 	     * Send "q" message to the notifier thread so that it will
687 	     * terminate. The notifier will return from its call to select()
688 	     * and notice that a "q" message has arrived, it will then close
689 	     * its side of the pipe and terminate its thread. Note the we can
690 	     * not just close the pipe and check for EOF in the notifier thread
691 	     * because if a background child process was created with exec,
692 	     * select() would not register the EOF on the pipe until the child
693 	     * processes had terminated. [Bug: 4139] [Bug: 1222872]
694 	     */
695 
696 	    write(triggerPipe, "q", 1);
697 	    close(triggerPipe);
698 
699 	    if (notifierThreadRunning) {
700 		int result = pthread_join(notifierThread, NULL);
701 
702 		if (result) {
703 		    Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier "
704 			    "thread");
705 		}
706 		notifierThreadRunning = 0;
707 	    }
708 
709 	    close(receivePipe);
710 	    triggerPipe = -1;
711 	}
712 	CLOSE_NOTIFIER_LOG;
713     }
714     UNLOCK_NOTIFIER_INIT;
715 
716     LOCK_NOTIFIER_TSD;		/* For concurrency with Tcl_AlertNotifier */
717     if (tsdPtr->runLoop) {
718 	tsdPtr->runLoop = NULL;
719 
720 	/*
721 	 * Remove runLoopSource, runLoopObserver and runLoopTimer from all
722 	 * CFRunLoops.
723 	 */
724 
725 	CFRunLoopSourceInvalidate(tsdPtr->runLoopSource);
726 	CFRelease(tsdPtr->runLoopSource);
727 	tsdPtr->runLoopSource = NULL;
728 	CFRunLoopObserverInvalidate(tsdPtr->runLoopObserver);
729 	CFRelease(tsdPtr->runLoopObserver);
730 	tsdPtr->runLoopObserver = NULL;
731 	CFRunLoopObserverInvalidate(tsdPtr->runLoopObserverTcl);
732 	CFRelease(tsdPtr->runLoopObserverTcl);
733 	tsdPtr->runLoopObserverTcl = NULL;
734 	if (tsdPtr->runLoopTimer) {
735 	    CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer);
736 	    CFRelease(tsdPtr->runLoopTimer);
737 	    tsdPtr->runLoopTimer = NULL;
738 	}
739     }
740     UNLOCK_NOTIFIER_TSD;
741 }
742 
743 /*
744  *----------------------------------------------------------------------
745  *
746  * Tcl_AlertNotifier --
747  *
748  *	Wake up the specified notifier from any thread. This routine is called
749  *	by the platform independent notifier code whenever the Tcl_ThreadAlert
750  *	routine is called. This routine is guaranteed not to be called on a
751  *	given notifier after Tcl_FinalizeNotifier is called for that notifier.
752  *
753  * Results:
754  *	None.
755  *
756  * Side effects:
757  *	Signals the notifier condition variable for the specified notifier.
758  *
759  *----------------------------------------------------------------------
760  */
761 
762 void
Tcl_AlertNotifier(ClientData clientData)763 Tcl_AlertNotifier(
764     ClientData clientData)
765 {
766     ThreadSpecificData *tsdPtr = clientData;
767 
768     LOCK_NOTIFIER_TSD;
769     if (tsdPtr->runLoop) {
770 	CFRunLoopSourceSignal(tsdPtr->runLoopSource);
771 	CFRunLoopWakeUp(tsdPtr->runLoop);
772     }
773     UNLOCK_NOTIFIER_TSD;
774 }
775 
776 /*
777  *----------------------------------------------------------------------
778  *
779  * Tcl_SetTimer --
780  *
781  *	This function sets the current notifier timer value.
782  *
783  * Results:
784  *	None.
785  *
786  * Side effects:
787  *	Replaces any previous timer.
788  *
789  *----------------------------------------------------------------------
790  */
791 
792 void
Tcl_SetTimer(Tcl_Time * timePtr)793 Tcl_SetTimer(
794     Tcl_Time *timePtr)		/* Timeout value, may be NULL. */
795 {
796     ThreadSpecificData *tsdPtr;
797     CFRunLoopTimerRef runLoopTimer;
798     CFTimeInterval waitTime;
799 
800     if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) {
801 	tclStubs.tcl_SetTimer(timePtr);
802 	return;
803     }
804 
805     tsdPtr = TCL_TSD_INIT(&dataKey);
806     runLoopTimer = tsdPtr->runLoopTimer;
807     if (!runLoopTimer) {
808 	return;
809     }
810     if (timePtr) {
811 	Tcl_Time vTime  = *timePtr;
812 
813 	if (vTime.sec != 0 || vTime.usec != 0) {
814 	    tclScaleTimeProcPtr(&vTime, tclTimeClientData);
815 	    waitTime = vTime.sec + 1.0e-6 * vTime.usec;
816 	} else {
817 	    waitTime = 0;
818 	}
819     } else {
820 	waitTime = CF_TIMEINTERVAL_FOREVER;
821     }
822     tsdPtr->waitTime = waitTime;
823     CFRunLoopTimerSetNextFireDate(runLoopTimer,
824 	    CFAbsoluteTimeGetCurrent() + waitTime);
825 }
826 
827 /*
828  *----------------------------------------------------------------------
829  *
830  * TimerWakeUp --
831  *
832  *	CFRunLoopTimer callback.
833  *
834  * Results:
835  *	None.
836  *
837  * Side effects:
838  *	None.
839  *
840  *----------------------------------------------------------------------
841  */
842 
843 static void
TimerWakeUp(CFRunLoopTimerRef timer,void * info)844 TimerWakeUp(
845     CFRunLoopTimerRef timer,
846     void *info)
847 {
848 }
849 
850 /*
851  *----------------------------------------------------------------------
852  *
853  * Tcl_ServiceModeHook --
854  *
855  *	This function is invoked whenever the service mode changes.
856  *
857  * Results:
858  *	None.
859  *
860  * Side effects:
861  *	None.
862  *
863  *----------------------------------------------------------------------
864  */
865 
866 void
Tcl_ServiceModeHook(int mode)867 Tcl_ServiceModeHook(
868     int mode)			/* Either TCL_SERVICE_ALL, or
869 				 * TCL_SERVICE_NONE. */
870 {
871     ThreadSpecificData *tsdPtr;
872 
873     tsdPtr = TCL_TSD_INIT(&dataKey);
874 
875     if (mode == TCL_SERVICE_ALL && !tsdPtr->runLoopTimer) {
876 	if (!tsdPtr->runLoop) {
877 	    Tcl_Panic("Tcl_ServiceModeHook: Notifier not initialized");
878 	}
879 	tsdPtr->runLoopTimer = CFRunLoopTimerCreate(NULL,
880 		CFAbsoluteTimeGetCurrent() + CF_TIMEINTERVAL_FOREVER,
881 		CF_TIMEINTERVAL_FOREVER, 0, 0, TimerWakeUp, NULL);
882 	if (tsdPtr->runLoopTimer) {
883 	    CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer,
884 		    kCFRunLoopCommonModes);
885 	    StartNotifierThread();
886 	}
887     }
888 }
889 
890 /*
891  *----------------------------------------------------------------------
892  *
893  * Tcl_CreateFileHandler --
894  *
895  *	This function registers a file handler with the select notifier.
896  *
897  * Results:
898  *	None.
899  *
900  * Side effects:
901  *	Creates a new file handler structure.
902  *
903  *----------------------------------------------------------------------
904  */
905 
906 void
Tcl_CreateFileHandler(int fd,int mask,Tcl_FileProc * proc,ClientData clientData)907 Tcl_CreateFileHandler(
908     int fd,			/* Handle of stream to watch. */
909     int mask,			/* OR'ed combination of TCL_READABLE,
910 				 * TCL_WRITABLE, and TCL_EXCEPTION: indicates
911 				 * conditions under which proc should be
912 				 * called. */
913     Tcl_FileProc *proc,		/* Function to call for each selected
914 				 * event. */
915     ClientData clientData)	/* Arbitrary data to pass to proc. */
916 {
917     ThreadSpecificData *tsdPtr;
918     FileHandler *filePtr;
919 
920     if (tclStubs.tcl_CreateFileHandler !=
921 	    tclOriginalNotifier.createFileHandlerProc) {
922 	tclStubs.tcl_CreateFileHandler(fd, mask, proc, clientData);
923 	return;
924     }
925 
926     tsdPtr = TCL_TSD_INIT(&dataKey);
927 
928     for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
929 	    filePtr = filePtr->nextPtr) {
930 	if (filePtr->fd == fd) {
931 	    break;
932 	}
933     }
934     if (filePtr == NULL) {
935 	filePtr = (FileHandler *) ckalloc(sizeof(FileHandler));
936 	filePtr->fd = fd;
937 	filePtr->readyMask = 0;
938 	filePtr->nextPtr = tsdPtr->firstFileHandlerPtr;
939 	tsdPtr->firstFileHandlerPtr = filePtr;
940     }
941     filePtr->proc = proc;
942     filePtr->clientData = clientData;
943     filePtr->mask = mask;
944 
945     /*
946      * Update the check masks for this file.
947      */
948 
949     LOCK_NOTIFIER_TSD;
950     if (mask & TCL_READABLE) {
951 	FD_SET(fd, &(tsdPtr->checkMasks.readable));
952     } else {
953 	FD_CLR(fd, &(tsdPtr->checkMasks.readable));
954     }
955     if (mask & TCL_WRITABLE) {
956 	FD_SET(fd, &(tsdPtr->checkMasks.writable));
957     } else {
958 	FD_CLR(fd, &(tsdPtr->checkMasks.writable));
959     }
960     if (mask & TCL_EXCEPTION) {
961 	FD_SET(fd, &(tsdPtr->checkMasks.exceptional));
962     } else {
963 	FD_CLR(fd, &(tsdPtr->checkMasks.exceptional));
964     }
965     if (tsdPtr->numFdBits <= fd) {
966 	tsdPtr->numFdBits = fd+1;
967     }
968     UNLOCK_NOTIFIER_TSD;
969 }
970 
971 /*
972  *----------------------------------------------------------------------
973  *
974  * Tcl_DeleteFileHandler --
975  *
976  *	Cancel a previously-arranged callback arrangement for a file.
977  *
978  * Results:
979  *	None.
980  *
981  * Side effects:
982  *	If a callback was previously registered on file, remove it.
983  *
984  *----------------------------------------------------------------------
985  */
986 
987 void
Tcl_DeleteFileHandler(int fd)988 Tcl_DeleteFileHandler(
989     int fd)			/* Stream id for which to remove callback
990 				 * function. */
991 {
992     FileHandler *filePtr, *prevPtr;
993     int i, numFdBits;
994     ThreadSpecificData *tsdPtr;
995 
996     if (tclStubs.tcl_DeleteFileHandler !=
997 	    tclOriginalNotifier.deleteFileHandlerProc) {
998 	tclStubs.tcl_DeleteFileHandler(fd);
999 	return;
1000     }
1001 
1002     tsdPtr = TCL_TSD_INIT(&dataKey);
1003     numFdBits = -1;
1004 
1005     /*
1006      * Find the entry for the given file (and return if there isn't one).
1007      */
1008 
1009     for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ;
1010 	    prevPtr = filePtr, filePtr = filePtr->nextPtr) {
1011 	if (filePtr == NULL) {
1012 	    return;
1013 	}
1014 	if (filePtr->fd == fd) {
1015 	    break;
1016 	}
1017     }
1018 
1019     /*
1020      * Find current max fd.
1021      */
1022 
1023     if (fd+1 == tsdPtr->numFdBits) {
1024 	numFdBits = 0;
1025 	for (i = fd-1; i >= 0; i--) {
1026 	    if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))
1027 		    || FD_ISSET(i, &(tsdPtr->checkMasks.writable))
1028 		    || FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) {
1029 		numFdBits = i+1;
1030 		break;
1031 	    }
1032 	}
1033     }
1034 
1035     LOCK_NOTIFIER_TSD;
1036     if (numFdBits != -1) {
1037 	tsdPtr->numFdBits = numFdBits;
1038     }
1039 
1040     /*
1041      * Update the check masks for this file.
1042      */
1043 
1044     if (filePtr->mask & TCL_READABLE) {
1045 	FD_CLR(fd, &(tsdPtr->checkMasks.readable));
1046     }
1047     if (filePtr->mask & TCL_WRITABLE) {
1048 	FD_CLR(fd, &(tsdPtr->checkMasks.writable));
1049     }
1050     if (filePtr->mask & TCL_EXCEPTION) {
1051 	FD_CLR(fd, &(tsdPtr->checkMasks.exceptional));
1052     }
1053     UNLOCK_NOTIFIER_TSD;
1054 
1055     /*
1056      * Clean up information in the callback record.
1057      */
1058 
1059     if (prevPtr == NULL) {
1060 	tsdPtr->firstFileHandlerPtr = filePtr->nextPtr;
1061     } else {
1062 	prevPtr->nextPtr = filePtr->nextPtr;
1063     }
1064     ckfree((char *) filePtr);
1065 }
1066 
1067 /*
1068  *----------------------------------------------------------------------
1069  *
1070  * FileHandlerEventProc --
1071  *
1072  *	This function is called by Tcl_ServiceEvent when a file event reaches
1073  *	the front of the event queue. This function is responsible for
1074  *	actually handling the event by invoking the callback for the file
1075  *	handler.
1076  *
1077  * Results:
1078  *	Returns 1 if the event was handled, meaning it should be removed from
1079  *	the queue. Returns 0 if the event was not handled, meaning it should
1080  *	stay on the queue. The only time the event isn't handled is if the
1081  *	TCL_FILE_EVENTS flag bit isn't set.
1082  *
1083  * Side effects:
1084  *	Whatever the file handler's callback function does.
1085  *
1086  *----------------------------------------------------------------------
1087  */
1088 
1089 static int
FileHandlerEventProc(Tcl_Event * evPtr,int flags)1090 FileHandlerEventProc(
1091     Tcl_Event *evPtr,		/* Event to service. */
1092     int flags)			/* Flags that indicate what events to handle,
1093 				 * such as TCL_FILE_EVENTS. */
1094 {
1095     int mask;
1096     FileHandler *filePtr;
1097     FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr;
1098     ThreadSpecificData *tsdPtr;
1099 
1100     if (!(flags & TCL_FILE_EVENTS)) {
1101 	return 0;
1102     }
1103 
1104     /*
1105      * Search through the file handlers to find the one whose handle matches
1106      * the event. We do this rather than keeping a pointer to the file handler
1107      * directly in the event, so that the handler can be deleted while the
1108      * event is queued without leaving a dangling pointer.
1109      */
1110 
1111     tsdPtr = TCL_TSD_INIT(&dataKey);
1112     for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
1113 	    filePtr = filePtr->nextPtr) {
1114 	if (filePtr->fd != fileEvPtr->fd) {
1115 	    continue;
1116 	}
1117 
1118 	/*
1119 	 * The code is tricky for two reasons:
1120 	 * 1. The file handler's desired events could have changed since the
1121 	 *    time when the event was queued, so AND the ready mask with the
1122 	 *    desired mask.
1123 	 * 2. The file could have been closed and re-opened since the time
1124 	 *    when the event was queued. This is why the ready mask is stored
1125 	 *    in the file handler rather than the queued event: it will be
1126 	 *    zeroed when a new file handler is created for the newly opened
1127 	 *    file.
1128 	 */
1129 
1130 	mask = filePtr->readyMask & filePtr->mask;
1131 	filePtr->readyMask = 0;
1132 	if (mask != 0) {
1133 	    LOCK_NOTIFIER_TSD;
1134 	    if (mask & TCL_READABLE) {
1135 		FD_CLR(filePtr->fd, &(tsdPtr->readyMasks.readable));
1136 	    }
1137 	    if (mask & TCL_WRITABLE) {
1138 		FD_CLR(filePtr->fd, &(tsdPtr->readyMasks.writable));
1139 	    }
1140 	    if (mask & TCL_EXCEPTION) {
1141 		FD_CLR(filePtr->fd, &(tsdPtr->readyMasks.exceptional));
1142 	    }
1143 	    UNLOCK_NOTIFIER_TSD;
1144 	    filePtr->proc(filePtr->clientData, mask);
1145 	}
1146 	break;
1147     }
1148     return 1;
1149 }
1150 
1151 /*
1152  *----------------------------------------------------------------------
1153  *
1154  * Tcl_WaitForEvent --
1155  *
1156  *	This function is called by Tcl_DoOneEvent to wait for new events on
1157  *	the message queue. If the block time is 0, then Tcl_WaitForEvent just
1158  *	polls without blocking.
1159  *
1160  * Results:
1161  *	Returns 0 if a tcl event or timeout ocurred and 1 if a non-tcl
1162  *	CFRunLoop source was processed.
1163  *
1164  * Side effects:
1165  *	None.
1166  *
1167  *----------------------------------------------------------------------
1168  */
1169 
1170 int
Tcl_WaitForEvent(Tcl_Time * timePtr)1171 Tcl_WaitForEvent(
1172     Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
1173 {
1174     int result, polling, runLoopRunning;
1175     CFTimeInterval waitTime;
1176     SInt32 runLoopStatus;
1177     ThreadSpecificData *tsdPtr;
1178 
1179     if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) {
1180 	return tclStubs.tcl_WaitForEvent(timePtr);
1181     }
1182     result = -1;
1183     polling = 0;
1184     waitTime = CF_TIMEINTERVAL_FOREVER;
1185     tsdPtr = TCL_TSD_INIT(&dataKey);
1186 
1187     if (!tsdPtr->runLoop) {
1188 	Tcl_Panic("Tcl_WaitForEvent: Notifier not initialized");
1189     }
1190 
1191     if (timePtr) {
1192 	Tcl_Time vTime  = *timePtr;
1193 
1194 	/*
1195 	 * TIP #233 (Virtualized Time). Is virtual time in effect? And do we
1196 	 * actually have something to scale? If yes to both then we call the
1197 	 * handler to do this scaling.
1198 	 */
1199 
1200 	if (vTime.sec != 0 || vTime.usec != 0) {
1201 	    tclScaleTimeProcPtr(&vTime, tclTimeClientData);
1202 	    waitTime = vTime.sec + 1.0e-6 * vTime.usec;
1203 	} else {
1204 	    /*
1205 	     * Polling: pretend to wait for files and tell the notifier thread
1206 	     * what we are doing. The notifier thread makes sure it goes
1207 	     * through select with its select mask in the same state as ours
1208 	     * currently is. We block until that happens.
1209 	     */
1210 
1211 	    polling = 1;
1212 	}
1213     }
1214 
1215     StartNotifierThread();
1216 
1217     LOCK_NOTIFIER_TSD;
1218     tsdPtr->polling = polling;
1219     UNLOCK_NOTIFIER_TSD;
1220     tsdPtr->runLoopSourcePerformed = 0;
1221 
1222     /*
1223      * If the Tcl runloop is already running (e.g. if Tcl_WaitForEvent was
1224      * called recursively) or is servicing events via the runloop observer,
1225      * re-run it in a custom runloop mode containing only the source for the
1226      * notifier thread, otherwise wakeups from other sources added to the
1227      * common runloop modes might get lost or 3rd party event handlers might
1228      * get called when they do not expect to be.
1229      */
1230 
1231     runLoopRunning = tsdPtr->runLoopRunning;
1232     tsdPtr->runLoopRunning = 1;
1233     runLoopStatus = CFRunLoopRunInMode(tsdPtr->runLoopServicingEvents ||
1234 	    runLoopRunning ? tclEventsOnlyRunLoopMode : kCFRunLoopDefaultMode,
1235 	    waitTime, TRUE);
1236     tsdPtr->runLoopRunning = runLoopRunning;
1237 
1238     LOCK_NOTIFIER_TSD;
1239     tsdPtr->polling = 0;
1240     UNLOCK_NOTIFIER_TSD;
1241     switch (runLoopStatus) {
1242     case kCFRunLoopRunFinished:
1243        Tcl_Panic("Tcl_WaitForEvent: CFRunLoop finished");
1244 	break;
1245     case kCFRunLoopRunTimedOut:
1246 	QueueFileEvents(tsdPtr);
1247 	result = 0;
1248 	break;
1249     case kCFRunLoopRunStopped:
1250     case kCFRunLoopRunHandledSource:
1251 	result = tsdPtr->runLoopSourcePerformed ? 0 : 1;
1252 	break;
1253     }
1254 
1255     return result;
1256 }
1257 
1258 /*
1259  *----------------------------------------------------------------------
1260  *
1261  * QueueFileEvents --
1262  *
1263  *	CFRunLoopSource callback for queueing file events.
1264  *
1265  * Results:
1266  *	None.
1267  *
1268  * Side effects:
1269  *	Queues file events that are detected by the select.
1270  *
1271  *----------------------------------------------------------------------
1272  */
1273 
1274 static void
QueueFileEvents(void * info)1275 QueueFileEvents(
1276     void *info)
1277 {
1278     SelectMasks readyMasks;
1279     FileHandler *filePtr;
1280     ThreadSpecificData *tsdPtr = (ThreadSpecificData *) info;
1281 
1282     /*
1283      * Queue all detected file events.
1284      */
1285 
1286     LOCK_NOTIFIER_TSD;
1287     FD_COPY(&(tsdPtr->readyMasks.readable), &readyMasks.readable);
1288     FD_COPY(&(tsdPtr->readyMasks.writable), &readyMasks.writable);
1289     FD_COPY(&(tsdPtr->readyMasks.exceptional), &readyMasks.exceptional);
1290     FD_ZERO(&(tsdPtr->readyMasks.readable));
1291     FD_ZERO(&(tsdPtr->readyMasks.writable));
1292     FD_ZERO(&(tsdPtr->readyMasks.exceptional));
1293     UNLOCK_NOTIFIER_TSD;
1294     tsdPtr->runLoopSourcePerformed = 1;
1295 
1296     for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL);
1297 	    filePtr = filePtr->nextPtr) {
1298 	int mask = 0;
1299 
1300 	if (FD_ISSET(filePtr->fd, &readyMasks.readable)) {
1301 	    mask |= TCL_READABLE;
1302 	}
1303 	if (FD_ISSET(filePtr->fd, &readyMasks.writable)) {
1304 	    mask |= TCL_WRITABLE;
1305 	}
1306 	if (FD_ISSET(filePtr->fd, &readyMasks.exceptional)) {
1307 	    mask |= TCL_EXCEPTION;
1308 	}
1309 	if (!mask) {
1310 	    continue;
1311 	}
1312 
1313 	/*
1314 	 * Don't bother to queue an event if the mask was previously non-zero
1315 	 * since an event must still be on the queue.
1316 	 */
1317 
1318 	if (filePtr->readyMask == 0) {
1319 	    FileHandlerEvent *fileEvPtr = (FileHandlerEvent *)
1320 		    ckalloc(sizeof(FileHandlerEvent));
1321 	    fileEvPtr->header.proc = FileHandlerEventProc;
1322 	    fileEvPtr->fd = filePtr->fd;
1323 	    Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
1324 	}
1325 	filePtr->readyMask = mask;
1326     }
1327 }
1328 
1329 /*
1330  *----------------------------------------------------------------------
1331  *
1332  * UpdateWaitingListAndServiceEvents --
1333  *
1334  *	CFRunLoopObserver callback for updating waitingList and
1335  *	servicing Tcl events.
1336  *
1337  * Results:
1338  *	None.
1339  *
1340  * Side effects:
1341  *	None.
1342  *
1343  *----------------------------------------------------------------------
1344  */
1345 
1346 static void
UpdateWaitingListAndServiceEvents(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void * info)1347 UpdateWaitingListAndServiceEvents(
1348     CFRunLoopObserverRef observer,
1349     CFRunLoopActivity activity,
1350     void *info)
1351 {
1352     ThreadSpecificData *tsdPtr = (ThreadSpecificData*) info;
1353 
1354     if (tsdPtr->sleeping) {
1355 	return;
1356     }
1357     switch (activity) {
1358     case kCFRunLoopEntry:
1359 	tsdPtr->runLoopNestingLevel++;
1360 	if (tsdPtr->numFdBits > 0 || tsdPtr->polling) {
1361 	    LOCK_NOTIFIER;
1362 	    if (!OnOffWaitingList(tsdPtr, 1, 1) && tsdPtr->polling) {
1363 	       write(triggerPipe, "", 1);
1364 	    }
1365 	    UNLOCK_NOTIFIER;
1366 	}
1367 	break;
1368     case kCFRunLoopExit:
1369 	if (tsdPtr->runLoopNestingLevel == 1) {
1370 	    LOCK_NOTIFIER;
1371 	    OnOffWaitingList(tsdPtr, 0, 1);
1372 	    UNLOCK_NOTIFIER;
1373 	}
1374 	tsdPtr->runLoopNestingLevel--;
1375 	break;
1376     case kCFRunLoopBeforeWaiting:
1377 	if (tsdPtr->runLoopTimer && !tsdPtr->runLoopServicingEvents &&
1378 		(tsdPtr->runLoopNestingLevel > 1 || !tsdPtr->runLoopRunning)) {
1379 	    tsdPtr->runLoopServicingEvents = 1;
1380            /* This call seems to simply force event processing through and prevents hangups that have long been observed with Tk-Cocoa.  */
1381 	    Tcl_ServiceAll();
1382 	    tsdPtr->runLoopServicingEvents = 0;
1383 	}
1384 	break;
1385     default:
1386 	break;
1387     }
1388 }
1389 
1390 /*
1391  *----------------------------------------------------------------------
1392  *
1393  * OnOffWaitingList --
1394  *
1395  *	Add/remove the specified thread to/from the global waitingList
1396  *	and optionally signal the notifier.
1397  *
1398  *	!!! Requires notifierLock to be held !!!
1399  *
1400  * Results:
1401  *	Boolean indicating whether the waitingList was changed.
1402  *
1403  * Side effects:
1404  *	None.
1405  *
1406  *----------------------------------------------------------------------
1407  */
1408 
1409 static int
OnOffWaitingList(ThreadSpecificData * tsdPtr,int onList,int signalNotifier)1410 OnOffWaitingList(
1411     ThreadSpecificData *tsdPtr,
1412     int onList,
1413     int signalNotifier)
1414 {
1415     int changeWaitingList;
1416 #ifdef TCL_MAC_DEBUG_NOTIFIER
1417     if(SpinLockTry(&notifierLock)) {
1418 	Tcl_Panic("OnOffWaitingList: notifierLock unlocked");
1419     }
1420 #endif
1421     changeWaitingList = (!onList ^ !tsdPtr->onList);
1422     if (changeWaitingList) {
1423 	if (onList) {
1424 	    tsdPtr->nextPtr = waitingListPtr;
1425 	    if (waitingListPtr) {
1426 		waitingListPtr->prevPtr = tsdPtr;
1427 	    }
1428 	    tsdPtr->prevPtr = NULL;
1429 	    waitingListPtr = tsdPtr;
1430 	    tsdPtr->onList = 1;
1431 	} else {
1432 	    if (tsdPtr->prevPtr) {
1433 		tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr;
1434 	    } else {
1435 		waitingListPtr = tsdPtr->nextPtr;
1436 	    }
1437 	    if (tsdPtr->nextPtr) {
1438 		tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr;
1439 	    }
1440 	    tsdPtr->nextPtr = tsdPtr->prevPtr = NULL;
1441 	    tsdPtr->onList = 0;
1442 	}
1443 	if (signalNotifier) {
1444 	   write(triggerPipe, "", 1);
1445 	}
1446     }
1447 
1448     return changeWaitingList;
1449 }
1450 
1451 /*
1452  *----------------------------------------------------------------------
1453  *
1454  * Tcl_Sleep --
1455  *
1456  *	Delay execution for the specified number of milliseconds.
1457  *
1458  * Results:
1459  *	None.
1460  *
1461  * Side effects:
1462  *	Time passes.
1463  *
1464  *----------------------------------------------------------------------
1465  */
1466 
1467 void
Tcl_Sleep(int ms)1468 Tcl_Sleep(
1469     int ms)			/* Number of milliseconds to sleep. */
1470 {
1471     Tcl_Time vdelay;
1472     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1473 
1474     if (ms <= 0) {
1475 	return;
1476     }
1477 
1478     /*
1479      * TIP #233: Scale from virtual time to real-time.
1480      */
1481 
1482     vdelay.sec  = ms / 1000;
1483     vdelay.usec = (ms % 1000) * 1000;
1484     tclScaleTimeProcPtr(&vdelay, tclTimeClientData);
1485 
1486 
1487     if (tsdPtr->runLoop) {
1488 	CFTimeInterval waitTime;
1489 	CFRunLoopTimerRef runLoopTimer = tsdPtr->runLoopTimer;
1490 	CFAbsoluteTime nextTimerFire = 0, waitEnd, now;
1491 	SInt32 runLoopStatus;
1492 
1493 	waitTime = vdelay.sec + 1.0e-6 * vdelay.usec;
1494  	now = CFAbsoluteTimeGetCurrent();
1495 	waitEnd = now + waitTime;
1496 
1497 	if (runLoopTimer) {
1498 	    nextTimerFire = CFRunLoopTimerGetNextFireDate(runLoopTimer);
1499 	    if (nextTimerFire < waitEnd) {
1500 		CFRunLoopTimerSetNextFireDate(runLoopTimer, now +
1501 			CF_TIMEINTERVAL_FOREVER);
1502 	    } else {
1503 		runLoopTimer = NULL;
1504 	    }
1505 	}
1506 	tsdPtr->sleeping = 1;
1507 	do {
1508 	    runLoopStatus = CFRunLoopRunInMode(kCFRunLoopDefaultMode, waitTime,
1509 		    FALSE);
1510 	    switch (runLoopStatus) {
1511 	    case kCFRunLoopRunFinished:
1512 		Tcl_Panic("Tcl_Sleep: CFRunLoop finished");
1513 		break;
1514 	    case kCFRunLoopRunStopped:
1515 		TclMacOSXNotifierDbgMsg("CFRunLoop stopped");
1516 		waitTime = waitEnd - CFAbsoluteTimeGetCurrent();
1517 		break;
1518 	    case kCFRunLoopRunTimedOut:
1519 		waitTime = 0;
1520 		break;
1521 	    }
1522 	} while (waitTime > 0);
1523 	tsdPtr->sleeping = 0;
1524  	if (runLoopTimer) {
1525 	    CFRunLoopTimerSetNextFireDate(runLoopTimer, nextTimerFire);
1526 	}
1527     } else {
1528 	struct timespec waitTime;
1529 
1530 	waitTime.tv_sec = vdelay.sec;
1531 	waitTime.tv_nsec = vdelay.usec * 1000;
1532 	while (nanosleep(&waitTime, &waitTime));
1533     }
1534 }
1535 
1536 /*
1537  *----------------------------------------------------------------------
1538  *
1539  * TclUnixWaitForFile --
1540  *
1541  *	This function waits synchronously for a file to become readable or
1542  *	writable, with an optional timeout.
1543  *
1544  * Results:
1545  *	The return value is an OR'ed combination of TCL_READABLE,
1546  *	TCL_WRITABLE, and TCL_EXCEPTION, indicating the conditions that are
1547  *	present on file at the time of the return. This function will not
1548  *	return until either "timeout" milliseconds have elapsed or at least
1549  *	one of the conditions given by mask has occurred for file (a return
1550  *	value of 0 means that a timeout occurred). No normal events will be
1551  *	serviced during the execution of this function.
1552  *
1553  * Side effects:
1554  *	Time passes.
1555  *
1556  *----------------------------------------------------------------------
1557  */
1558 
1559 int
TclUnixWaitForFile(int fd,int mask,int timeout)1560 TclUnixWaitForFile(
1561     int fd,			/* Handle for file on which to wait. */
1562     int mask,			/* What to wait for: OR'ed combination of
1563 				 * TCL_READABLE, TCL_WRITABLE, and
1564 				 * TCL_EXCEPTION. */
1565     int timeout)		/* Maximum amount of time to wait for one of
1566 				 * the conditions in mask to occur, in
1567 				 * milliseconds. A value of 0 means don't wait
1568 				 * at all, and a value of -1 means wait
1569 				 * forever. */
1570 {
1571     Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */
1572     struct timeval blockTime, *timeoutPtr;
1573     int numFound, result = 0;
1574     fd_set readableMask;
1575     fd_set writableMask;
1576     fd_set exceptionalMask;
1577 
1578 #define SET_BITS(var, bits)	((var) |= (bits))
1579 #define CLEAR_BITS(var, bits)	((var) &= ~(bits))
1580 
1581 #ifndef _DARWIN_C_SOURCE
1582     /*
1583      * Sanity check fd.
1584      */
1585 
1586     if (fd >= FD_SETSIZE) {
1587 	Tcl_Panic("TclUnixWaitForFile can't handle file id %d", fd);
1588 	/* must never get here, or select masks overrun will occur below */
1589     }
1590 #endif
1591 
1592     /*
1593      * If there is a non-zero finite timeout, compute the time when we give
1594      * up.
1595      */
1596 
1597     if (timeout > 0) {
1598 	Tcl_GetTime(&now);
1599 	abortTime.sec = now.sec + timeout/1000;
1600 	abortTime.usec = now.usec + (timeout%1000)*1000;
1601 	if (abortTime.usec >= 1000000) {
1602 	    abortTime.usec -= 1000000;
1603 	    abortTime.sec += 1;
1604 	}
1605 	timeoutPtr = &blockTime;
1606     } else if (timeout == 0) {
1607 	timeoutPtr = &blockTime;
1608 	blockTime.tv_sec = 0;
1609 	blockTime.tv_usec = 0;
1610     } else {
1611 	timeoutPtr = NULL;
1612     }
1613 
1614     /*
1615      * Initialize the select masks.
1616      */
1617 
1618     FD_ZERO(&readableMask);
1619     FD_ZERO(&writableMask);
1620     FD_ZERO(&exceptionalMask);
1621 
1622     /*
1623      * Loop in a mini-event loop of our own, waiting for either the file to
1624      * become ready or a timeout to occur.
1625      */
1626 
1627     while (1) {
1628 	if (timeout > 0) {
1629 	    blockTime.tv_sec = abortTime.sec - now.sec;
1630 	    blockTime.tv_usec = abortTime.usec - now.usec;
1631 	    if (blockTime.tv_usec < 0) {
1632 		blockTime.tv_sec -= 1;
1633 		blockTime.tv_usec += 1000000;
1634 	    }
1635 	    if (blockTime.tv_sec < 0) {
1636 		blockTime.tv_sec = 0;
1637 		blockTime.tv_usec = 0;
1638 	    }
1639 	}
1640 
1641 	/*
1642 	 * Setup the select masks for the fd.
1643 	 */
1644 
1645 	if (mask & TCL_READABLE)  {
1646 	    FD_SET(fd, &readableMask);
1647 	}
1648 	if (mask & TCL_WRITABLE)  {
1649 	    FD_SET(fd, &writableMask);
1650 	}
1651 	if (mask & TCL_EXCEPTION) {
1652 	    FD_SET(fd, &exceptionalMask);
1653 	}
1654 
1655 	/*
1656 	 * Wait for the event or a timeout.
1657 	 */
1658 
1659 	numFound = select(fd + 1, &readableMask, &writableMask,
1660 		&exceptionalMask, timeoutPtr);
1661 	if (numFound == 1) {
1662 	    if (FD_ISSET(fd, &readableMask))   {
1663 		SET_BITS(result, TCL_READABLE);
1664 	    }
1665 	    if (FD_ISSET(fd, &writableMask))  {
1666 		SET_BITS(result, TCL_WRITABLE);
1667 	    }
1668 	    if (FD_ISSET(fd, &exceptionalMask)) {
1669 		SET_BITS(result, TCL_EXCEPTION);
1670 	    }
1671 	    result &= mask;
1672 	    if (result) {
1673 		break;
1674 	    }
1675 	}
1676 	if (timeout == 0) {
1677 	    break;
1678 	}
1679 	if (timeout < 0) {
1680 	    continue;
1681 	}
1682 
1683 	/*
1684 	 * The select returned early, so we need to recompute the timeout.
1685 	 */
1686 
1687 	Tcl_GetTime(&now);
1688 	if ((abortTime.sec < now.sec)
1689 		|| (abortTime.sec==now.sec && abortTime.usec<=now.usec)) {
1690 	    break;
1691 	}
1692     }
1693     return result;
1694 }
1695 
1696 /*
1697  *----------------------------------------------------------------------
1698  *
1699  * NotifierThreadProc --
1700  *
1701  *	This routine is the initial (and only) function executed by the
1702  *	special notifier thread. Its job is to wait for file descriptors to
1703  *	become readable or writable or to have an exception condition and then
1704  *	to notify other threads who are interested in this information by
1705  *	signalling a condition variable. Other threads can signal this
1706  *	notifier thread of a change in their interests by writing a single
1707  *	byte to a special pipe that the notifier thread is monitoring.
1708  *
1709  * Result:
1710  *	None. Once started, this routine never exits. It dies with the overall
1711  *	process.
1712  *
1713  * Side effects:
1714  *	The trigger pipe used to signal the notifier thread is created when
1715  *	the notifier thread first starts.
1716  *
1717  *----------------------------------------------------------------------
1718  */
1719 
1720 static void
NotifierThreadProc(ClientData clientData)1721 NotifierThreadProc(
1722     ClientData clientData)	/* Not used. */
1723 {
1724     ThreadSpecificData *tsdPtr;
1725     fd_set readableMask, writableMask, exceptionalMask;
1726     int i, numFdBits = 0, polling;
1727     struct timeval poll = {0., 0.}, *timePtr;
1728     char buf[2];
1729 
1730     /*
1731      * Look for file events and report them to interested threads.
1732      */
1733 
1734     while (1) {
1735 	FD_ZERO(&readableMask);
1736 	FD_ZERO(&writableMask);
1737 	FD_ZERO(&exceptionalMask);
1738 
1739 	/*
1740 	 * Compute the logical OR of the select masks from all the waiting
1741 	 * notifiers.
1742 	 */
1743 
1744 	timePtr = NULL;
1745 	LOCK_NOTIFIER;
1746 	for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
1747 	    LOCK_NOTIFIER_TSD;
1748 	    for (i = tsdPtr->numFdBits-1; i >= 0; --i) {
1749 		if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))) {
1750 		    FD_SET(i, &readableMask);
1751 		}
1752 		if (FD_ISSET(i, &(tsdPtr->checkMasks.writable))) {
1753 		    FD_SET(i, &writableMask);
1754 		}
1755 		if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) {
1756 		    FD_SET(i, &exceptionalMask);
1757 		}
1758 	    }
1759 	    if (tsdPtr->numFdBits > numFdBits) {
1760 		numFdBits = tsdPtr->numFdBits;
1761 	    }
1762 	    polling = tsdPtr->polling;
1763 	    UNLOCK_NOTIFIER_TSD;
1764 	    if ((tsdPtr->polled = polling)) {
1765 		timePtr = &poll;
1766 	    }
1767 	}
1768 	UNLOCK_NOTIFIER;
1769 
1770 	/*
1771 	 * Set up the select mask to include the receive pipe.
1772 	 */
1773 
1774 	if (receivePipe >= numFdBits) {
1775 	    numFdBits = receivePipe + 1;
1776 	}
1777 	FD_SET(receivePipe, &readableMask);
1778 
1779 	if (select(numFdBits, &readableMask, &writableMask, &exceptionalMask,
1780 		timePtr) == -1) {
1781 	    /*
1782 	     * Try again immediately on an error.
1783 	     */
1784 
1785 	    continue;
1786 	}
1787 
1788 	/*
1789 	 * Alert any threads that are waiting on a ready file descriptor.
1790 	 */
1791 
1792 	LOCK_NOTIFIER;
1793 	for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
1794 	    int found = 0;
1795 	    SelectMasks readyMasks, checkMasks;
1796 
1797 	    LOCK_NOTIFIER_TSD;
1798 	    FD_COPY(&(tsdPtr->checkMasks.readable), &checkMasks.readable);
1799 	    FD_COPY(&(tsdPtr->checkMasks.writable), &checkMasks.writable);
1800 	    FD_COPY(&(tsdPtr->checkMasks.exceptional), &checkMasks.exceptional);
1801 	    UNLOCK_NOTIFIER_TSD;
1802 	    found = tsdPtr->polled;
1803 	    FD_ZERO(&readyMasks.readable);
1804 	    FD_ZERO(&readyMasks.writable);
1805 	    FD_ZERO(&readyMasks.exceptional);
1806 
1807 	    for (i = tsdPtr->numFdBits-1; i >= 0; --i) {
1808 		if (FD_ISSET(i, &checkMasks.readable)
1809 			&& FD_ISSET(i, &readableMask)) {
1810 		    FD_SET(i, &readyMasks.readable);
1811 		    found = 1;
1812 		}
1813 		if (FD_ISSET(i, &checkMasks.writable)
1814 			&& FD_ISSET(i, &writableMask)) {
1815 		    FD_SET(i, &readyMasks.writable);
1816 		    found = 1;
1817 		}
1818 		if (FD_ISSET(i, &checkMasks.exceptional)
1819 			&& FD_ISSET(i, &exceptionalMask)) {
1820 		    FD_SET(i, &readyMasks.exceptional);
1821 		    found = 1;
1822 		}
1823 	    }
1824 
1825 	    if (found) {
1826 		/*
1827 		 * Remove the ThreadSpecificData structure of this thread from
1828 		 * the waiting list. This prevents us from spinning
1829 		 * continuously on select until the other threads runs and
1830 		 * services the file event.
1831 		 */
1832 
1833 		OnOffWaitingList(tsdPtr, 0, 0);
1834 
1835 		LOCK_NOTIFIER_TSD;
1836 		FD_COPY(&readyMasks.readable, &(tsdPtr->readyMasks.readable));
1837 		FD_COPY(&readyMasks.writable, &(tsdPtr->readyMasks.writable));
1838 		FD_COPY(&readyMasks.exceptional, &(tsdPtr->readyMasks.exceptional));
1839 		UNLOCK_NOTIFIER_TSD;
1840 		tsdPtr->polled = 0;
1841 		if (tsdPtr->runLoop) {
1842 		    CFRunLoopSourceSignal(tsdPtr->runLoopSource);
1843 		    CFRunLoopWakeUp(tsdPtr->runLoop);
1844 		}
1845 	    }
1846 	}
1847 	UNLOCK_NOTIFIER;
1848 
1849 	/*
1850 	 * Consume the next byte from the notifier pipe if the pipe was
1851 	 * readable. Note that there may be multiple bytes pending, but to
1852 	 * avoid a race condition we only read one at a time.
1853 	 */
1854 
1855 	if (FD_ISSET(receivePipe, &readableMask)) {
1856 	    i = read(receivePipe, buf, 1);
1857 
1858 	    if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) {
1859 		/*
1860 		 * Someone closed the write end of the pipe or sent us a Quit
1861 		 * message [Bug: 4139] and then closed the write end of the
1862 		 * pipe so we need to shut down the notifier thread.
1863 		 */
1864 
1865 		break;
1866 	    }
1867 	}
1868     }
1869     pthread_exit(0);
1870 }
1871 
1872 #ifdef HAVE_PTHREAD_ATFORK
1873 /*
1874  *----------------------------------------------------------------------
1875  *
1876  * AtForkPrepare --
1877  *
1878  *	Lock the notifier in preparation for a fork.
1879  *
1880  * Results:
1881  *	None.
1882  *
1883  * Side effects:
1884  *	None.
1885  *
1886  *----------------------------------------------------------------------
1887  */
1888 
1889 static void
AtForkPrepare(void)1890 AtForkPrepare(void)
1891 {
1892     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1893 
1894     LOCK_NOTIFIER_INIT;
1895     LOCK_NOTIFIER;
1896     LOCK_NOTIFIER_TSD;
1897 }
1898 
1899 /*
1900  *----------------------------------------------------------------------
1901  *
1902  * AtForkParent --
1903  *
1904  *	Unlock the notifier in the parent after a fork.
1905  *
1906  * Results:
1907  *	None.
1908  *
1909  * Side effects:
1910  *	None.
1911  *
1912  *----------------------------------------------------------------------
1913  */
1914 
1915 static void
AtForkParent(void)1916 AtForkParent(void)
1917 {
1918     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1919 
1920     UNLOCK_NOTIFIER_TSD;
1921     UNLOCK_NOTIFIER;
1922     UNLOCK_NOTIFIER_INIT;
1923 }
1924 
1925 /*
1926  *----------------------------------------------------------------------
1927  *
1928  * AtForkChild --
1929  *
1930  *	Unlock and reinstall the notifier in the child after a fork.
1931  *
1932  * Results:
1933  *	None.
1934  *
1935  * Side effects:
1936  *	None.
1937  *
1938  *----------------------------------------------------------------------
1939  */
1940 
1941 static void
AtForkChild(void)1942 AtForkChild(void)
1943 {
1944     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1945 
1946     UNLOCK_NOTIFIER_TSD;
1947     UNLOCK_NOTIFIER;
1948     UNLOCK_NOTIFIER_INIT;
1949     if (tsdPtr->runLoop) {
1950 	tsdPtr->runLoop = NULL;
1951 	if (!noCFafterFork) {
1952 	    CFRunLoopSourceInvalidate(tsdPtr->runLoopSource);
1953 	    CFRelease(tsdPtr->runLoopSource);
1954 	    if (tsdPtr->runLoopTimer) {
1955 		CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer);
1956 		CFRelease(tsdPtr->runLoopTimer);
1957 	    }
1958 	}
1959 	tsdPtr->runLoopSource = NULL;
1960 	tsdPtr->runLoopTimer = NULL;
1961     }
1962     if (notifierCount > 0) {
1963 	notifierCount = 1;
1964 	notifierThreadRunning = 0;
1965 
1966 	/*
1967 	 * Assume that the return value of Tcl_InitNotifier in the child will
1968 	 * be identical to the one stored as clientData in tclNotify.c's
1969 	 * ThreadSpecificData by the parent's TclInitNotifier, so discard the
1970 	 * return value here. This assumption may require the fork() to be
1971 	 * executed in the main thread of the parent, otherwise
1972 	 * Tcl_AlertNotifier may break in the child.
1973 	 */
1974 
1975 	if (!noCFafterFork) {
1976 	    Tcl_InitNotifier();
1977 	}
1978     }
1979 }
1980 #endif /* HAVE_PTHREAD_ATFORK */
1981 
1982 #else /* HAVE_COREFOUNDATION */
1983 
1984 void
TclMacOSXNotifierAddRunLoopMode(CONST void * runLoopMode)1985 TclMacOSXNotifierAddRunLoopMode(
1986     CONST void *runLoopMode)
1987 {
1988     Tcl_Panic("TclMacOSXNotifierAddRunLoopMode: "
1989 	    "Tcl not built with CoreFoundation support");
1990 }
1991 
1992 #endif /* HAVE_COREFOUNDATION */
1993 
1994 /*
1995  * Local Variables:
1996  * mode: c
1997  * c-basic-offset: 4
1998  * fill-column: 78
1999  * End:
2000  */
2001