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(¬ifierInitLock)
113 #define UNLOCK_NOTIFIER_INIT SpinLockUnlock(¬ifierInitLock)
114 #define LOCK_NOTIFIER SpinLockLock(¬ifierLock)
115 #define UNLOCK_NOTIFIER SpinLockUnlock(¬ifierLock)
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(¬ifierInitLock)
139 #undef LOCK_NOTIFIER
140 #define LOCK_NOTIFIER SpinLockLockDbg(¬ifierLock)
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(¬ifierThread, &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(¬ifierLock)) {
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