Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/macosx/tclMacOSXNotify.c @ 35

Last change on this file since 35 was 25, checked in by landauf, 17 years ago

added tcl to libs

File size: 36.7 KB
Line 
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, Apple Computer, Inc.
10 * Copyright (c) 2005-2008 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 2008/03/11 22:24:17 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
24extern TclStubs tclStubs;
25extern Tcl_NotifierProcs tclOriginalNotifier;
26
27/*
28 * This structure is used to keep track of the notifier info for a registered
29 * file.
30 */
31
32typedef struct FileHandler {
33    int fd;
34    int mask;                   /* Mask of desired events: TCL_READABLE,
35                                 * etc. */
36    int readyMask;              /* Mask of events that have been seen since
37                                 * the last time file handlers were invoked
38                                 * for this file. */
39    Tcl_FileProc *proc;         /* Function to call, in the style of
40                                 * Tcl_CreateFileHandler. */
41    ClientData clientData;      /* Argument to pass to proc. */
42    struct FileHandler *nextPtr;/* Next in list of all files we care about. */
43} FileHandler;
44
45/*
46 * The following structure is what is added to the Tcl event queue when file
47 * handlers are ready to fire.
48 */
49
50typedef struct FileHandlerEvent {
51    Tcl_Event header;           /* Information that is standard for all
52                                 * events. */
53    int fd;                     /* File descriptor that is ready. Used to find
54                                 * the FileHandler structure for the file
55                                 * (can't point directly to the FileHandler
56                                 * structure because it could go away while
57                                 * the event is queued). */
58} FileHandlerEvent;
59
60/*
61 * The following structure contains a set of select() masks to track readable,
62 * writable, and exceptional conditions.
63 */
64
65typedef struct SelectMasks {
66    fd_set readable;
67    fd_set writable;
68    fd_set exceptional;
69} SelectMasks;
70
71/*
72 * The following static structure contains the state information for the
73 * select based implementation of the Tcl notifier. One of these structures is
74 * created for each thread that is using the notifier.
75 */
76
77typedef struct ThreadSpecificData {
78    FileHandler *firstFileHandlerPtr;
79                                /* Pointer to head of file handler list. */
80    SelectMasks checkMasks;     /* This structure is used to build up the
81                                 * masks to be used in the next call to
82                                 * select. Bits are set in response to calls
83                                 * to Tcl_CreateFileHandler. */
84    SelectMasks readyMasks;     /* This array reflects the readable/writable
85                                 * conditions that were found to exist by the
86                                 * last call to select. */
87    int numFdBits;              /* Number of valid bits in checkMasks (one
88                                 * more than highest fd for which
89                                 * Tcl_WatchFile has been called). */
90    int onList;                 /* True if it is in this list */
91    unsigned int pollState;     /* pollState is used to implement a polling
92                                 * handshake between each thread and the
93                                 * notifier thread. Bits defined below. */
94    struct ThreadSpecificData *nextPtr, *prevPtr;
95                                /* All threads that are currently waiting on
96                                 * an event have their ThreadSpecificData
97                                 * structure on a doubly-linked listed formed
98                                 * from these pointers. You must hold the
99                                 * notifierLock before accessing these
100                                 * fields. */
101    CFRunLoopSourceRef runLoopSource;
102                                /* Any other thread alerts a notifier that an
103                                 * event is ready to be processed by signaling
104                                 * this CFRunLoopSource. */
105    CFRunLoopRef runLoop;       /* This thread's CFRunLoop, needs to be woken
106                                 * up whenever the runLoopSource is
107                                 * signaled. */
108    int eventReady;             /* True if an event is ready to be
109                                 * processed. */
110} ThreadSpecificData;
111
112static Tcl_ThreadDataKey dataKey;
113
114/*
115 * The following static indicates the number of threads that have initialized
116 * notifiers.
117 *
118 * You must hold the notifierInitLock before accessing this variable.
119 */
120
121static int notifierCount = 0;
122
123/*
124 * The following variable points to the head of a doubly-linked list of
125 * ThreadSpecificData structures for all threads that are currently waiting on
126 * an event.
127 *
128 * You must hold the notifierLock before accessing this list.
129 */
130
131static ThreadSpecificData *waitingListPtr = NULL;
132
133/*
134 * The notifier thread spends all its time in select() waiting for a file
135 * descriptor associated with one of the threads on the waitingListPtr list to
136 * do something interesting. But if the contents of the waitingListPtr list
137 * ever changes, we need to wake up and restart the select() system call. You
138 * can wake up the notifier thread by writing a single byte to the file
139 * descriptor defined below. This file descriptor is the input-end of a pipe
140 * and the notifier thread is listening for data on the output-end of the same
141 * pipe. Hence writing to this file descriptor will cause the select() system
142 * call to return and wake up the notifier thread.
143 *
144 * You must hold the notifierLock lock before writing to the pipe.
145 */
146
147static int triggerPipe = -1;
148static int receivePipe = -1; /* Output end of triggerPipe */
149
150/*
151 * We use the Darwin-native spinlock API rather than pthread mutexes for
152 * notifier locking: this radically simplifies the implementation and lowers
153 * overhead. Note that these are not pure spinlocks, they employ various
154 * strategies to back off and relinquish the processor, making them immune to
155 * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin
156 * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s).
157 */
158
159#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK)
160/*
161 * Use OSSpinLock API where available (Tiger or later).
162 */
163
164#include <libkern/OSAtomic.h>
165
166#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040
167/*
168 * Support for weakly importing spinlock API.
169 */
170#define WEAK_IMPORT_SPINLOCKLOCK
171#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
172#define VOLATILE volatile
173#else
174#define VOLATILE
175#endif
176#ifndef bool
177#define bool int
178#endif
179extern void OSSpinLockLock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
180extern void OSSpinLockUnlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
181extern bool OSSpinLockTry(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
182extern void _spin_lock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
183extern void _spin_unlock(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
184extern bool _spin_lock_try(VOLATILE OSSpinLock *lock) WEAK_IMPORT_ATTRIBUTE;
185static void (* lockLock)(VOLATILE OSSpinLock *lock) = NULL;
186static void (* lockUnlock)(VOLATILE OSSpinLock *lock) = NULL;
187static bool (* lockTry)(VOLATILE OSSpinLock *lock) = NULL;
188#undef VOLATILE
189static pthread_once_t spinLockLockInitControl = PTHREAD_ONCE_INIT;
190static void SpinLockLockInit(void) {
191    lockLock   = OSSpinLockLock   != NULL ? OSSpinLockLock   : _spin_lock;
192    lockUnlock = OSSpinLockUnlock != NULL ? OSSpinLockUnlock : _spin_unlock;
193    lockTry    = OSSpinLockTry    != NULL ? OSSpinLockTry    : _spin_lock_try;
194    if (lockLock == NULL || lockUnlock == NULL) {
195        Tcl_Panic("SpinLockLockInit: no spinlock API available");
196    }
197}
198#define SpinLockLock(p)         lockLock(p)
199#define SpinLockUnlock(p)       lockUnlock(p)
200#define SpinLockTry(p)          lockTry(p)
201#else
202#define SpinLockLock(p)         OSSpinLockLock(p)
203#define SpinLockUnlock(p)       OSSpinLockUnlock(p)
204#define SpinLockTry(p)          OSSpinLockTry(p)
205#endif /* HAVE_WEAK_IMPORT */
206#define SPINLOCK_INIT           OS_SPINLOCK_INIT
207
208#else
209/*
210 * Otherwise, use commpage spinlock SPI directly.
211 */
212
213typedef uint32_t OSSpinLock;
214extern void _spin_lock(OSSpinLock *lock);
215extern void _spin_unlock(OSSpinLock *lock);
216extern int  _spin_lock_try(OSSpinLock *lock);
217#define SpinLockLock(p)         _spin_lock(p)
218#define SpinLockUnlock(p)       _spin_unlock(p)
219#define SpinLockTry(p)          _spin_lock_try(p)
220#define SPINLOCK_INIT           0
221
222#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */
223
224/*
225 * These spinlocks lock access to the global notifier state.
226 */
227
228static OSSpinLock notifierInitLock = SPINLOCK_INIT;
229static OSSpinLock notifierLock     = SPINLOCK_INIT;
230
231/*
232 * Macros abstracting notifier locking/unlocking
233 */
234
235#define LOCK_NOTIFIER_INIT      SpinLockLock(&notifierInitLock)
236#define UNLOCK_NOTIFIER_INIT    SpinLockUnlock(&notifierInitLock)
237#define LOCK_NOTIFIER           SpinLockLock(&notifierLock)
238#define UNLOCK_NOTIFIER         SpinLockUnlock(&notifierLock)
239
240#ifdef TCL_MAC_DEBUG_NOTIFIER
241/*
242 * Debug version of SpinLockLock that logs the time spent waiting for the lock
243 */
244
245#define SpinLockLockDbg(p)      if(!SpinLockTry(p)) { \
246                                    Tcl_WideInt s = TclpGetWideClicks(), e; \
247                                    SpinLockLock(p); e = TclpGetWideClicks(); \
248                                    fprintf(notifierLog, "tclMacOSXNotify.c:" \
249                                    "%4d: thread %10p waited on %s for " \
250                                    "%8llu ns\n", __LINE__, pthread_self(), \
251                                    #p, TclpWideClicksToNanoseconds(e-s)); \
252                                    fflush(notifierLog); \
253                                }
254#undef LOCK_NOTIFIER_INIT
255#define LOCK_NOTIFIER_INIT      SpinLockLockDbg(&notifierInitLock)
256#undef LOCK_NOTIFIER
257#define LOCK_NOTIFIER           SpinLockLockDbg(&notifierLock)
258static FILE *notifierLog = stderr;
259#ifndef NOTIFIER_LOG
260#define NOTIFIER_LOG "/tmp/tclMacOSXNotify.log"
261#endif
262#define OPEN_NOTIFIER_LOG       if (notifierLog == stderr) { \
263                                    notifierLog = fopen(NOTIFIER_LOG, "a"); \
264                                }
265#define CLOSE_NOTIFIER_LOG      if (notifierLog != stderr) { \
266                                    fclose(notifierLog); \
267                                    notifierLog = stderr; \
268                                }
269#endif /* TCL_MAC_DEBUG_NOTIFIER */
270
271/*
272 * The pollState bits
273 *      POLL_WANT is set by each thread before it waits on its condition
274 *              variable. It is checked by the notifier before it does select.
275 *      POLL_DONE is set by the notifier if it goes into select after seeing
276 *              POLL_WANT. The idea is to ensure it tries a select with the
277 *              same bits the initial thread had set.
278 */
279
280#define POLL_WANT       0x1
281#define POLL_DONE       0x2
282
283/*
284 * This is the thread ID of the notifier thread that does select.
285 */
286
287static pthread_t notifierThread;
288
289/*
290 * Custom run loop mode containing only the run loop source for the
291 * notifier thread.
292 */
293
294#ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE
295#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode"
296#endif
297#ifdef __CONSTANT_CFSTRINGS__
298#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE)
299#else
300static CFStringRef tclEventsOnlyRunLoopMode = NULL;
301#endif
302
303/*
304 * Static routines defined in this file.
305 */
306
307static void     NotifierThreadProc(ClientData clientData)
308        __attribute__ ((__noreturn__));
309static int      FileHandlerEventProc(Tcl_Event *evPtr, int flags);
310
311#ifdef HAVE_PTHREAD_ATFORK
312static int      atForkInit = 0;
313static void     AtForkPrepare(void);
314static void     AtForkParent(void);
315static void     AtForkChild(void);
316#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040
317/* Support for weakly importing pthread_atfork. */
318#define WEAK_IMPORT_PTHREAD_ATFORK
319extern int pthread_atfork(void (*prepare)(void), void (*parent)(void),
320                          void (*child)(void)) WEAK_IMPORT_ATTRIBUTE;
321#endif /* HAVE_WEAK_IMPORT */
322/*
323 * On Darwin 9 and later, it is not possible to call CoreFoundation after
324 * a fork.
325 */
326#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \
327        MAC_OS_X_VERSION_MIN_REQUIRED < 1050
328MODULE_SCOPE long tclMacOSXDarwinRelease;
329#define noCFafterFork (tclMacOSXDarwinRelease >= 9)
330#else /* MAC_OS_X_VERSION_MIN_REQUIRED */
331#define noCFafterFork 1
332#endif /* MAC_OS_X_VERSION_MIN_REQUIRED */
333#endif /* HAVE_PTHREAD_ATFORK */
334
335/*
336 *----------------------------------------------------------------------
337 *
338 * Tcl_InitNotifier --
339 *
340 *      Initializes the platform specific notifier state.
341 *
342 * Results:
343 *      Returns a handle to the notifier state for this thread.
344 *
345 * Side effects:
346 *      None.
347 *
348 *----------------------------------------------------------------------
349 */
350
351ClientData
352Tcl_InitNotifier(void)
353{
354    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
355
356    tsdPtr->eventReady = 0;
357
358#ifdef WEAK_IMPORT_SPINLOCKLOCK
359    /*
360     * Initialize support for weakly imported spinlock API.
361     */
362    if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) {
363        Tcl_Panic("Tcl_InitNotifier: pthread_once failed");
364    }
365#endif
366
367#ifndef __CONSTANT_CFSTRINGS__
368    if (!tclEventsOnlyRunLoopMode) {
369        tclEventsOnlyRunLoopMode = CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE);
370    }
371#endif
372
373    /*
374     * Initialize CFRunLoopSource and add it to CFRunLoop of this thread.
375     */
376
377    if (!tsdPtr->runLoop) {
378        CFRunLoopRef runLoop = CFRunLoopGetCurrent();
379        CFRunLoopSourceRef runLoopSource;
380        CFRunLoopSourceContext runLoopSourceContext;
381
382        bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext));
383        runLoopSourceContext.info = tsdPtr;
384        runLoopSource = CFRunLoopSourceCreate(NULL, 0, &runLoopSourceContext);
385        if (!runLoopSource) {
386            Tcl_Panic("Tcl_InitNotifier: could not create CFRunLoopSource");
387        }
388        CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopCommonModes);
389        CFRunLoopAddSource(runLoop, runLoopSource, tclEventsOnlyRunLoopMode);
390        tsdPtr->runLoopSource = runLoopSource;
391        tsdPtr->runLoop = runLoop;
392    }
393
394    LOCK_NOTIFIER_INIT;
395#ifdef HAVE_PTHREAD_ATFORK
396    /*
397     * Install pthread_atfork handlers to reinitialize the notifier in the
398     * child of a fork.
399     */
400
401    if (
402#ifdef WEAK_IMPORT_PTHREAD_ATFORK
403            pthread_atfork != NULL &&
404#endif
405            !atForkInit) {
406        int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild);
407        if (result) {
408            Tcl_Panic("Tcl_InitNotifier: pthread_atfork failed");
409        }
410        atForkInit = 1;
411    }
412#endif
413    if (notifierCount == 0) {
414        int fds[2], status;
415
416        /*
417         * Initialize trigger pipe.
418         */
419
420        if (pipe(fds) != 0) {
421            Tcl_Panic("Tcl_InitNotifier: could not create trigger pipe");
422        }
423
424        status = fcntl(fds[0], F_GETFL);
425        status |= O_NONBLOCK;
426        if (fcntl(fds[0], F_SETFL, status) < 0) {
427            Tcl_Panic("Tcl_InitNotifier: could not make receive pipe non blocking");
428        }
429        status = fcntl(fds[1], F_GETFL);
430        status |= O_NONBLOCK;
431        if (fcntl(fds[1], F_SETFL, status) < 0) {
432            Tcl_Panic("Tcl_InitNotifier: could not make trigger pipe non blocking");
433        }
434
435        receivePipe = fds[0];
436        triggerPipe = fds[1];
437
438        /*
439         * Create notifier thread lazily in Tcl_WaitForEvent() to avoid
440         * interfering with fork() followed immediately by execve()
441         * (cannot execve() when more than one thread is present).
442         */
443
444        notifierThread = 0;
445#ifdef TCL_MAC_DEBUG_NOTIFIER
446        OPEN_NOTIFIER_LOG;
447#endif
448    }
449    notifierCount++;
450    UNLOCK_NOTIFIER_INIT;
451
452    return (ClientData) tsdPtr;
453}
454
455/*
456 *----------------------------------------------------------------------
457 *
458 * Tcl_FinalizeNotifier --
459 *
460 *      This function is called to cleanup the notifier state before a thread
461 *      is terminated.
462 *
463 * Results:
464 *      None.
465 *
466 * Side effects:
467 *      May terminate the background notifier thread if this is the last
468 *      notifier instance.
469 *
470 *----------------------------------------------------------------------
471 */
472
473void
474Tcl_FinalizeNotifier(
475    ClientData clientData)              /* Not used. */
476{
477    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
478
479    LOCK_NOTIFIER_INIT;
480    notifierCount--;
481
482    /*
483     * If this is the last thread to use the notifier, close the notifier pipe
484     * and wait for the background thread to terminate.
485     */
486
487    if (notifierCount == 0) {
488        int result;
489
490        if (triggerPipe < 0) {
491            Tcl_Panic("Tcl_FinalizeNotifier: notifier pipe not initialized");
492        }
493
494        /*
495         * Send "q" message to the notifier thread so that it will terminate.
496         * The notifier will return from its call to select() and notice that
497         * a "q" message has arrived, it will then close its side of the pipe
498         * and terminate its thread. Note the we can not just close the pipe
499         * and check for EOF in the notifier thread because if a background
500         * child process was created with exec, select() would not register
501         * the EOF on the pipe until the child processes had terminated. [Bug:
502         * 4139] [Bug: 1222872]
503         */
504
505        write(triggerPipe, "q", 1);
506        close(triggerPipe);
507
508        if (notifierThread) {
509            result = pthread_join(notifierThread, NULL);
510            if (result) {
511                Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier thread");
512            }
513            notifierThread = 0;
514        }
515
516        close(receivePipe);
517        triggerPipe = -1;
518#ifdef TCL_MAC_DEBUG_NOTIFIER
519        CLOSE_NOTIFIER_LOG;
520#endif
521    }
522    UNLOCK_NOTIFIER_INIT;
523
524    LOCK_NOTIFIER;              /* for concurrency with Tcl_AlertNotifier */
525    if (tsdPtr->runLoop) {
526        tsdPtr->runLoop = NULL;
527
528        /*
529         * Remove runLoopSource from all CFRunLoops and release it.
530         */
531
532        CFRunLoopSourceInvalidate(tsdPtr->runLoopSource);
533        CFRelease(tsdPtr->runLoopSource);
534        tsdPtr->runLoopSource = NULL;
535    }
536    UNLOCK_NOTIFIER;
537}
538
539/*
540 *----------------------------------------------------------------------
541 *
542 * Tcl_AlertNotifier --
543 *
544 *      Wake up the specified notifier from any thread. This routine is called
545 *      by the platform independent notifier code whenever the Tcl_ThreadAlert
546 *      routine is called. This routine is guaranteed not to be called on a
547 *      given notifier after Tcl_FinalizeNotifier is called for that notifier.
548 *
549 * Results:
550 *      None.
551 *
552 * Side effects:
553 *      Signals the notifier condition variable for the specified notifier.
554 *
555 *----------------------------------------------------------------------
556 */
557
558void
559Tcl_AlertNotifier(
560    ClientData clientData)
561{
562    ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
563
564    LOCK_NOTIFIER;
565    if (tsdPtr->runLoop) {
566        tsdPtr->eventReady = 1;
567        CFRunLoopSourceSignal(tsdPtr->runLoopSource);
568        CFRunLoopWakeUp(tsdPtr->runLoop);
569    }
570    UNLOCK_NOTIFIER;
571}
572
573/*
574 *----------------------------------------------------------------------
575 *
576 * Tcl_SetTimer --
577 *
578 *      This function sets the current notifier timer value. This interface is
579 *      not implemented in this notifier because we are always running inside
580 *      of Tcl_DoOneEvent.
581 *
582 * Results:
583 *      None.
584 *
585 * Side effects:
586 *      None.
587 *
588 *----------------------------------------------------------------------
589 */
590
591void
592Tcl_SetTimer(
593    Tcl_Time *timePtr)          /* Timeout value, may be NULL. */
594{
595    /*
596     * The interval timer doesn't do anything in this implementation, because
597     * the only event loop is via Tcl_DoOneEvent, which passes timeout values
598     * to Tcl_WaitForEvent.
599     */
600
601    if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) {
602        tclStubs.tcl_SetTimer(timePtr);
603    }
604}
605
606/*
607 *----------------------------------------------------------------------
608 *
609 * Tcl_ServiceModeHook --
610 *
611 *      This function is invoked whenever the service mode changes.
612 *
613 * Results:
614 *      None.
615 *
616 * Side effects:
617 *      None.
618 *
619 *----------------------------------------------------------------------
620 */
621
622void
623Tcl_ServiceModeHook(
624    int mode)                   /* Either TCL_SERVICE_ALL, or
625                                 * TCL_SERVICE_NONE. */
626{
627}
628
629/*
630 *----------------------------------------------------------------------
631 *
632 * Tcl_CreateFileHandler --
633 *
634 *      This function registers a file handler with the select notifier.
635 *
636 * Results:
637 *      None.
638 *
639 * Side effects:
640 *      Creates a new file handler structure.
641 *
642 *----------------------------------------------------------------------
643 */
644
645void
646Tcl_CreateFileHandler(
647    int fd,                     /* Handle of stream to watch. */
648    int mask,                   /* OR'ed combination of TCL_READABLE,
649                                 * TCL_WRITABLE, and TCL_EXCEPTION: indicates
650                                 * conditions under which proc should be
651                                 * called. */
652    Tcl_FileProc *proc,         /* Function to call for each selected
653                                 * event. */
654    ClientData clientData)      /* Arbitrary data to pass to proc. */
655{
656    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
657    FileHandler *filePtr;
658
659    if (tclStubs.tcl_CreateFileHandler !=
660            tclOriginalNotifier.createFileHandlerProc) {
661        tclStubs.tcl_CreateFileHandler(fd, mask, proc, clientData);
662        return;
663    }
664
665    for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
666            filePtr = filePtr->nextPtr) {
667        if (filePtr->fd == fd) {
668            break;
669        }
670    }
671    if (filePtr == NULL) {
672        filePtr = (FileHandler*) ckalloc(sizeof(FileHandler));
673        filePtr->fd = fd;
674        filePtr->readyMask = 0;
675        filePtr->nextPtr = tsdPtr->firstFileHandlerPtr;
676        tsdPtr->firstFileHandlerPtr = filePtr;
677    }
678    filePtr->proc = proc;
679    filePtr->clientData = clientData;
680    filePtr->mask = mask;
681
682    /*
683     * Update the check masks for this file.
684     */
685
686    if (mask & TCL_READABLE) {
687        FD_SET(fd, &(tsdPtr->checkMasks.readable));
688    } else {
689        FD_CLR(fd, &(tsdPtr->checkMasks.readable));
690    }
691    if (mask & TCL_WRITABLE) {
692        FD_SET(fd, &(tsdPtr->checkMasks.writable));
693    } else {
694        FD_CLR(fd, &(tsdPtr->checkMasks.writable));
695    }
696    if (mask & TCL_EXCEPTION) {
697        FD_SET(fd, &(tsdPtr->checkMasks.exceptional));
698    } else {
699        FD_CLR(fd, &(tsdPtr->checkMasks.exceptional));
700    }
701    if (tsdPtr->numFdBits <= fd) {
702        tsdPtr->numFdBits = fd+1;
703    }
704}
705
706/*
707 *----------------------------------------------------------------------
708 *
709 * Tcl_DeleteFileHandler --
710 *
711 *      Cancel a previously-arranged callback arrangement for a file.
712 *
713 * Results:
714 *      None.
715 *
716 * Side effects:
717 *      If a callback was previously registered on file, remove it.
718 *
719 *----------------------------------------------------------------------
720 */
721
722void
723Tcl_DeleteFileHandler(
724    int fd)                     /* Stream id for which to remove callback
725                                 * function. */
726{
727    FileHandler *filePtr, *prevPtr;
728    int i;
729    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
730
731    if (tclStubs.tcl_DeleteFileHandler !=
732            tclOriginalNotifier.deleteFileHandlerProc) {
733        tclStubs.tcl_DeleteFileHandler(fd);
734        return;
735    }
736
737    /*
738     * Find the entry for the given file (and return if there isn't one).
739     */
740
741    for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ;
742         prevPtr = filePtr, filePtr = filePtr->nextPtr) {
743        if (filePtr == NULL) {
744            return;
745        }
746        if (filePtr->fd == fd) {
747            break;
748        }
749    }
750
751    /*
752     * Update the check masks for this file.
753     */
754
755    if (filePtr->mask & TCL_READABLE) {
756        FD_CLR(fd, &(tsdPtr->checkMasks.readable));
757    }
758    if (filePtr->mask & TCL_WRITABLE) {
759        FD_CLR(fd, &(tsdPtr->checkMasks.writable));
760    }
761    if (filePtr->mask & TCL_EXCEPTION) {
762        FD_CLR(fd, &(tsdPtr->checkMasks.exceptional));
763    }
764
765    /*
766     * Find current max fd.
767     */
768
769    if (fd+1 == tsdPtr->numFdBits) {
770        tsdPtr->numFdBits = 0;
771        for (i = fd-1; i >= 0; i--) {
772            if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))
773                    || FD_ISSET(i, &(tsdPtr->checkMasks.writable))
774                    || FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) {
775                tsdPtr->numFdBits = i+1;
776                break;
777            }
778        }
779    }
780
781    /*
782     * Clean up information in the callback record.
783     */
784
785    if (prevPtr == NULL) {
786        tsdPtr->firstFileHandlerPtr = filePtr->nextPtr;
787    } else {
788        prevPtr->nextPtr = filePtr->nextPtr;
789    }
790    ckfree((char *) filePtr);
791}
792
793/*
794 *----------------------------------------------------------------------
795 *
796 * FileHandlerEventProc --
797 *
798 *      This function is called by Tcl_ServiceEvent when a file event reaches
799 *      the front of the event queue. This function is responsible for
800 *      actually handling the event by invoking the callback for the file
801 *      handler.
802 *
803 * Results:
804 *      Returns 1 if the event was handled, meaning it should be removed from
805 *      the queue. Returns 0 if the event was not handled, meaning it should
806 *      stay on the queue. The only time the event isn't handled is if the
807 *      TCL_FILE_EVENTS flag bit isn't set.
808 *
809 * Side effects:
810 *      Whatever the file handler's callback function does.
811 *
812 *----------------------------------------------------------------------
813 */
814
815static int
816FileHandlerEventProc(
817    Tcl_Event *evPtr,           /* Event to service. */
818    int flags)                  /* Flags that indicate what events to handle,
819                                 * such as TCL_FILE_EVENTS. */
820{
821    int mask;
822    FileHandler *filePtr;
823    FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr;
824    ThreadSpecificData *tsdPtr;
825
826    if (!(flags & TCL_FILE_EVENTS)) {
827        return 0;
828    }
829
830    /*
831     * Search through the file handlers to find the one whose handle matches
832     * the event. We do this rather than keeping a pointer to the file handler
833     * directly in the event, so that the handler can be deleted while the
834     * event is queued without leaving a dangling pointer.
835     */
836
837    tsdPtr = TCL_TSD_INIT(&dataKey);
838    for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
839            filePtr = filePtr->nextPtr) {
840        if (filePtr->fd != fileEvPtr->fd) {
841            continue;
842        }
843
844        /*
845         * The code is tricky for two reasons:
846         * 1. The file handler's desired events could have changed since the
847         *    time when the event was queued, so AND the ready mask with the
848         *    desired mask.
849         * 2. The file could have been closed and re-opened since the time
850         *    when the event was queued. This is why the ready mask is stored
851         *    in the file handler rather than the queued event: it will be
852         *    zeroed when a new file handler is created for the newly opened
853         *    file.
854         */
855
856        mask = filePtr->readyMask & filePtr->mask;
857        filePtr->readyMask = 0;
858        if (mask != 0) {
859            (*filePtr->proc)(filePtr->clientData, mask);
860        }
861        break;
862    }
863    return 1;
864}
865
866/*
867 *----------------------------------------------------------------------
868 *
869 * Tcl_WaitForEvent --
870 *
871 *      This function is called by Tcl_DoOneEvent to wait for new events on
872 *      the message queue. If the block time is 0, then Tcl_WaitForEvent just
873 *      polls without blocking.
874 *
875 * Results:
876 *      Returns -1 if the select would block forever, otherwise returns 0.
877 *
878 * Side effects:
879 *      Queues file events that are detected by the select.
880 *
881 *----------------------------------------------------------------------
882 */
883
884int
885Tcl_WaitForEvent(
886    Tcl_Time *timePtr)          /* Maximum block time, or NULL. */
887{
888    FileHandler *filePtr;
889    FileHandlerEvent *fileEvPtr;
890    int mask;
891    Tcl_Time myTime;
892    int waitForFiles;
893    Tcl_Time *myTimePtr;
894    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
895
896    if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) {
897        return tclStubs.tcl_WaitForEvent(timePtr);
898    }
899
900    if (timePtr != NULL) {
901        /*
902         * TIP #233 (Virtualized Time). Is virtual time in effect? And do we
903         * actually have something to scale? If yes to both then we call the
904         * handler to do this scaling.
905         */
906
907        myTime.sec  = timePtr->sec;
908        myTime.usec = timePtr->usec;
909
910        if (myTime.sec != 0 || myTime.usec != 0) {
911            (*tclScaleTimeProcPtr) (&myTime, tclTimeClientData);
912        }
913
914        myTimePtr = &myTime;
915    } else {
916        myTimePtr = NULL;
917    }
918
919    /*
920     * Start notifier thread if necessary.
921     */
922
923    LOCK_NOTIFIER_INIT;
924    if (!notifierCount) {
925        Tcl_Panic("Tcl_WaitForEvent: notifier not initialized");
926    }
927    if (!notifierThread) {
928        int result;
929        pthread_attr_t attr;
930
931        pthread_attr_init(&attr);
932        pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
933        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
934        pthread_attr_setstacksize(&attr, 60 * 1024);
935        result = pthread_create(&notifierThread, &attr,
936                (void * (*)(void *))NotifierThreadProc, NULL);
937        pthread_attr_destroy(&attr);
938        if (result || !notifierThread) {
939            Tcl_Panic("Tcl_WaitForEvent: unable to start notifier thread");
940        }
941    }
942    UNLOCK_NOTIFIER_INIT;
943
944    /*
945     * Place this thread on the list of interested threads, signal the
946     * notifier thread, and wait for a response or a timeout.
947     */
948
949    LOCK_NOTIFIER;
950    if (!tsdPtr->runLoop) {
951        Tcl_Panic("Tcl_WaitForEvent: CFRunLoop not initialized");
952    }
953    waitForFiles = (tsdPtr->numFdBits > 0);
954    if (myTimePtr != NULL && myTimePtr->sec == 0 && myTimePtr->usec == 0) {
955        /*
956         * Cannot emulate a polling select with a polling condition variable.
957         * Instead, pretend to wait for files and tell the notifier thread
958         * what we are doing. The notifier thread makes sure it goes through
959         * select with its select mask in the same state as ours currently is.
960         * We block until that happens.
961         */
962
963        waitForFiles = 1;
964        tsdPtr->pollState = POLL_WANT;
965        myTimePtr = NULL;
966    } else {
967        tsdPtr->pollState = 0;
968    }
969
970    if (waitForFiles) {
971        /*
972         * Add the ThreadSpecificData structure of this thread to the list of
973         * ThreadSpecificData structures of all threads that are waiting on
974         * file events.
975         */
976
977        tsdPtr->nextPtr = waitingListPtr;
978        if (waitingListPtr) {
979            waitingListPtr->prevPtr = tsdPtr;
980        }
981        tsdPtr->prevPtr = 0;
982        waitingListPtr = tsdPtr;
983        tsdPtr->onList = 1;
984
985        write(triggerPipe, "", 1);
986    }
987
988    FD_ZERO(&(tsdPtr->readyMasks.readable));
989    FD_ZERO(&(tsdPtr->readyMasks.writable));
990    FD_ZERO(&(tsdPtr->readyMasks.exceptional));
991
992    if (!tsdPtr->eventReady) {
993        CFTimeInterval waitTime;
994        CFStringRef runLoopMode;
995
996        if (myTimePtr == NULL) {
997            waitTime = 1.0e10; /* Wait forever, as per CFRunLoop.c */
998        } else {
999            waitTime = myTimePtr->sec + 1.0e-6 * myTimePtr->usec;
1000        }
1001        /*
1002         * If the run loop is already running (e.g. if Tcl_WaitForEvent was
1003         * called recursively), re-run it in a custom run loop mode containing
1004         * only the source for the notifier thread, otherwise wakeups from other
1005         * sources added to the common run loop modes might get lost.
1006         */
1007        if ((runLoopMode = CFRunLoopCopyCurrentMode(tsdPtr->runLoop))) {
1008            CFRelease(runLoopMode);
1009            runLoopMode = tclEventsOnlyRunLoopMode;
1010        } else {
1011            runLoopMode = kCFRunLoopDefaultMode;
1012        }
1013        UNLOCK_NOTIFIER;
1014        CFRunLoopRunInMode(runLoopMode, waitTime, TRUE);
1015        LOCK_NOTIFIER;
1016    }
1017    tsdPtr->eventReady = 0;
1018
1019    if (waitForFiles && tsdPtr->onList) {
1020        /*
1021         * Remove the ThreadSpecificData structure of this thread from the
1022         * waiting list. Alert the notifier thread to recompute its select
1023         * masks - skipping this caused a hang when trying to close a pipe
1024         * which the notifier thread was still doing a select on.
1025         */
1026
1027        if (tsdPtr->prevPtr) {
1028            tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr;
1029        } else {
1030            waitingListPtr = tsdPtr->nextPtr;
1031        }
1032        if (tsdPtr->nextPtr) {
1033            tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr;
1034        }
1035        tsdPtr->nextPtr = tsdPtr->prevPtr = NULL;
1036        tsdPtr->onList = 0;
1037        write(triggerPipe, "", 1);
1038    }
1039
1040    /*
1041     * Queue all detected file events before returning.
1042     */
1043
1044    for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL);
1045            filePtr = filePtr->nextPtr) {
1046
1047        mask = 0;
1048        if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.readable))) {
1049            mask |= TCL_READABLE;
1050        }
1051        if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.writable))) {
1052            mask |= TCL_WRITABLE;
1053        }
1054        if (FD_ISSET(filePtr->fd, &(tsdPtr->readyMasks.exceptional))) {
1055            mask |= TCL_EXCEPTION;
1056        }
1057
1058        if (!mask) {
1059            continue;
1060        }
1061
1062        /*
1063         * Don't bother to queue an event if the mask was previously non-zero
1064         * since an event must still be on the queue.
1065         */
1066
1067        if (filePtr->readyMask == 0) {
1068            fileEvPtr = (FileHandlerEvent *) ckalloc(sizeof(FileHandlerEvent));
1069            fileEvPtr->header.proc = FileHandlerEventProc;
1070            fileEvPtr->fd = filePtr->fd;
1071            Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
1072        }
1073        filePtr->readyMask = mask;
1074    }
1075    UNLOCK_NOTIFIER;
1076    return 0;
1077}
1078
1079/*
1080 *----------------------------------------------------------------------
1081 *
1082 * NotifierThreadProc --
1083 *
1084 *      This routine is the initial (and only) function executed by the
1085 *      special notifier thread. Its job is to wait for file descriptors to
1086 *      become readable or writable or to have an exception condition and then
1087 *      to notify other threads who are interested in this information by
1088 *      signalling a condition variable. Other threads can signal this
1089 *      notifier thread of a change in their interests by writing a single
1090 *      byte to a special pipe that the notifier thread is monitoring.
1091 *
1092 * Result:
1093 *      None. Once started, this routine never exits. It dies with the overall
1094 *      process.
1095 *
1096 * Side effects:
1097 *      The trigger pipe used to signal the notifier thread is created when
1098 *      the notifier thread first starts.
1099 *
1100 *----------------------------------------------------------------------
1101 */
1102
1103static void
1104NotifierThreadProc(
1105    ClientData clientData)      /* Not used. */
1106{
1107    ThreadSpecificData *tsdPtr;
1108    fd_set readableMask;
1109    fd_set writableMask;
1110    fd_set exceptionalMask;
1111    int i, numFdBits = 0;
1112    long found;
1113    struct timeval poll = {0., 0.}, *timePtr;
1114    char buf[2];
1115
1116    /*
1117     * Look for file events and report them to interested threads.
1118     */
1119
1120    while (1) {
1121        FD_ZERO(&readableMask);
1122        FD_ZERO(&writableMask);
1123        FD_ZERO(&exceptionalMask);
1124
1125        /*
1126         * Compute the logical OR of the select masks from all the waiting
1127         * notifiers.
1128         */
1129
1130        LOCK_NOTIFIER;
1131        timePtr = NULL;
1132        for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
1133            for (i = tsdPtr->numFdBits-1; i >= 0; --i) {
1134                if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))) {
1135                    FD_SET(i, &readableMask);
1136                }
1137                if (FD_ISSET(i, &(tsdPtr->checkMasks.writable))) {
1138                    FD_SET(i, &writableMask);
1139                }
1140                if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))) {
1141                    FD_SET(i, &exceptionalMask);
1142                }
1143            }
1144            if (tsdPtr->numFdBits > numFdBits) {
1145                numFdBits = tsdPtr->numFdBits;
1146            }
1147            if (tsdPtr->pollState & POLL_WANT) {
1148                /*
1149                 * Here we make sure we go through select() with the same mask
1150                 * bits that were present when the thread tried to poll.
1151                 */
1152
1153                tsdPtr->pollState |= POLL_DONE;
1154                timePtr = &poll;
1155            }
1156        }
1157        UNLOCK_NOTIFIER;
1158
1159        /*
1160         * Set up the select mask to include the receive pipe.
1161         */
1162
1163        if (receivePipe >= numFdBits) {
1164            numFdBits = receivePipe + 1;
1165        }
1166        FD_SET(receivePipe, &readableMask);
1167
1168        if (select(numFdBits, &readableMask, &writableMask, &exceptionalMask,
1169                timePtr) == -1) {
1170            /*
1171             * Try again immediately on an error.
1172             */
1173
1174            continue;
1175        }
1176
1177        /*
1178         * Alert any threads that are waiting on a ready file descriptor.
1179         */
1180
1181        LOCK_NOTIFIER;
1182        for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
1183            found = 0;
1184
1185            for (i = tsdPtr->numFdBits-1; i >= 0; --i) {
1186                if (FD_ISSET(i, &(tsdPtr->checkMasks.readable))
1187                        && FD_ISSET(i, &readableMask)) {
1188                    FD_SET(i, &(tsdPtr->readyMasks.readable));
1189                    found = 1;
1190                }
1191                if (FD_ISSET(i, &(tsdPtr->checkMasks.writable))
1192                        && FD_ISSET(i, &writableMask)) {
1193                    FD_SET(i, &(tsdPtr->readyMasks.writable));
1194                    found = 1;
1195                }
1196                if (FD_ISSET(i, &(tsdPtr->checkMasks.exceptional))
1197                        && FD_ISSET(i, &exceptionalMask)) {
1198                    FD_SET(i, &(tsdPtr->readyMasks.exceptional));
1199                    found = 1;
1200                }
1201            }
1202
1203            if (found || (tsdPtr->pollState & POLL_DONE)) {
1204                tsdPtr->eventReady = 1;
1205                if (tsdPtr->onList) {
1206                    /*
1207                     * Remove the ThreadSpecificData structure of this thread
1208                     * from the waiting list. This prevents us from
1209                     * continuously spining on select until the other threads
1210                     * runs and services the file event.
1211                     */
1212
1213                    if (tsdPtr->prevPtr) {
1214                        tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr;
1215                    } else {
1216                        waitingListPtr = tsdPtr->nextPtr;
1217                    }
1218                    if (tsdPtr->nextPtr) {
1219                        tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr;
1220                    }
1221                    tsdPtr->nextPtr = tsdPtr->prevPtr = NULL;
1222                    tsdPtr->onList = 0;
1223                    tsdPtr->pollState = 0;
1224                }
1225                if (tsdPtr->runLoop) {
1226                    CFRunLoopSourceSignal(tsdPtr->runLoopSource);
1227                    CFRunLoopWakeUp(tsdPtr->runLoop);
1228                }
1229            }
1230        }
1231        UNLOCK_NOTIFIER;
1232
1233        /*
1234         * Consume the next byte from the notifier pipe if the pipe was
1235         * readable. Note that there may be multiple bytes pending, but to
1236         * avoid a race condition we only read one at a time.
1237         */
1238
1239        if (FD_ISSET(receivePipe, &readableMask)) {
1240            i = read(receivePipe, buf, 1);
1241
1242            if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) {
1243                /*
1244                 * Someone closed the write end of the pipe or sent us a Quit
1245                 * message [Bug: 4139] and then closed the write end of the
1246                 * pipe so we need to shut down the notifier thread.
1247                 */
1248
1249                break;
1250            }
1251        }
1252    }
1253    pthread_exit(0);
1254}
1255
1256#ifdef HAVE_PTHREAD_ATFORK
1257/*
1258 *----------------------------------------------------------------------
1259 *
1260 * AtForkPrepare --
1261 *
1262 *      Lock the notifier in preparation for a fork.
1263 *
1264 * Results:
1265 *      None.
1266 *
1267 * Side effects:
1268 *      None.
1269 *
1270 *----------------------------------------------------------------------
1271 */
1272
1273static void
1274AtForkPrepare(void)
1275{
1276    LOCK_NOTIFIER_INIT;
1277    LOCK_NOTIFIER;
1278}
1279
1280/*
1281 *----------------------------------------------------------------------
1282 *
1283 * AtForkParent --
1284 *
1285 *      Unlock the notifier in the parent after a fork.
1286 *
1287 * Results:
1288 *      None.
1289 *
1290 * Side effects:
1291 *      None.
1292 *
1293 *----------------------------------------------------------------------
1294 */
1295
1296static void
1297AtForkParent(void)
1298{
1299    UNLOCK_NOTIFIER;
1300    UNLOCK_NOTIFIER_INIT;
1301}
1302
1303/*
1304 *----------------------------------------------------------------------
1305 *
1306 * AtForkChild --
1307 *
1308 *      Unlock and reinstall the notifier in the child after a fork.
1309 *
1310 * Results:
1311 *      None.
1312 *
1313 * Side effects:
1314 *      None.
1315 *
1316 *----------------------------------------------------------------------
1317 */
1318
1319static void
1320AtForkChild(void)
1321{
1322    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1323
1324    UNLOCK_NOTIFIER;
1325    UNLOCK_NOTIFIER_INIT;
1326    if (tsdPtr->runLoop) {
1327        tsdPtr->runLoop = NULL;
1328        if (!noCFafterFork) {
1329            CFRunLoopSourceInvalidate(tsdPtr->runLoopSource);
1330        }
1331        CFRelease(tsdPtr->runLoopSource);
1332        tsdPtr->runLoopSource = NULL;
1333    }
1334    if (notifierCount > 0) {
1335        notifierCount = 0;
1336
1337        /*
1338         * Assume that the return value of Tcl_InitNotifier in the child will
1339         * be identical to the one stored as clientData in tclNotify.c's
1340         * ThreadSpecificData by the parent's TclInitNotifier, so discard the
1341         * return value here. This assumption may require the fork() to be
1342         * executed in the main thread of the parent, otherwise
1343         * Tcl_AlertNotifier may break in the child.
1344         */
1345
1346        if (!noCFafterFork) {
1347            Tcl_InitNotifier();
1348        }
1349    }
1350}
1351#endif /* HAVE_PTHREAD_ATFORK */
1352
1353#endif /* HAVE_COREFOUNDATION */
1354
1355/*
1356 * Local Variables:
1357 * mode: c
1358 * c-basic-offset: 4
1359 * fill-column: 78
1360 * End:
1361 */
Note: See TracBrowser for help on using the repository browser.