diff options
author | darin@google.com <darin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-08-12 18:48:07 +0000 |
---|---|---|
committer | darin@google.com <darin@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-08-12 18:48:07 +0000 |
commit | 5be517218a74dc9e291fb2821e2af9f01fb47e10 (patch) | |
tree | a0c4ff49da17d8121a9796523e1da7faa9e2f7c3 /base | |
parent | b4a718ef7d860ccea49e05fa2959790259c8f712 (diff) | |
download | chromium_src-5be517218a74dc9e291fb2821e2af9f01fb47e10.zip chromium_src-5be517218a74dc9e291fb2821e2af9f01fb47e10.tar.gz chromium_src-5be517218a74dc9e291fb2821e2af9f01fb47e10.tar.bz2 |
Make timer.cc portable by factoring its Windows bits into MessageLoop.
Please note that the goal of this CL is merely to move the Windowisms out of timer.cc and into message_loop.cc. Next up will be to refactor message_loop.cc so that the Windowisms are further isolated.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@734 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/message_loop.cc | 78 | ||||
-rw-r--r-- | base/message_loop.h | 5 | ||||
-rw-r--r-- | base/timer.cc | 153 | ||||
-rw-r--r-- | base/timer.h | 114 | ||||
-rw-r--r-- | base/timer_unittest.cc | 3 | ||||
-rw-r--r-- | base/tracked_objects.cc | 2 |
6 files changed, 158 insertions, 197 deletions
diff --git a/base/message_loop.cc b/base/message_loop.cc index 5331bc6..18f4448 100644 --- a/base/message_loop.cc +++ b/base/message_loop.cc @@ -108,14 +108,17 @@ static LPTOP_LEVEL_EXCEPTION_FILTER GetTopSEHFilter() { //------------------------------------------------------------------------------ -MessageLoop::MessageLoop() : message_hwnd_(NULL), - exception_restoration_(false), - nestable_tasks_allowed_(true), - dispatcher_(NULL), - quit_received_(false), - quit_now_(false), - task_pump_message_pending_(false), - run_depth_(0) { +MessageLoop::MessageLoop() +#pragma warning(suppress: 4355) // OK, to use |this| in the initializer list. + : timer_manager_(this), + message_hwnd_(NULL), + exception_restoration_(false), + nestable_tasks_allowed_(true), + dispatcher_(NULL), + quit_received_(false), + quit_now_(false), + task_pump_message_pending_(false), + run_depth_(0) { DCHECK(tls_index_) << "static initializer failed"; DCHECK(!current()) << "should only have one message loop per thread"; ThreadLocalStorage::Set(tls_index_, this); @@ -406,6 +409,11 @@ LRESULT MessageLoop::WndProc( return 0; } + case WM_TIMER: + ProcessSomeTimers(); // Give the TimerManager a tickle. + DidChangeNextTimerExpiry(); // Maybe generate another WM_TIMER. + return 0; + case kMsgQuit: { // TODO(jar): bug 1300541 The following assert should be used, but // currently too much code actually triggers the assert, especially in @@ -654,21 +662,22 @@ bool MessageLoop::SignalWatcher(size_t object_index) { bool MessageLoop::RunTimerTask(Timer* timer) { HistogramEvent(kTimerEvent); + Task* task = timer->task(); if (task->is_owned_by_message_loop()) { - // We constructed it through PostTask(). + // We constructed it through PostDelayedTask(). DCHECK(!timer->repeating()); timer->set_task(NULL); delete timer; task->ResetBirthTime(); return QueueOrRunTask(task); - } else { - // This is an unknown timer task, and we *can't* delay running it, as a - // user might try to cancel it with TimerManager at any moment. - DCHECK(nestable_tasks_allowed_); - RunTask(task); - return true; } + + // This is an unknown timer task, and we *can't* delay running it, as a user + // might try to cancel it with TimerManager at any moment. + DCHECK(nestable_tasks_allowed_); + RunTask(task); + return true; } void MessageLoop::DiscardTimer(Timer* timer) { @@ -823,6 +832,45 @@ void MessageLoop::DeletePendingTasks() { */ } +void MessageLoop::DidChangeNextTimerExpiry() { +#if defined(OS_WIN) + // + // We would *like* to provide high resolution timers. Windows timers using + // SetTimer() have a 10ms granularity. We have to use WM_TIMER as a wakeup + // mechanism because the application can enter modal windows loops where it + // is not running our MessageLoop; the only way to have our timers fire in + // these cases is to post messages there. + // + // To provide sub-10ms timers, we process timers directly from our run loop. + // For the common case, timers will be processed there as the run loop does + // its normal work. However, we *also* set the system timer so that WM_TIMER + // events fire. This mops up the case of timers not being able to work in + // modal message loops. It is possible for the SetTimer to pop and have no + // pending timers, because they could have already been processed by the + // run loop itself. + // + // We use a single SetTimer corresponding to the timer that will expire + // soonest. As new timers are created and destroyed, we update SetTimer. + // Getting a spurrious SetTimer event firing is benign, as we'll just be + // processing an empty timer queue. + // + int delay = timer_manager_.GetCurrentDelay(); + if (delay == -1) { + KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this)); + } else { + if (delay < USER_TIMER_MINIMUM) + delay = USER_TIMER_MINIMUM; + // Simulates malfunctioning, early firing timers. Pending tasks should only + // be invoked when the delay they specify has elapsed. + if (timer_manager_.use_broken_delay()) + delay = 10; + // Create a WM_TIMER event that will wake us up to check for any pending + // timers (in case we are running within a nested, external sub-pump). + SetTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this), delay, NULL); + } +#endif // defined(OS_WIN) +} + //------------------------------------------------------------------------------ // Implementation of the work_queue_ as a ProiritizedTaskQueue diff --git a/base/message_loop.h b/base/message_loop.h index 85535b4..81c1e52 100644 --- a/base/message_loop.h +++ b/base/message_loop.h @@ -365,6 +365,8 @@ class MessageLoop { //---------------------------------------------------------------------------- private: + friend class TimerManager; // So it can call DidChangeNextTimerExpiry + struct ScopedStateSave { explicit ScopedStateSave(MessageLoop* loop) : loop_(loop), @@ -573,6 +575,9 @@ class MessageLoop { // Post a task to our incomming queue. void PostTaskInternal(Task* task); + // Called by the TimerManager when its next timer changes. + void DidChangeNextTimerExpiry(); + // Start recording histogram info about events and action IF it was enabled // and IF the statistics recorder can accept a registration of our histogram. void StartHistogrammer(); diff --git a/base/timer.cc b/base/timer.cc index 7c3a636..c5e4e87 100644 --- a/base/timer.cc +++ b/base/timer.cc @@ -28,52 +28,17 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "base/timer.h" + +#include <math.h> +#if defined(OS_WIN) #include <mmsystem.h> +#endif #include "base/atomic_sequence_num.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/task.h" -// Note about hi-resolution timers. -// This class would *like* to provide high resolution timers. Windows timers -// using SetTimer() have a 10ms granularity. We have to use WM_TIMER as a -// wakeup mechanism because the application can enter modal windows loops where -// it is not running our MessageLoop; the only way to have our timers fire in -// these cases is to post messages there. -// -// To provide sub-10ms timers, we process timers directly from our main -// MessageLoop. For the common case, timers will be processed there as the -// message loop does its normal work. However, we *also* set the system timer -// so that WM_TIMER events fire. This mops up the case of timers not being -// able to work in modal message loops. It is possible for the SetTimer to -// pop and have no pending timers, because they could have already been -// processed by the message loop itself. -// -// We use a single SetTimer corresponding to the timer that will expire -// soonest. As new timers are created and destroyed, we update SetTimer. -// Getting a spurrious SetTimer event firing is benign, as we'll just be -// processing an empty timer queue. - -static const wchar_t kWndClass[] = L"Chrome_TimerMessageWindow"; - -static LRESULT CALLBACK MessageWndProc(HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) { - if (message == WM_TIMER) { - // Timer not firing? Maybe you're suffering from a WM_PAINTstorm. Make sure - // any WM_PAINT handler you have calls BeginPaint and EndPaint to validate - // the invalid region, otherwise you will be flooded with paint messages - // that trump WM_TIMER when PeekMessage is called. - UINT_PTR timer_id = static_cast<UINT_PTR>(wparam); - TimerManager* tm = reinterpret_cast<TimerManager*>(timer_id); - return tm->MessageWndProc(hwnd, message, wparam, lparam); - } - - return DefWindowProc(hwnd, message, wparam, lparam); -} - // A sequence number for all allocated times (used to break ties when // comparing times in the TimerManager, and assure FIFO execution sequence). static base::AtomicSequenceNumber timer_id_counter_; @@ -90,6 +55,14 @@ Timer::Timer(int delay, Task* task, bool repeating) Reset(); } +int Timer::GetCurrentDelay() const { + // Be careful here. Timers have a precision of microseconds, but this API is + // in milliseconds. If there are 5.5ms left, should the delay be 5 or 6? It + // should be 6 to avoid timers firing early. + double delay = ceil((fire_time_ - Time::Now()).InMillisecondsF()); + return static_cast<int>(delay); +} + void Timer::Reset() { creation_time_ = Time::Now(); fire_time_ = creation_time_ + TimeDelta::FromMilliseconds(delay_); @@ -115,11 +88,10 @@ bool TimerPQueue::ContainsTimer(const Timer* timer) const { //----------------------------------------------------------------------------- // TimerManager -TimerManager::TimerManager() - : message_hwnd_(NULL), - use_broken_delay_(false), - use_native_timers_(true), - message_loop_(NULL) { +TimerManager::TimerManager(MessageLoop* message_loop) + : use_broken_delay_(false), + message_loop_(message_loop) { +#if defined(OS_WIN) // We've experimented with all sorts of timers, and initially tried // to avoid using timeBeginPeriod because it does affect the system // globally. However, after much investigation, it turns out that all @@ -129,18 +101,14 @@ TimerManager::TimerManager() // needs to support a fast clock. We may as well use this ourselves, // as it really is the best timer mechanism for our needs. timeBeginPeriod(1); - - // Initialize the Message HWND in the constructor so that the window - // belongs to the same thread as the message loop (this is important!) - GetMessageHWND(); +#endif } TimerManager::~TimerManager() { +#if defined(OS_WIN) // Match timeBeginPeriod() from construction. timeEndPeriod(1); - - if (message_hwnd_ != NULL) - DestroyWindow(message_hwnd_); +#endif // Be nice to unit tests, and discard and delete all timers along with the // embedded task objects by handing off to MessageLoop (which would have Run() @@ -168,7 +136,7 @@ void TimerManager::StopTimer(Timer* timer) { timers_.RemoveTimer(timer); } else { timers_.pop(); - UpdateWindowsWmTimer(); // We took away the head of our queue. + DidChangeNextTimer(); } } @@ -190,7 +158,6 @@ Timer* TimerManager::PeekTopTimer() { bool TimerManager::RunSomePendingTimers() { bool did_work = false; - bool allowed_to_run = message_loop()->NestableTasksAllowed(); // Process a small group of timers. Cap the maximum number of timers we can // process so we don't deny cycles to other parts of the process when lots of // timers have been set. @@ -203,10 +170,11 @@ bool TimerManager::RunSomePendingTimers() { // the TopTimer. We'll execute the timer task only after the timer queue // is back in a consistent state. Timer* pending = timers_.top(); + // If pending task isn't invoked_later, then it must be possible to run it // now (i.e., current task needs to be reentrant). // TODO(jar): We may block tasks that we can queue from being popped. - if (!message_loop()->NestableTasksAllowed() && + if (!message_loop_->NestableTasksAllowed() && !pending->task()->is_owned_by_message_loop()) break; @@ -219,12 +187,12 @@ bool TimerManager::RunSomePendingTimers() { timers_.push(pending); } - message_loop()->RunTimerTask(pending); + message_loop_->RunTimerTask(pending); } // Restart the WM_TIMER (if necessary). if (did_work) - UpdateWindowsWmTimer(); + DidChangeNextTimer(); return did_work; } @@ -239,77 +207,30 @@ void TimerManager::StartTimer(Timer* timer) { timers_.push(timer); // Priority queue will sort the timer into place. - if (timers_.top() == timer) - UpdateWindowsWmTimer(); // We are new head of queue. -} - -void TimerManager::UpdateWindowsWmTimer() { - if (!use_native_timers_) - return; - - if (timers_.empty()) { - KillTimer(GetMessageHWND(), reinterpret_cast<UINT_PTR>(this)); - return; - } - - int delay = GetCurrentDelay(); - if (delay < USER_TIMER_MINIMUM) - delay = USER_TIMER_MINIMUM; - - // Simulates malfunctioning, early firing timers. Pending tasks should - // only be invoked when the delay they specify has elapsed. - if (use_broken_delay_) - delay = 10; - - // Create a WM_TIMER event that will wake us up to check for any pending - // timers (in case the message loop was otherwise starving us). - SetTimer(GetMessageHWND(), reinterpret_cast<UINT_PTR>(this), delay, NULL); + if (timers_.top() == timer) // We are new head of queue. + DidChangeNextTimer(); } int TimerManager::GetCurrentDelay() { if (timers_.empty()) return -1; - int delay = timers_.top()->current_delay(); + int delay = timers_.top()->GetCurrentDelay(); if (delay < 0) delay = 0; return delay; } -int TimerManager::MessageWndProc(HWND hwnd, UINT message, WPARAM wparam, - LPARAM lparam) { - DCHECK(!lparam); - DCHECK(this == message_loop()->timer_manager()); - if (message_loop()->NestableTasksAllowed()) - RunSomePendingTimers(); - else - UpdateWindowsWmTimer(); - return 0; -} - -MessageLoop* TimerManager::message_loop() { - if (!message_loop_) - message_loop_ = MessageLoop::current(); - DCHECK(message_loop_ == MessageLoop::current()); - return message_loop_; -} - - -HWND TimerManager::GetMessageHWND() { - if (!message_hwnd_) { - HINSTANCE hinst = GetModuleHandle(NULL); - - WNDCLASSEX wc = {0}; - wc.cbSize = sizeof(wc); - wc.lpfnWndProc = ::MessageWndProc; - wc.hInstance = hinst; - wc.lpszClassName = kWndClass; - RegisterClassEx(&wc); - - message_hwnd_ = CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, - hinst, 0); - DCHECK(message_hwnd_); +void TimerManager::DidChangeNextTimer() { + // Determine if the next timer expiry actually changed... + if (!timers_.empty()) { + const Time& expiry = timers_.top()->fire_time(); + if (expiry == next_timer_expiry_) + return; + next_timer_expiry_ = expiry; + } else { + next_timer_expiry_ = Time(); } - return message_hwnd_; + message_loop_->DidChangeNextTimerExpiry(); } //----------------------------------------------------------------------------- diff --git a/base/timer.h b/base/timer.h index de22b64..c23f725 100644 --- a/base/timer.h +++ b/base/timer.h @@ -30,54 +30,50 @@ #ifndef BASE_TIMER_H_ #define BASE_TIMER_H_ -#include <math.h> - #include <queue> #include <vector> #include "base/basictypes.h" #include "base/time.h" -#ifdef OS_WIN -#include <windows.h> -#endif // OS_WIN - +//----------------------------------------------------------------------------- // Timer/TimerManager are objects designed to help setting timers. // Goals of TimerManager: -// - have only one Windows system timer for all app timer functionality +// - have only one system timer for all app timer functionality // - work around bugs with timers firing arbitrarily earlier than specified // - provide the ability to run timers even if the application is in a // windows modal app loop. +//----------------------------------------------------------------------------- +class MessageLoop; class TimerManager; class Task; -class MessageLoop; +//----------------------------------------------------------------------------- +// The core timer object. Use TimerManager to create and control timers. class Timer { public: Timer(int delay, Task* task, bool repeating); + // The task to be run when the timer fires. Task* task() const { return task_; } void set_task(Task* task) { task_ = task; } - int current_delay() const { - // Be careful here. Timers have a precision of microseconds, - // but this API is in milliseconds. If there are 5.5ms left, - // should the delay be 5 or 6? It should be 6 to avoid timers - // firing early. Implement ceiling by adding 999us prior to - // conversion to ms. - double delay = ceil((fire_time_ - Time::Now()).InMillisecondsF()); - return static_cast<int>(delay); - } + // Returns the time in msec relative to now that the timer should fire. + int GetCurrentDelay() const; + // Returns the absolute time at which the timer should fire. const Time &fire_time() const { return fire_time_; } + // A repeating timer is a timer that is automatically scheduled to fire again + // after it fires. bool repeating() const { return repeating_; } // Update (or fill in) creation_time_, and calculate future fire_time_ based // on current time plus delay_. void Reset(); + // A unique identifier for this timer. int id() const { return timer_id_; } protected: @@ -93,9 +89,8 @@ class Timer { // Timer delay in milliseconds. int delay_; - // A monotonically increasing timer id. Used - // for ordering two timers which have the same - // timestamp in a FIFO manner. + // A monotonically increasing timer id. Used for ordering two timers which + // have the same timestamp in a FIFO manner. int timer_id_; // Whether or not this timer repeats. @@ -105,9 +100,11 @@ class Timer { // iteration started.) Time creation_time_; - DISALLOW_EVIL_CONSTRUCTORS(Timer); + DISALLOW_COPY_AND_ASSIGN(Timer); }; +//----------------------------------------------------------------------------- +// Used to implement TimerPQueue class TimerComparison { public: bool operator() (const Timer* t1, const Timer* t2) const { @@ -126,14 +123,14 @@ class TimerComparison { } }; +//----------------------------------------------------------------------------- // Subclass priority_queue to provide convenient access to removal from this // list. // // Terminology: The "pending" timer is the timer at the top of the queue, // i.e. the timer whose task needs to be Run next. -class TimerPQueue : public std::priority_queue<Timer*, - std::vector<Timer*>, - TimerComparison> { +class TimerPQueue : + public std::priority_queue<Timer*, std::vector<Timer*>, TimerComparison> { public: // Removes |timer| from the queue. void RemoveTimer(Timer* timer); @@ -142,32 +139,26 @@ class TimerPQueue : public std::priority_queue<Timer*, bool ContainsTimer(const Timer* timer) const; }; -// There is one TimerManager per thread, owned by the MessageLoop. -// Timers can either be fired directly by the MessageLoop, or by -// SetTimer and a WM_TIMER message. The advantage of the former -// is that we can make timers fire significantly faster than the 10ms -// granularity provided by SetTimer(). The advantage of SetTimer() -// is that modal message loops which don't run our MessageLoop -// code will still be able to process WM_TIMER messages. +//----------------------------------------------------------------------------- +// There is one TimerManager per thread, owned by the MessageLoop. Timers can +// either be fired by the MessageLoop from within its run loop or via a system +// timer event that the MesssageLoop constructs. The advantage of the former +// is that we can make timers fire significantly faster than the granularity +// provided by the system. The advantage of a system timer is that modal +// message loops which don't run our MessageLoop code will still be able to +// process system timer events. // -// Note: TimerManager is not thread safe. You cannot set timers -// onto a thread other than your own. +// NOTE: TimerManager is not thread safe. You cannot set timers onto a thread +// other than your own. class TimerManager { public: - TimerManager(); + explicit TimerManager(MessageLoop* message_loop); ~TimerManager(); // Create and start a new timer. |task| is owned by the caller, as is the // timer object that is returned. Timer* StartTimer(int delay, Task* task, bool repeating); - // Flag indicating whether the timer manager should use the OS - // timers or not. Default is true. MessageLoops which are not reliably - // called due to nested windows message loops should set this to - // true. - bool use_native_timers() { return use_native_timers_; } - void set_use_native_timers(bool value) { use_native_timers_ = value; } - // Starts a timer. This is a no-op if the timer is already started. void StartTimer(Timer* timer); @@ -190,15 +181,6 @@ class TimerManager { // pqueue) needs to be fired. Returns -1 if no timers are pending. int GetCurrentDelay(); - // A handler for WM_TIMER messages. - // If a task is not running currently, it runs some timer tasks (if there are - // some ready to fire), otherwise it just updates the WM_TIMER to be called - // again (hopefully when it is allowed to run a task). - int MessageWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); - - // Return cached copy of MessageLoop::current(). - MessageLoop* message_loop(); - #ifdef UNIT_TEST // For testing only, used to simulate broken early-firing WM_TIMER // notifications by setting arbitrarily small delays in SetTimer. @@ -207,35 +189,33 @@ class TimerManager { } #endif // UNIT_TEST + bool use_broken_delay() const { + return use_broken_delay_; + } + protected: // Peek at the timer which will fire soonest. Timer* PeekTopTimer(); private: - // Update our Windows WM_TIMER to match our most immediately pending timer. - void UpdateWindowsWmTimer(); - -#ifdef OS_WIN - // Retrieve the Message Window that handles WM_TIMER messages from the - // system. - HWND GetMessageHWND(); + void DidChangeNextTimer(); - HWND message_hwnd_; -#endif // OS_WIN + // A cached value that indicates the time when we think the next timer is to + // fire. We use this to determine if we should call DidChangeNextTimerExpiry + // on the MessageLoop. + Time next_timer_expiry_; TimerPQueue timers_; bool use_broken_delay_; - // Flag to enable/disable use of native timers. - bool use_native_timers_; - // A lazily cached copy of MessageLoop::current. MessageLoop* message_loop_; - DISALLOW_EVIL_CONSTRUCTORS(TimerManager); + DISALLOW_COPY_AND_ASSIGN(TimerManager); }; +//----------------------------------------------------------------------------- // A simple wrapper for the Timer / TimerManager API. This is a helper class. // Use OneShotTimer or RepeatingTimer instead. class SimpleTimer { @@ -281,9 +261,10 @@ class SimpleTimer { // we are deallocated. Defaults to true. bool owns_task_; - DISALLOW_EVIL_CONSTRUCTORS(SimpleTimer); + DISALLOW_COPY_AND_ASSIGN(SimpleTimer); }; +//----------------------------------------------------------------------------- // A simple, one-shot timer. The task is run after the specified delay once // the Start method is called. The task is deleted when the timer object is // destroyed. @@ -298,9 +279,10 @@ class OneShotTimer : public SimpleTimer { : SimpleTimer(delay, task, false) { } private: - DISALLOW_EVIL_CONSTRUCTORS(OneShotTimer); + DISALLOW_COPY_AND_ASSIGN(OneShotTimer); }; +//----------------------------------------------------------------------------- // A simple, repeating timer. The task is run at the specified interval once // the Start method is called. The task is deleted when the timer object is // destroyed. @@ -315,7 +297,7 @@ class RepeatingTimer : public SimpleTimer { : SimpleTimer(interval, task, true) { } private: - DISALLOW_EVIL_CONSTRUCTORS(RepeatingTimer); + DISALLOW_COPY_AND_ASSIGN(RepeatingTimer); }; #endif // BASE_TIMER_H_ diff --git a/base/timer_unittest.cc b/base/timer_unittest.cc index 814836b..4c2593f 100644 --- a/base/timer_unittest.cc +++ b/base/timer_unittest.cc @@ -295,6 +295,9 @@ TEST(TimerTest, FifoOrder) { class MockTimerManager : public TimerManager { public: + MockTimerManager() : TimerManager(MessageLoop::current()) { + } + // Pops the most-recent to fire timer and returns its timer id. // Returns -1 if there are no timers in the list. int pop() { diff --git a/base/tracked_objects.cc b/base/tracked_objects.cc index 8694bfd..f94d77b 100644 --- a/base/tracked_objects.cc +++ b/base/tracked_objects.cc @@ -29,6 +29,8 @@ #include "base/tracked_objects.h" +#include <math.h> + #include "base/string_util.h" namespace tracked_objects { |