From 07abfaa1257d10162ab31f3e2e113c192650e2d8 Mon Sep 17 00:00:00 2001 From: das Date: Mon, 24 Aug 2009 00:27:53 +0000 Subject: * macosx/tclMacOSXNotify.c: fix multiple issues with nested event loops when CoreFoundation notifier is running in embedded mode. (fixes problems in TkAqua Cocoa reported by Youness Alaoui on tcl-mac) --- ChangeLog | 6 ++++ macosx/tclMacOSXNotify.c | 79 +++++++++++++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index 84d8453..f982bcd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2009-08-24 Daniel Steffen + + * macosx/tclMacOSXNotify.c: fix multiple issues with nested event loops + when CoreFoundation notifier is running in embedded mode. (fixes + problems in TkAqua Cocoa reported by Youness Alaoui on tcl-mac) + 2009-08-21 Don Porter * generic/tclFileName.c: Correct regression in [Bug 2837800] fix. diff --git a/macosx/tclMacOSXNotify.c b/macosx/tclMacOSXNotify.c index f56bb9a..8016b32 100644 --- a/macosx/tclMacOSXNotify.c +++ b/macosx/tclMacOSXNotify.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclMacOSXNotify.c,v 1.18.2.4 2009/04/14 00:55:34 das Exp $ + * RCS: @(#) $Id: tclMacOSXNotify.c,v 1.18.2.5 2009/08/24 00:27:53 das Exp $ */ #include "tclInt.h" @@ -246,6 +246,8 @@ typedef struct ThreadSpecificData { * performed. */ int runLoopRunning; /* True if this thread's Tcl runLoop is running */ int runLoopNestingLevel; /* Level of nested runLoop invocations */ + int runLoopServicingEvents; /* True if this thread's runLoop is servicing + * tcl events */ /* Must hold the notifierLock before accessing the following fields: */ /* Start notifierLock section */ int onList; /* True if this thread is on the waitingList */ @@ -277,7 +279,7 @@ typedef struct ThreadSpecificData { /* Any other thread alerts a notifier that an * event is ready to be processed by signaling * this CFRunLoopSource. */ - CFRunLoopObserverRef runLoopObserver; + CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; /* Adds/removes this thread from waitingList * when the CFRunLoop starts/stops. */ CFRunLoopTimerRef runLoopTimer; @@ -453,7 +455,7 @@ Tcl_InitNotifier(void) CFRunLoopSourceRef runLoopSource; CFRunLoopSourceContext runLoopSourceContext; CFRunLoopObserverContext runLoopObserverContext; - CFRunLoopObserverRef runLoopObserver; + CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext)); runLoopSourceContext.info = tsdPtr; @@ -477,12 +479,30 @@ Tcl_InitNotifier(void) "CFRunLoopObserver"); } CFRunLoopAddObserver(runLoop, runLoopObserver, kCFRunLoopCommonModes); - CFRunLoopAddObserver(runLoop, runLoopObserver, + + /* + * Create a second CFRunLoopObserver with the same callback as above + * for the tclEventsOnlyRunLoopMode to ensure that the callback can be + * re-entered via Tcl_ServiceAll() in the kCFRunLoopBeforeWaiting case + * (CFRunLoop prevents observer callback re-entry of a given observer + * instance). + */ + + runLoopObserverTcl = CFRunLoopObserverCreate(NULL, + kCFRunLoopEntry|kCFRunLoopExit|kCFRunLoopBeforeWaiting, TRUE, + LONG_MIN, UpdateWaitingListAndServiceEvents, + &runLoopObserverContext); + if (!runLoopObserverTcl) { + Tcl_Panic("Tcl_InitNotifier: could not create " + "CFRunLoopObserver"); + } + CFRunLoopAddObserver(runLoop, runLoopObserverTcl, tclEventsOnlyRunLoopMode); tsdPtr->runLoop = runLoop; tsdPtr->runLoopSource = runLoopSource; tsdPtr->runLoopObserver = runLoopObserver; + tsdPtr->runLoopObserverTcl = runLoopObserverTcl; tsdPtr->runLoopTimer = NULL; tsdPtr->waitTime = CF_TIMEINTERVAL_FOREVER; tsdPtr->tsdLock = SPINLOCK_INIT; @@ -710,6 +730,9 @@ Tcl_FinalizeNotifier( CFRunLoopObserverInvalidate(tsdPtr->runLoopObserver); CFRelease(tsdPtr->runLoopObserver); tsdPtr->runLoopObserver = NULL; + CFRunLoopObserverInvalidate(tsdPtr->runLoopObserverTcl); + CFRelease(tsdPtr->runLoopObserverTcl); + tsdPtr->runLoopObserverTcl = NULL; if (tsdPtr->runLoopTimer) { CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer); CFRelease(tsdPtr->runLoopTimer); @@ -1150,9 +1173,8 @@ int Tcl_WaitForEvent( Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { - int result, polling; + int result, polling, runLoopRunning; CFTimeInterval waitTime; - CFStringRef runLoopMode; SInt32 runLoopStatus; ThreadSpecificData *tsdPtr; @@ -1200,22 +1222,20 @@ Tcl_WaitForEvent( tsdPtr->runLoopSourcePerformed = 0; /* - * If the Tcl run loop is already running (e.g. if Tcl_WaitForEvent was - * called recursively), re-run it in a custom run loop mode containing only - * the source for the notifier thread, otherwise wakeups from other sources - * added to the common run loop modes might get lost. + * If the Tcl runloop is already running (e.g. if Tcl_WaitForEvent was + * called recursively) or is servicing events via the runloop observer, + * re-run it in a custom runloop mode containing only the source for the + * notifier thread, otherwise wakeups from other sources added to the + * common runloop modes might get lost or 3rd party event handlers might + * get called when they do not expect to be. */ - if (tsdPtr->runLoopRunning) { - runLoopMode = tclEventsOnlyRunLoopMode; - } else { - runLoopMode = kCFRunLoopDefaultMode; - tsdPtr->runLoopRunning = 1; - } - runLoopStatus = CFRunLoopRunInMode(runLoopMode, waitTime, TRUE); - if (runLoopMode == kCFRunLoopDefaultMode) { - tsdPtr->runLoopRunning = 0; - } + runLoopRunning = tsdPtr->runLoopRunning; + tsdPtr->runLoopRunning = 1; + runLoopStatus = CFRunLoopRunInMode(tsdPtr->runLoopServicingEvents || + runLoopRunning ? tclEventsOnlyRunLoopMode : kCFRunLoopDefaultMode, + waitTime, TRUE); + tsdPtr->runLoopRunning = runLoopRunning; LOCK_NOTIFIER_TSD; tsdPtr->polling = 0; @@ -1333,19 +1353,22 @@ UpdateWaitingListAndServiceEvents( { ThreadSpecificData *tsdPtr = (ThreadSpecificData*) info; + if (tsdPtr->sleeping) { + return; + } switch (activity) { case kCFRunLoopEntry: tsdPtr->runLoopNestingLevel++; - if (tsdPtr->runLoopNestingLevel == 1 && !tsdPtr->sleeping && - (tsdPtr->numFdBits > 0 || tsdPtr->polling)) { + if (tsdPtr->numFdBits > 0 || tsdPtr->polling) { LOCK_NOTIFIER; - OnOffWaitingList(tsdPtr, 1, 1); + if (!OnOffWaitingList(tsdPtr, 1, 1) && tsdPtr->polling) { + write(triggerPipe, "", 1); + } UNLOCK_NOTIFIER; } break; case kCFRunLoopExit: - if (tsdPtr->runLoopNestingLevel == 1 && !tsdPtr->sleeping && - (tsdPtr->numFdBits > 0 || tsdPtr->polling)) { + if (tsdPtr->runLoopNestingLevel == 1) { LOCK_NOTIFIER; OnOffWaitingList(tsdPtr, 0, 1); UNLOCK_NOTIFIER; @@ -1353,9 +1376,11 @@ UpdateWaitingListAndServiceEvents( tsdPtr->runLoopNestingLevel--; break; case kCFRunLoopBeforeWaiting: - if (!tsdPtr->sleeping && tsdPtr->runLoopTimer && + if (tsdPtr->runLoopTimer && !tsdPtr->runLoopServicingEvents && (tsdPtr->runLoopNestingLevel > 1 || !tsdPtr->runLoopRunning)) { + tsdPtr->runLoopServicingEvents = 1; while (Tcl_ServiceAll() && tsdPtr->waitTime == 0) {} + tsdPtr->runLoopServicingEvents = 0; } break; default: @@ -1641,7 +1666,7 @@ TclUnixWaitForFile( if (FD_ISSET(fd, &writableMask)) { SET_BITS(result, TCL_WRITABLE); } - if (FD_ISSET(fd, &exceptionalMask)) { + if (FD_ISSET(fd, &exceptionalMask)) { SET_BITS(result, TCL_EXCEPTION); } result &= mask; -- cgit v0.12