Fix runloop problems where a timer is added to the loop more than once

(in different modes).


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25694 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
rfm 2007-12-07 06:32:04 +00:00
parent f62d69afd1
commit 5429e579a7
4 changed files with 64 additions and 32 deletions

View file

@ -1,3 +1,13 @@
2007-12-07 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSRunLoop.m: Keep timers unordered and check all of them
each time round ... to avoid bug where a timer is added to more
than one run loop mode and firing in one mode could result in badly
ordered timers in another mode.
Handle resetting of time for repeating timers.
* Source/NSTimer.m: Remove resetting of fire date from ([-fire])
and move it to the run loop for MacOS-X compatibility.
2007-12-06 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSRunLoop.m: Report the current mode when producing detailed

View file

@ -29,6 +29,7 @@
#include <Foundation/NSZone.h>
/* To turn assertions on, define GSI_ARRAY_CHECKS */
#define GSI_ARRAY_CHECKS 1
#if defined(__cplusplus)
extern "C" {

View file

@ -740,6 +740,10 @@ static NSComparisonResult tSort(GSIArrayItem i0, GSIArrayItem i1)
{
GSRunLoopCtxt *context;
GSIArray timers;
unsigned i;
NSDebugMLLog(@"NSRunLoop", @"add timer for %f in %@",
[[timer fireDate] timeIntervalSinceReferenceDate], mode);
context = NSMapGet(_contextMap, mode);
if (context == nil)
@ -749,7 +753,30 @@ static NSComparisonResult tSort(GSIArrayItem i0, GSIArrayItem i1)
RELEASE(context);
}
timers = context->timers;
GSIArrayInsertSorted(timers, (GSIArrayItem)((id)timer), tSort);
i = GSIArrayCount(timers);
while (i-- > 0)
{
if (timer == GSIArrayItemAtIndex(timers, i).obj)
{
return; /* Timer already present */
}
}
/*
* NB. A previous version of the timer code maintained an ordered
* array on the theory that we could improve performance by only
* checking the first few timers (up to the first one whose fire
* date is in the future) each time -limitDateForMode: is called.
* The problem with this was that it's possible for one timer to
* be added in multiple modes (or to different run loops) and for
* a repeated timer this could mean that the firing of the timer
* in one mode/loop adjusts its date ... without changing the
* ordering of the timers in the other modes/loops which contain
* the timer. When the ordering of timers in an array was broken
* we could get delays in processing timeouts, so we reverted to
* simply having timers in an unordered array and checking them
* all each time -limitDateForMode: is called.
*/
GSIArrayAddItem(timers, (GSIArrayItem)((id)timer));
}
@ -776,6 +803,7 @@ static NSComparisonResult tSort(GSIArrayItem i0, GSIArrayItem i1)
GSIArray timers = context->timers;
NSTimeInterval now;
NSTimer *t;
unsigned i;
/*
* Save current time so we don't keep redoing system call to
@ -788,25 +816,32 @@ static NSComparisonResult tSort(GSIArrayItem i0, GSIArrayItem i1)
/*
* Fire housekeeping timer as necessary
*/
while ((t = context->housekeeper) != nil
if ((t = context->housekeeper) != nil
&& ([timerDate(t) timeIntervalSinceReferenceDate] <= now))
{
NSDate *next;
[t fire];
IF_NO_GC([arp emptyPool]);
now = GSTimeNow();
next = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:
now + [t timeInterval]];
[t setFireDate: next];
RELEASE(next);
}
/*
* Handle normal timers ... remove invalidated timers and fire any
* whose date has passed.
*/
while (GSIArrayCount(timers) != 0)
i = GSIArrayCount(timers);
while (i-- > 0)
{
NSTimer *min_timer = GSIArrayItemAtIndex(timers, 0).obj;
NSTimer *min_timer = GSIArrayItemAtIndex(timers, i).obj;
if (timerInvalidated(min_timer) == YES)
{
GSIArrayRemoveItemAtIndex(timers, 0);
GSIArrayRemoveItemAtIndex(timers, i);
min_timer = nil;
continue;
}
@ -817,18 +852,21 @@ static NSComparisonResult tSort(GSIArrayItem i0, GSIArrayItem i1)
break;
}
GSIArrayRemoveItemAtIndexNoRelease(timers, 0);
/* Firing will also increment its fireDate, if it is repeating. */
[min_timer fire];
now = GSTimeNow();
if (timerInvalidated(min_timer) == NO)
{
GSIArrayInsertSortedNoRetain(timers,
(GSIArrayItem)((id)min_timer), tSort);
NSDate *next;
next = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:
now + [min_timer timeInterval]];
[min_timer setFireDate: next];
RELEASE(next);
}
else
{
RELEASE(min_timer);
GSIArrayRemoveItemAtIndex(timers, i);
}
GSPrivateNotifyASAP(); /* Post notifications. */
IF_NO_GC([arp emptyPool]);

View file

@ -99,6 +99,10 @@ static Class NSDate_class;
_selector = selector;
_info = RETAIN(info);
_repeats = f;
if (_repeats == NO)
{
_interval = 0.0;
}
return self;
}
@ -239,28 +243,6 @@ static Class NSDate_class;
{
[self invalidate];
}
else if (_invalidated == NO)
{
extern NSTimeInterval GSTimeNow();
NSTimeInterval now = GSTimeNow();
NSTimeInterval nxt = [_date timeIntervalSinceReferenceDate];
int inc = -1;
while (nxt <= now) // xxx remove this
{
inc++;
nxt += _interval;
}
#ifdef LOG_MISSED
if (inc > 0)
{
NSLog(@"Missed %d timeouts at %f second intervals", inc, _interval);
}
#endif
RELEASE(_date);
_date = [[NSDate_class allocWithZone: NSDefaultMallocZone()]
initWithTimeIntervalSinceReferenceDate: nxt];
}
}
/**
@ -320,7 +302,8 @@ static Class NSDate_class;
}
/**
* Returns the interval between firings.
* Returns the interval between firings, or zero if the timer
* does not repeat.
*/
- (NSTimeInterval) timeInterval
{