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