Cocoa: allow timer activation to recurse

CFRunLoopTimer's callback will never recurse. Since we are using one
CFRunLoopTimer to drive all Qt timers, we need to work around this by
sending all timers via a dedicated CFRunLoopSource (since these
callbacks can recurse). We also need to block this new timer source
along with the posted event source when calling processEvents()
"manually" to prevent livelock deep in CFRunLoop.

Change-Id: I375e46b6cfa0c76db678a1085314d42d8996d062
Reviewed-by: Morten Johan Sørvig <morten.sorvig@nokia.com>
This commit is contained in:
Bradley T. Hughes 2012-04-11 13:08:40 +02:00 committed by Qt by Nokia
parent 2afeb07227
commit bffbfc5b33
2 changed files with 57 additions and 23 deletions

View File

@ -116,7 +116,6 @@ public:
explicit QCocoaEventDispatcher(QObject *parent = 0);
~QCocoaEventDispatcher();
bool processEvents(QEventLoop::ProcessEventsFlags flags);
bool hasPendingEvents();
@ -154,9 +153,11 @@ public:
// timer handling
QTimerInfoList timerInfoList;
CFRunLoopTimerRef runLoopTimerRef;
CFRunLoopSourceRef activateTimersSourceRef;
void maybeStartCFRunLoopTimer();
void maybeStopCFRunLoopTimer();
static void activateTimer(CFRunLoopTimerRef, void *info);
static void runLoopTimerCallback(CFRunLoopTimerRef, void *info);
static void activateTimersSourceCallback(void *info);
// Set 'blockSendPostedEvents' to true if you _really_ need
// to make sure that qt events are not posted while calling
@ -188,8 +189,7 @@ public:
int lastSerial;
bool interrupt;
static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2);
static void postedEventsSourcePerformCallback(void *info);
static void postedEventsSourceCallback(void *info);
static void waitingObserverCallback(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void *info);
static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info);

View File

@ -104,12 +104,28 @@ static inline CFRunLoopRef mainRunLoop()
return CFRunLoopGetMain();
}
static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
{
return info1 == info2;
}
/*****************************************************************************
Timers stuff
*****************************************************************************/
/* timer call back */
void QCocoaEventDispatcherPrivate::activateTimer(CFRunLoopTimerRef, void *info)
void QCocoaEventDispatcherPrivate::runLoopTimerCallback(CFRunLoopTimerRef, void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
// processEvents() was called "manually," ignore this source for now
d->maybeCancelWaitForMoreEvents();
return;
}
CFRunLoopSourceSignal(d->activateTimersSourceRef);
}
void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
(void) d->timerInfoList.activateTimers();
@ -145,7 +161,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFRunLoopTimerContext info = { 0, this, 0, 0, 0 };
// create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate()
// documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working
runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::activateTimer, &info);
runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info);
Q_ASSERT(runLoopTimerRef != 0);
CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes);
@ -513,10 +529,10 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
emit awake();
bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents;
bool retVal = false;
uint oldflags = d->processEventsFlags;
d->processEventsFlags = flags;
bool excludeUserEvents = d->processEventsFlags & QEventLoop::ExcludeUserInputEvents;
bool retVal = false;
forever {
if (d->interrupt)
break;
@ -544,8 +560,9 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
// Finally, if we are to exclude user input events, we cannot call [NSApp run]
// as we then loose control over which events gets dispatched:
const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
const bool canExec_Qt = !excludeUserEvents &&
(flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) ;
const bool canExec_Qt = (!excludeUserEvents
&& ((d->processEventsFlags & QEventLoop::DialogExec)
|| (d->processEventsFlags & QEventLoop::EventLoopExec)));
if (canExec_Qt && canExec_3rdParty) {
// We can use exec-mode, meaning that we can stay in a tight loop until
@ -632,9 +649,11 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
}
} while (!d->interrupt && event != nil);
if ((flags & QEventLoop::WaitForMoreEvents) == 0) {
// when called "manually", always send posted events
if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
// when called "manually", always send posted events and timers
d->processPostedEvents();
retVal = d->timerInfoList.activateTimers() > 0 || retVal;
d->maybeStartCFRunLoopTimer();
}
// be sure to return true if the posted event source fired
@ -651,12 +670,12 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
bool canWait = (d->threadData->canWait
&& !retVal
&& !d->interrupt
&& (flags & QEventLoop::WaitForMoreEvents));
&& (d->processEventsFlags & QEventLoop::WaitForMoreEvents));
if (canWait) {
// INVARIANT: We haven't processed any events yet. And we're told
// to stay inside this function until at least one event is processed.
qt_mac_waitForMoreEvents();
flags &= ~QEventLoop::WaitForMoreEvents;
d->processEventsFlags &= ~QEventLoop::WaitForMoreEvents;
} else {
// Done with event processing for now.
// Leave the function:
@ -904,15 +923,28 @@ QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent)
: QAbstractEventDispatcher(*new QCocoaEventDispatcherPrivate, parent)
{
Q_D(QCocoaEventDispatcher);
// keep our sources running when modal loops are running
CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
CFRunLoopSourceContext context;
bzero(&context, sizeof(CFRunLoopSourceContext));
context.info = d;
context.equal = QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback;
context.perform = QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback;
d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context);
context.equal = runLoopSourceEqualCallback;
// source used to activate timers
context.perform = QCocoaEventDispatcherPrivate::activateTimersSourceCallback;
d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(d->activateTimersSourceRef);
CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
// source used to send posted events
context.perform = QCocoaEventDispatcherPrivate::postedEventsSourceCallback;
d->postedEventsSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(d->postedEventsSource);
CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
// observer to emit aboutToBlock() and awake()
CFRunLoopObserverContext observerContext;
bzero(&observerContext, sizeof(CFRunLoopObserverContext));
observerContext.info = this;
@ -948,11 +980,6 @@ void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef,
emit static_cast<QCocoaEventDispatcher*>(info)->awake();
}
Boolean QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2)
{
return info1 == info2;
}
void QCocoaEventDispatcherPrivate::processPostedEvents()
{
if (blockSendPostedEvents) {
@ -1017,9 +1044,14 @@ void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
static_cast<QCocoaEventDispatcherPrivate *>(info)->processPostedEvents();
}
void QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback(void *info)
void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
// processEvents() was called "manually," ignore this source for now
d->maybeCancelWaitForMoreEvents();
return;
}
d->processPostedEvents();
d->maybeCancelWaitForMoreEvents();
}
@ -1069,6 +1101,8 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
qDeleteAll(d->timerInfoList);
d->maybeStopCFRunLoopTimer();
CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
CFRelease(d->activateTimersSourceRef);
// Remove CFSockets from the runloop.
for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) {