@@ -887,7 +887,7 @@ - (void) addTimer: (NSTimer*)timer
887887{
888888 const void *loop = (const void *)self;
889889 GSRunLoopCtxt *context;
890- GSIArray timers;
890+ GSMinHeap * timers;
891891 unsigned i;
892892
893893 if ([timer isKindOfClass: [NSTimer class ]] == NO
@@ -903,6 +903,12 @@ - (void) addTimer: (NSTimer*)timer
903903 format: @" [%@ -%@ ] timer already scheduled in another runloop" ,
904904 NSStringFromClass ([self class ]), NSStringFromSelector (_cmd )];
905905 }
906+ if (255 == timer->_scheduled )
907+ {
908+ [NSException raise: NSInvalidArgumentException
909+ format: @" [%@ -%@ ] timer already scheduled in too many modes" ,
910+ NSStringFromClass ([self class ]), NSStringFromSelector (_cmd )];
911+ }
906912 if ([mode isKindOfClass: [NSString class ]] == NO )
907913 {
908914 [NSException raise: NSInvalidArgumentException
@@ -927,40 +933,22 @@ - (void) addTimer: (NSTimer*)timer
927933 {
928934 /* Timer was already scheduled: check whether it is in this mode.
929935 */
930- i = GSIArrayCount (timers);
931- while (i-- > 0 )
936+ if ([timers containsObjectIdenticalTo: timer])
932937 {
933- if (timer == GSIArrayItemAtIndex (timers, i).obj )
934- {
935- return ; /* Timer already present */
936- }
938+ return ; /* Timer already present in this mode */
937939 }
938940 }
939941
940- /* Count scheduling of timer (skip counts too large to fit field).
942+ /* Timers can be scheduled in more than one mode, and if a timer fires
943+ * and repeats it will have updated its fire date. That will leave it
944+ * incorrectly positioned in the min heap in other modes. To handle
945+ * that we must track whether it is scheduled in more than one mode to
946+ * know if we need to check other modes for repositionng.
941947 */
942- if (timer->_scheduled < 255 )
943- {
944- timer->_scheduled ++;
945- }
948+ timer->_scheduled ++;
949+ [timers push: timer];
946950
947- /*
948- * NB. A previous version of the timer code maintained an ordered
949- * array on the theory that we could improve performance by only
950- * checking the first few timers (up to the first one whose fire
951- * date is in the future) each time -limitDateForMode: is called.
952- * The problem with this was that it's possible for one timer to
953- * be added in multiple modes (or to different run loops) and for
954- * a repeated timer this could mean that the firing of the timer
955- * in one mode/loop adjusts its date ... without changing the
956- * ordering of the timers in the other modes/loops which contain
957- * the timer. When the ordering of timers in an array was broken
958- * we could get delays in processing timeouts, so we reverted to
959- * simply having timers in an unordered array and checking them
960- * all each time -limitDateForMode: is called.
961- */
962- GSIArrayAddItem (timers, (GSIArrayItem)((id )timer));
963- i = GSIArrayCount (timers);
951+ i = [timers count ];
964952 if (i % 1000 == 0 && i > context->maxTimers )
965953 {
966954 context->maxTimers = i;
@@ -1029,20 +1017,14 @@ - (NSDate*) _limitDateForContext: (GSRunLoopCtxt *)context
10291017{
10301018 NSDate *when = nil ;
10311019 NSAutoreleasePool *arp = [NSAutoreleasePool new ];
1032- GSIArray timers = context->timers ;
1020+ GSMinHeap * timers = context->timers ;
10331021 NSTimeInterval now;
10341022 NSDate *earliest;
10351023 NSDate *d;
10361024 NSTimer *t;
10371025 NSTimeInterval ti;
1038- NSTimeInterval ei;
1039- unsigned c;
1040- unsigned i;
10411026
1042- ei = 0.0 ; // Only needed to avoid compiler warning
1043-
1044- /*
1045- * Save current time so we don't keep redoing system call to
1027+ /* Save current time so we don't keep redoing system call to
10461028 * get it and so that we check timer fire dates against a known
10471029 * value at the point when the method was called.
10481030 * If we refetched the date after firing each timer, the time
@@ -1054,67 +1036,76 @@ - (NSDate*) _limitDateForContext: (GSRunLoopCtxt *)context
10541036
10551037 /* Fire the oldest/first valid timer whose fire date has passed
10561038 * and fire it.
1057- * We fire timers in the order in which they were added to the
1058- * run loop rather than in date order. This prevents code
1059- * from blocking other timers by adding timers whose fire date
1060- * is some time in the past... we guarantee fair handling.
10611039 */
1062- c = GSIArrayCount ( timers) ;
1063- for (i = 0 ; i < c; i++ )
1040+ t = [ timers peek ] ;
1041+ while (t != nil )
10641042 {
1065- t = GSIArrayItemAtIndex (timers, i).obj ;
1066- if (timerInvalidated (t) == NO )
1043+ if (timerInvalidated (t))
1044+ {
1045+ t = [timers next ];
1046+ }
1047+ else
10671048 {
10681049 d = timerDate (t);
10691050 ti = [d timeIntervalSinceReferenceDate ];
10701051 if (ti < now)
10711052 {
1072- GSIArrayRemoveItemAtIndexNoRelease ( timers, i) ;
1053+ t = [ timers popRetained ] ;
10731054 [t fire ];
10741055 GSPrivateNotifyASAP (_currentMode);
10751056 IF_NO_ARC ([arp emptyPool ];)
10761057 if (updateTimer (t, d, now) == YES )
10771058 {
1078- /* Updated ... replace in array.
1079- */
1080- GSIArrayAddItemNoRetain (timers,
1081- (GSIArrayItem)((id )t));
1082- }
1083- else
1084- {
1085- /* The timer was invalidated, so we can
1086- * release it as we aren't putting it back
1087- * in the array.
1059+ /* Updated ... replace in heap.
10881060 */
1089- RELEASE (t);
1061+ [timers push: t];
1062+ if (t->_scheduled > 1 )
1063+ {
1064+ uint8_t n = t->_scheduled - 1 ;
1065+
1066+ /* The timer was scheduled in more than one mode,
1067+ * so we must reposition it (for the new fire date)
1068+ * in any other mode it is in.
1069+ */
1070+ GS_FOR_IN (NSString *, m, _contextMap)
1071+ {
1072+ GSRunLoopCtxt *c = NSMapGet (_contextMap, m);
1073+
1074+ if (c != context)
1075+ {
1076+ if ([context->timers repositionObject: t] != nil )
1077+ {
1078+ if (--n == 0 )
1079+ {
1080+ break ;
1081+ }
1082+ }
1083+ }
1084+ }
1085+ GS_END_FOR (_contextMap)
1086+ }
10901087 }
1091- break ;
1088+ RELEASE (t) ;
10921089 }
1090+ break ;
10931091 }
10941092 }
10951093
10961094 /* Now, find the earliest remaining timer date while removing
1097- * any invalidated timers. We iterate from the end of the
1098- * array to minimise the amount of array alteration needed.
1095+ * any invalidated timers.
10991096 */
11001097 earliest = nil ;
1101- i = GSIArrayCount ( timers) ;
1102- while (i-- > 0 )
1098+ t = [ timers peek ] ;
1099+ while (t != nil )
11031100 {
1104- t = GSIArrayItemAtIndex (timers, i).obj ;
1105- if (timerInvalidated (t) == YES )
1101+ if (timerInvalidated (t))
11061102 {
1107- GSIArrayRemoveItemAtIndex ( timers, i) ;
1103+ t = [ timers next ] ;
11081104 }
11091105 else
11101106 {
1111- d = timerDate (t);
1112- ti = [d timeIntervalSinceReferenceDate ];
1113- if (earliest == nil || ti < ei)
1114- {
1115- earliest = d;
1116- ei = ti;
1117- }
1107+ earliest = timerDate (t);
1108+ break ;
11181109 }
11191110 }
11201111 [arp drain ];
0 commit comments