Bug 28455 - ThreadTimer: avoid blocking UI when too many timers ready to fire
Summary: ThreadTimer: avoid blocking UI when too many timers ready to fire
Status: RESOLVED DUPLICATE of bug 23865
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebCore Misc. (show other bugs)
Version: 528+ (Nightly build)
Hardware: Other Other
: P2 Normal
Assignee: Nobody
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-08-19 08:37 PDT by Yong Li
Modified: 2009-08-21 00:16 PDT (History)
4 users (show)

See Also:


Attachments
the patch (5.04 KB, patch)
2009-08-19 08:51 PDT, Yong Li
darin: review-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Yong Li 2009-08-19 08:37:29 PDT
Current ThreadTimer fires all timers that meet the fire time. This can block UI, if some of those timers are big time-consumers.

Solution is to set a timeout when firing those timers. When time is out, those unfired timers should be postponed to next time.
Comment 1 Yong Li 2009-08-19 08:51:12 PDT
Created attachment 35123 [details]
the patch
Comment 2 Eric Seidel (no email) 2009-08-19 10:44:59 PDT
Darin Adler is probably your best bet for review here.
Comment 3 Darin Adler 2009-08-19 11:24:35 PDT
Comment on attachment 35123 [details]
the patch

The concept of the code is here OK, but the conditionals need to be done better.

I think you should have the TimerBeingFired struct all the time, and just make the fire time member be conditional.

Normally we do not use the m_ prefix for struct members, only for classes where they are private.

> +    for (Vector<TimerBeingFired>::const_iterator i = firingTimers.begin(); i != firingTimers.end(); ++i) {
> +        TimerBase* timer = i->m_timer;

>      for (size_t i = 0; i != size; ++i) {
>          TimerBase* timer = firingTimers[i];

We do not normally use iterators to go through a Vector. Instead we just use indexing as you would with an array. That's what we did in the existing code, and there's no reason to change that just because the timers are a struct.

> +        // 30ms
> +        if (currentTime() - startTime > 0.03) {

The time interval should be a constant at the top of the file, and the comment should explain why that particular interval was chosen.

> +            for (++i; i != firingTimers.end(); ++i) {
> +                timer = i->m_timer;
> +                if (timer->m_nextFireTime == 0 && m_timersReadyToFire.contains(timer))
> +                    timer->setNextFireTime(i->m_fireTime);
> +            }

I wrote the class, and yet I do not understand this code. It may be correct. There needs to be a comment explaining why this is the right thing to do.
Comment 4 Dmitry Titov 2009-08-19 12:43:42 PDT
I refactored the code Darin originally wrote to the point when I almost understand it :-) so let me add a comment: I think this will remove and then re-insert unfired timers into the heap structure, causing unneeded sort operations - which might be unwanted in a scenario when we have so many timers it freezes UI...

Bug 23865 has a patch addressing the same very issue, but instead of taking all the ready timers first into the separate list and then re-inserting the unfired timers back, it just fires them in a loop one-by-one, taking them from original heap until time interval permits. This avoids taking and then re-inserting timers into timer heap, while still keeping track of removed and rescheduled timers.

That patch was not landed because the original bug was about even bigger issue (recursive multiplication of timers) - but for avoiding UI freeze IMHO the patch would work better. Also, I think it is useful for any port and doesn't affect functionality so it doesn't need ENABLE define guards.

If you find this makes sense, please feel free to use the code in Bug 23865 as an idea or ping me to continue to work on it...
Comment 5 Yong Li 2009-08-19 14:11:02 PDT
> > +    for (Vector<TimerBeingFired>::const_iterator i = firingTimers.begin(); i != firingTimers.end(); ++i) {
> > +        TimerBase* timer = i->m_timer;
> 
> >      for (size_t i = 0; i != size; ++i) {
> >          TimerBase* timer = firingTimers[i];
> 
> We do not normally use iterators to go through a Vector. Instead we just use
> indexing as you would with an array. That's what we did in the existing code,
> and there's no reason to change that just because the timers are a struct.

I agree with the rest of your suggestions. but for this one I think using iterator (or const_iterator) should be better.

First, "iterator" is kind of standard way for enumerating a C++ container. 

typedef Vector<some type> MyContainerType; 

for (MyContainerType::const_iterator i = container.begin(); i != container.end(); ++i) {
}

If someday you change the container type from Vector to List, you don't have to change the code that enumerates the container.

Second, I think the fastest way of walking through an array is using an increasing pointer instead of an index, because array[i] may require i * sizeof(T) to get the address of the element. Although some compilers can be smart enough to optimize this, I saw replacing array index with increasing pointer boosted performance in practice. Vector<T>::iterator is just a pointer T*, and so that it doesn't rely on how smart the compiler is to make the code run faster.
Comment 6 Yong Li 2009-08-19 14:21:40 PDT
(In reply to comment #4)
> I refactored the code Darin originally wrote to the point when I almost
> understand it :-) so let me add a comment: I think this will remove and then
> re-insert unfired timers into the heap structure, causing unneeded sort
> operations - which might be unwanted in a scenario when we have so many timers
> it freezes UI...
> 
> Bug 23865 has a patch addressing the same very issue, but instead of taking all
> the ready timers first into the separate list and then re-inserting the unfired
> timers back, it just fires them in a loop one-by-one, taking them from original
> heap until time interval permits. This avoids taking and then re-inserting
> timers into timer heap, while still keeping track of removed and rescheduled
> timers.
> 
> That patch was not landed because the original bug was about even bigger issue
> (recursive multiplication of timers) - but for avoiding UI freeze IMHO the
> patch would work better. Also, I think it is useful for any port and doesn't
> affect functionality so it doesn't need ENABLE define guards.
> 
> If you find this makes sense, please feel free to use the code in Bug 23865 as
> an idea or ping me to continue to work on it...

If that patch doesn't 
(In reply to comment #4)
> I refactored the code Darin originally wrote to the point when I almost
> understand it :-) so let me add a comment: I think this will remove and then
> re-insert unfired timers into the heap structure, causing unneeded sort
> operations - which might be unwanted in a scenario when we have so many timers
> it freezes UI...
> 
> Bug 23865 has a patch addressing the same very issue, but instead of taking all
> the ready timers first into the separate list and then re-inserting the unfired
> timers back, it just fires them in a loop one-by-one, taking them from original
> heap until time interval permits. This avoids taking and then re-inserting
> timers into timer heap, while still keeping track of removed and rescheduled
> timers.
> 
> That patch was not landed because the original bug was about even bigger issue
> (recursive multiplication of timers) - but for avoiding UI freeze IMHO the
> patch would work better. Also, I think it is useful for any port and doesn't
> affect functionality so it doesn't need ENABLE define guards.
> 
> If you find this makes sense, please feel free to use the code in Bug 23865 as
> an idea or ping me to continue to work on it...

Hm... It seems better.
Comment 7 Darin Adler 2009-08-19 17:23:31 PDT
> I agree with the rest of your suggestions. but for this one I think using
> iterator (or const_iterator) should be better.
> 
> First, "iterator" is kind of standard way for enumerating a C++ container. 
> 
> typedef Vector<some type> MyContainerType; 
> 
> for (MyContainerType::const_iterator i = container.begin(); i !=
> container.end(); ++i) {
> }
> 
> If someday you change the container type from Vector to List, you don't have to
> change the code that enumerates the container.

I don't agree. I am aware that using iterators lets you use the same code on Vector and List. I find the code using indices much easier to read and have never had an experience where I had to change code from Vector to List.

I do find it useful for generic code that works on, say, both Vector and List.
Comment 8 Dmitry Titov 2009-08-21 00:16:08 PDT
Re-resolving due to Bugzilla data loss. The bug 23865 now has the patch fixing this issue.

*** This bug has been marked as a duplicate of bug 23865 ***