diff options
author | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-27 18:04:56 +0000 |
---|---|---|
committer | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-27 18:04:56 +0000 |
commit | 89bf27e34e49c271aa958c1795d5bccad0e63f88 (patch) | |
tree | f971f9282a07dbb991a079daade728830df3da2d /base/timer | |
parent | aa0ec7b63637a5917ce4be9a0e6cceb26b7a3298 (diff) | |
download | chromium_src-89bf27e34e49c271aa958c1795d5bccad0e63f88.zip chromium_src-89bf27e34e49c271aa958c1795d5bccad0e63f88.tar.gz chromium_src-89bf27e34e49c271aa958c1795d5bccad0e63f88.tar.bz2 |
Move timing files into base/time and base/timer, install forwarding headers.
BUG=254986
TEST=none
TBR=brettw@chromium.org
Review URL: https://codereview.chromium.org/18063004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208951 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/timer')
-rw-r--r-- | base/timer/hi_res_timer_manager.h | 38 | ||||
-rw-r--r-- | base/timer/hi_res_timer_manager_posix.cc | 24 | ||||
-rw-r--r-- | base/timer/hi_res_timer_manager_unittest.cc | 57 | ||||
-rw-r--r-- | base/timer/hi_res_timer_manager_win.cc | 35 | ||||
-rw-r--r-- | base/timer/timer.cc | 186 | ||||
-rw-r--r-- | base/timer/timer.h | 246 | ||||
-rw-r--r-- | base/timer/timer_unittest.cc | 489 |
7 files changed, 1075 insertions, 0 deletions
diff --git a/base/timer/hi_res_timer_manager.h b/base/timer/hi_res_timer_manager.h new file mode 100644 index 0000000..7fcdb0d --- /dev/null +++ b/base/timer/hi_res_timer_manager.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TIMER_HI_RES_TIMER_MANAGER_H_ +#define BASE_TIMER_HI_RES_TIMER_MANAGER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/power_monitor/power_observer.h" + +namespace base { + +// Ensures that the Windows high resolution timer is only used +// when not running on battery power. +class BASE_EXPORT HighResolutionTimerManager : public base::PowerObserver { + public: + HighResolutionTimerManager(); + virtual ~HighResolutionTimerManager(); + + // base::PowerObserver method. + virtual void OnPowerStateChange(bool on_battery_power) OVERRIDE; + + // Returns true if the hi resolution clock could be used right now. + bool hi_res_clock_available() const { return hi_res_clock_available_; } + + private: + // Enable or disable the faster multimedia timer. + void UseHiResClock(bool use); + + bool hi_res_clock_available_; + + DISALLOW_COPY_AND_ASSIGN(HighResolutionTimerManager); +}; + +} // namespace base + +#endif // BASE_TIMER_HI_RES_TIMER_MANAGER_H_ diff --git a/base/timer/hi_res_timer_manager_posix.cc b/base/timer/hi_res_timer_manager_posix.cc new file mode 100644 index 0000000..d2f152c --- /dev/null +++ b/base/timer/hi_res_timer_manager_posix.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/timer/hi_res_timer_manager.h" + +// On POSIX we don't need to do anything special with the system timer. + +namespace base { + +HighResolutionTimerManager::HighResolutionTimerManager() + : hi_res_clock_available_(false) { +} + +HighResolutionTimerManager::~HighResolutionTimerManager() { +} + +void HighResolutionTimerManager::OnPowerStateChange(bool on_battery_power) { +} + +void HighResolutionTimerManager::UseHiResClock(bool use) { +} + +} // namespace base diff --git a/base/timer/hi_res_timer_manager_unittest.cc b/base/timer/hi_res_timer_manager_unittest.cc new file mode 100644 index 0000000..0017859 --- /dev/null +++ b/base/timer/hi_res_timer_manager_unittest.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/timer/hi_res_timer_manager.h" + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/power_monitor/power_monitor.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +#if defined(OS_WIN) +// http://crbug.com/114048 +TEST(HiResTimerManagerTest, DISABLED_ToggleOnOff) { + base::MessageLoop loop; + scoped_ptr<base::PowerMonitor> power_monitor(new base::PowerMonitor()); + HighResolutionTimerManager manager; + + // At this point, we don't know if the high resolution timers are on or off, + // it depends on what system the tests are running on (for example, if this + // test is running on a laptop/battery, then the PowerMonitor would have + // already set the PowerState to battery power; but if we're running on a + // desktop, then the PowerState will be non-battery power). Simulate a power + // level change to get to a deterministic state. + manager.OnPowerStateChange(/* on_battery */ false); + + // Loop a few times to test power toggling. + for (int loop = 2; loop >= 0; --loop) { + // The manager has the high resolution clock enabled now. + EXPECT_TRUE(manager.hi_res_clock_available()); + // But the Time class has it off, because it hasn't been activated. + EXPECT_FALSE(base::Time::IsHighResolutionTimerInUse()); + + // Activate the high resolution timer. + base::Time::ActivateHighResolutionTimer(true); + EXPECT_TRUE(base::Time::IsHighResolutionTimerInUse()); + + // Simulate a on-battery power event. + manager.OnPowerStateChange(/* on_battery */ true); + EXPECT_FALSE(manager.hi_res_clock_available()); + EXPECT_FALSE(base::Time::IsHighResolutionTimerInUse()); + + // Simulate a off-battery power event. + manager.OnPowerStateChange(/* on_battery */ false); + EXPECT_TRUE(manager.hi_res_clock_available()); + EXPECT_TRUE(base::Time::IsHighResolutionTimerInUse()); + + // De-activate the high resolution timer. + base::Time::ActivateHighResolutionTimer(false); + } +} +#endif // defined(OS_WIN) + +} // namespace base diff --git a/base/timer/hi_res_timer_manager_win.cc b/base/timer/hi_res_timer_manager_win.cc new file mode 100644 index 0000000..3647714 --- /dev/null +++ b/base/timer/hi_res_timer_manager_win.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/timer/hi_res_timer_manager.h" + +#include "base/power_monitor/power_monitor.h" +#include "base/time/time.h" + +namespace base { + +HighResolutionTimerManager::HighResolutionTimerManager() + : hi_res_clock_available_(false) { + base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); + power_monitor->AddObserver(this); + UseHiResClock(!power_monitor->BatteryPower()); +} + +HighResolutionTimerManager::~HighResolutionTimerManager() { + base::PowerMonitor::Get()->RemoveObserver(this); + UseHiResClock(false); +} + +void HighResolutionTimerManager::OnPowerStateChange(bool on_battery_power) { + UseHiResClock(!on_battery_power); +} + +void HighResolutionTimerManager::UseHiResClock(bool use) { + if (use == hi_res_clock_available_) + return; + hi_res_clock_available_ = use; + base::Time::EnableHighResolutionTimer(use); +} + +} // namespace base diff --git a/base/timer/timer.cc b/base/timer/timer.cc new file mode 100644 index 0000000..c7dcce0 --- /dev/null +++ b/base/timer/timer.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/timer.h" + +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/platform_thread.h" + +namespace base { + +// BaseTimerTaskInternal is a simple delegate for scheduling a callback to +// Timer in the thread's default task runner. It also handles the following +// edge cases: +// - deleted by the task runner. +// - abandoned (orphaned) by Timer. +class BaseTimerTaskInternal { + public: + explicit BaseTimerTaskInternal(Timer* timer) + : timer_(timer) { + } + + ~BaseTimerTaskInternal() { + // This task may be getting cleared because the task runner has been + // destructed. If so, don't leave Timer with a dangling pointer + // to this. + if (timer_) + timer_->StopAndAbandon(); + } + + void Run() { + // timer_ is NULL if we were abandoned. + if (!timer_) + return; + + // *this will be deleted by the task runner, so Timer needs to + // forget us: + timer_->scheduled_task_ = NULL; + + // Although Timer should not call back into *this, let's clear + // the timer_ member first to be pedantic. + Timer* timer = timer_; + timer_ = NULL; + timer->RunScheduledTask(); + } + + // The task remains in the MessageLoop queue, but nothing will happen when it + // runs. + void Abandon() { + timer_ = NULL; + } + + private: + Timer* timer_; +}; + +Timer::Timer(bool retain_user_task, bool is_repeating) + : scheduled_task_(NULL), + thread_id_(0), + is_repeating_(is_repeating), + retain_user_task_(retain_user_task), + is_running_(false) { +} + +Timer::Timer(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task, + bool is_repeating) + : scheduled_task_(NULL), + posted_from_(posted_from), + delay_(delay), + user_task_(user_task), + thread_id_(0), + is_repeating_(is_repeating), + retain_user_task_(true), + is_running_(false) { +} + +Timer::~Timer() { + StopAndAbandon(); +} + +void Timer::Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task) { + SetTaskInfo(posted_from, delay, user_task); + Reset(); +} + +void Timer::Stop() { + is_running_ = false; + if (!retain_user_task_) + user_task_.Reset(); +} + +void Timer::Reset() { + DCHECK(!user_task_.is_null()); + + // If there's no pending task, start one up and return. + if (!scheduled_task_) { + PostNewScheduledTask(delay_); + return; + } + + // Set the new desired_run_time_. + desired_run_time_ = TimeTicks::Now() + delay_; + + // We can use the existing scheduled task if it arrives before the new + // desired_run_time_. + if (desired_run_time_ > scheduled_run_time_) { + is_running_ = true; + return; + } + + // We can't reuse the scheduled_task_, so abandon it and post a new one. + AbandonScheduledTask(); + PostNewScheduledTask(delay_); +} + +void Timer::SetTaskInfo(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task) { + posted_from_ = posted_from; + delay_ = delay; + user_task_ = user_task; +} + +void Timer::PostNewScheduledTask(TimeDelta delay) { + DCHECK(scheduled_task_ == NULL); + is_running_ = true; + scheduled_task_ = new BaseTimerTaskInternal(this); + ThreadTaskRunnerHandle::Get()->PostDelayedTask(posted_from_, + base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)), + delay); + scheduled_run_time_ = desired_run_time_ = TimeTicks::Now() + delay; + // Remember the thread ID that posts the first task -- this will be verified + // later when the task is abandoned to detect misuse from multiple threads. + if (!thread_id_) + thread_id_ = static_cast<int>(PlatformThread::CurrentId()); +} + +void Timer::AbandonScheduledTask() { + DCHECK(thread_id_ == 0 || + thread_id_ == static_cast<int>(PlatformThread::CurrentId())); + if (scheduled_task_) { + scheduled_task_->Abandon(); + scheduled_task_ = NULL; + } +} + +void Timer::RunScheduledTask() { + // Task may have been disabled. + if (!is_running_) + return; + + // First check if we need to delay the task because of a new target time. + if (desired_run_time_ > scheduled_run_time_) { + // TimeTicks::Now() can be expensive, so only call it if we know the user + // has changed the desired_run_time_. + TimeTicks now = TimeTicks::Now(); + // Task runner may have called us late anyway, so only post a continuation + // task if the desired_run_time_ is in the future. + if (desired_run_time_ > now) { + // Post a new task to span the remaining time. + PostNewScheduledTask(desired_run_time_ - now); + return; + } + } + + // Make a local copy of the task to run. The Stop method will reset the + // user_task_ member if retain_user_task_ is false. + base::Closure task = user_task_; + + if (is_repeating_) + PostNewScheduledTask(delay_); + else + Stop(); + + task.Run(); + + // No more member accesses here: *this could be deleted at this point. +} + +} // namespace base diff --git a/base/timer/timer.h b/base/timer/timer.h new file mode 100644 index 0000000..23b1d3c --- /dev/null +++ b/base/timer/timer.h @@ -0,0 +1,246 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// OneShotTimer and RepeatingTimer provide a simple timer API. As the names +// suggest, OneShotTimer calls you back once after a time delay expires. +// RepeatingTimer on the other hand calls you back periodically with the +// prescribed time interval. +// +// OneShotTimer and RepeatingTimer both cancel the timer when they go out of +// scope, which makes it easy to ensure that you do not get called when your +// object has gone out of scope. Just instantiate a OneShotTimer or +// RepeatingTimer as a member variable of the class for which you wish to +// receive timer events. +// +// Sample RepeatingTimer usage: +// +// class MyClass { +// public: +// void StartDoingStuff() { +// timer_.Start(FROM_HERE, TimeDelta::FromSeconds(1), +// this, &MyClass::DoStuff); +// } +// void StopDoingStuff() { +// timer_.Stop(); +// } +// private: +// void DoStuff() { +// // This method is called every second to do stuff. +// ... +// } +// base::RepeatingTimer<MyClass> timer_; +// }; +// +// Both OneShotTimer and RepeatingTimer also support a Reset method, which +// allows you to easily defer the timer event until the timer delay passes once +// again. So, in the above example, if 0.5 seconds have already passed, +// calling Reset on timer_ would postpone DoStuff by another 1 second. In +// other words, Reset is shorthand for calling Stop and then Start again with +// the same arguments. +// +// NOTE: These APIs are not thread safe. Always call from the same thread. + +#ifndef BASE_TIMER_TIMER_H_ +#define BASE_TIMER_TIMER_H_ + +// IMPORTANT: If you change timer code, make sure that all tests (including +// disabled ones) from timer_unittests.cc pass locally. Some are disabled +// because they're flaky on the buildbot, but when you run them locally you +// should be able to tell the difference. + +#include "base/base_export.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/time/time.h" + +namespace base { + +class BaseTimerTaskInternal; +class MessageLoop; + +//----------------------------------------------------------------------------- +// This class wraps MessageLoop::PostDelayedTask to manage delayed and repeating +// tasks. It must be destructed on the same thread that starts tasks. There are +// DCHECKs in place to verify this. +// +class BASE_EXPORT Timer { + public: + // Construct a timer in repeating or one-shot mode. Start or SetTaskInfo must + // be called later to set task info. |retain_user_task| determines whether the + // user_task is retained or reset when it runs or stops. + Timer(bool retain_user_task, bool is_repeating); + + // Construct a timer with retained task info. + Timer(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task, + bool is_repeating); + + virtual ~Timer(); + + // Returns true if the timer is running (i.e., not stopped). + bool IsRunning() const { + return is_running_; + } + + // Returns the current delay for this timer. + TimeDelta GetCurrentDelay() const { + return delay_; + } + + // Start the timer to run at the given |delay| from now. If the timer is + // already running, it will be replaced to call the given |user_task|. + void Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task); + + // Call this method to stop and cancel the timer. It is a no-op if the timer + // is not running. + void Stop(); + + // Call this method to reset the timer delay. The user_task_ must be set. If + // the timer is not running, this will start it by posting a task. + void Reset(); + + const base::Closure& user_task() const { return user_task_; } + const TimeTicks& desired_run_time() const { return desired_run_time_; } + + protected: + // Used to initiate a new delayed task. This has the side-effect of disabling + // scheduled_task_ if it is non-null. + void SetTaskInfo(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task); + + private: + friend class BaseTimerTaskInternal; + + // Allocates a new scheduled_task_ and posts it on the current MessageLoop + // with the given |delay|. scheduled_task_ must be NULL. scheduled_run_time_ + // and desired_run_time_ are reset to Now() + delay. + void PostNewScheduledTask(TimeDelta delay); + + // Disable scheduled_task_ and abandon it so that it no longer refers back to + // this object. + void AbandonScheduledTask(); + + // Called by BaseTimerTaskInternal when the MessageLoop runs it. + void RunScheduledTask(); + + // Stop running task (if any) and abandon scheduled task (if any). + void StopAndAbandon() { + Stop(); + AbandonScheduledTask(); + } + + // When non-NULL, the scheduled_task_ is waiting in the MessageLoop to call + // RunScheduledTask() at scheduled_run_time_. + BaseTimerTaskInternal* scheduled_task_; + + // Location in user code. + tracked_objects::Location posted_from_; + // Delay requested by user. + TimeDelta delay_; + // user_task_ is what the user wants to be run at desired_run_time_. + base::Closure user_task_; + + // The estimated time that the MessageLoop will run the scheduled_task_ that + // will call RunScheduledTask(). + TimeTicks scheduled_run_time_; + + // The desired run time of user_task_. The user may update this at any time, + // even if their previous request has not run yet. If desired_run_time_ is + // greater than scheduled_run_time_, a continuation task will be posted to + // wait for the remaining time. This allows us to reuse the pending task so as + // not to flood the MessageLoop with orphaned tasks when the user code + // excessively Stops and Starts the timer. + TimeTicks desired_run_time_; + + // Thread ID of current MessageLoop for verifying single-threaded usage. + int thread_id_; + + // Repeating timers automatically post the task again before calling the task + // callback. + const bool is_repeating_; + + // If true, hold on to the user_task_ closure object for reuse. + const bool retain_user_task_; + + // If true, user_task_ is scheduled to run sometime in the future. + bool is_running_; + + DISALLOW_COPY_AND_ASSIGN(Timer); +}; + +//----------------------------------------------------------------------------- +// This class is an implementation detail of OneShotTimer and RepeatingTimer. +// Please do not use this class directly. +template <class Receiver, bool kIsRepeating> +class BaseTimerMethodPointer : public Timer { + public: + typedef void (Receiver::*ReceiverMethod)(); + + // This is here to work around the fact that Timer::Start is "hidden" by the + // Start definition below, rather than being overloaded. + // TODO(tim): We should remove uses of BaseTimerMethodPointer::Start below + // and convert callers to use the base::Closure version in Timer::Start, + // see bug 148832. + using Timer::Start; + + BaseTimerMethodPointer() : Timer(kIsRepeating, kIsRepeating) {} + + // Start the timer to run at the given |delay| from now. If the timer is + // already running, it will be replaced to call a task formed from + // |reviewer->*method|. + void Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + Receiver* receiver, + ReceiverMethod method) { + Timer::Start(posted_from, delay, + base::Bind(method, base::Unretained(receiver))); + } +}; + +//----------------------------------------------------------------------------- +// A simple, one-shot timer. See usage notes at the top of the file. +template <class Receiver> +class OneShotTimer : public BaseTimerMethodPointer<Receiver, false> {}; + +//----------------------------------------------------------------------------- +// A simple, repeating timer. See usage notes at the top of the file. +template <class Receiver> +class RepeatingTimer : public BaseTimerMethodPointer<Receiver, true> {}; + +//----------------------------------------------------------------------------- +// A Delay timer is like The Button from Lost. Once started, you have to keep +// calling Reset otherwise it will call the given method in the MessageLoop +// thread. +// +// Once created, it is inactive until Reset is called. Once |delay| seconds have +// passed since the last call to Reset, the callback is made. Once the callback +// has been made, it's inactive until Reset is called again. +// +// If destroyed, the timeout is canceled and will not occur even if already +// inflight. +template <class Receiver> +class DelayTimer : protected Timer { + public: + typedef void (Receiver::*ReceiverMethod)(); + + DelayTimer(const tracked_objects::Location& posted_from, + TimeDelta delay, + Receiver* receiver, + ReceiverMethod method) + : Timer(posted_from, delay, + base::Bind(method, base::Unretained(receiver)), + false) {} + + void Reset() { Timer::Reset(); } +}; + +} // namespace base + +#endif // BASE_TIMER_TIMER_H_ diff --git a/base/timer/timer_unittest.cc b/base/timer/timer_unittest.cc new file mode 100644 index 0000000..9fe21a4 --- /dev/null +++ b/base/timer/timer_unittest.cc @@ -0,0 +1,489 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/timer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::TimeDelta; + +namespace { + +// The message loops on which each timer should be tested. +const base::MessageLoop::Type testing_message_loops[] = { + base::MessageLoop::TYPE_DEFAULT, + base::MessageLoop::TYPE_IO, +#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop. + base::MessageLoop::TYPE_UI, +#endif +}; + +const int kNumTestingMessageLoops = arraysize(testing_message_loops); + +class OneShotTimerTester { + public: + explicit OneShotTimerTester(bool* did_run, unsigned milliseconds = 10) + : did_run_(did_run), + delay_ms_(milliseconds) { + } + void Start() { + timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(delay_ms_), this, + &OneShotTimerTester::Run); + } + private: + void Run() { + *did_run_ = true; + base::MessageLoop::current()->QuitWhenIdle(); + } + bool* did_run_; + base::OneShotTimer<OneShotTimerTester> timer_; + const unsigned delay_ms_; +}; + +class OneShotSelfDeletingTimerTester { + public: + explicit OneShotSelfDeletingTimerTester(bool* did_run) : + did_run_(did_run), + timer_(new base::OneShotTimer<OneShotSelfDeletingTimerTester>()) { + } + void Start() { + timer_->Start(FROM_HERE, TimeDelta::FromMilliseconds(10), this, + &OneShotSelfDeletingTimerTester::Run); + } + private: + void Run() { + *did_run_ = true; + timer_.reset(); + base::MessageLoop::current()->QuitWhenIdle(); + } + bool* did_run_; + scoped_ptr<base::OneShotTimer<OneShotSelfDeletingTimerTester> > timer_; +}; + +class RepeatingTimerTester { + public: + explicit RepeatingTimerTester(bool* did_run) + : did_run_(did_run), counter_(10) { + } + + void Start() { + timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), this, + &RepeatingTimerTester::Run); + } + private: + void Run() { + if (--counter_ == 0) { + *did_run_ = true; + base::MessageLoop::current()->QuitWhenIdle(); + } + } + bool* did_run_; + int counter_; + base::RepeatingTimer<RepeatingTimerTester> timer_; +}; + +void RunTest_OneShotTimer(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + bool did_run = false; + OneShotTimerTester f(&did_run); + f.Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_TRUE(did_run); +} + +void RunTest_OneShotTimer_Cancel(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + bool did_run_a = false; + OneShotTimerTester* a = new OneShotTimerTester(&did_run_a); + + // This should run before the timer expires. + base::MessageLoop::current()->DeleteSoon(FROM_HERE, a); + + // Now start the timer. + a->Start(); + + bool did_run_b = false; + OneShotTimerTester b(&did_run_b); + b.Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_FALSE(did_run_a); + EXPECT_TRUE(did_run_b); +} + +void RunTest_OneShotSelfDeletingTimer( + base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + bool did_run = false; + OneShotSelfDeletingTimerTester f(&did_run); + f.Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_TRUE(did_run); +} + +void RunTest_RepeatingTimer(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + bool did_run = false; + RepeatingTimerTester f(&did_run); + f.Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_TRUE(did_run); +} + +void RunTest_RepeatingTimer_Cancel(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + bool did_run_a = false; + RepeatingTimerTester* a = new RepeatingTimerTester(&did_run_a); + + // This should run before the timer expires. + base::MessageLoop::current()->DeleteSoon(FROM_HERE, a); + + // Now start the timer. + a->Start(); + + bool did_run_b = false; + RepeatingTimerTester b(&did_run_b); + b.Start(); + + base::MessageLoop::current()->Run(); + + EXPECT_FALSE(did_run_a); + EXPECT_TRUE(did_run_b); +} + +class DelayTimerTarget { + public: + DelayTimerTarget() + : signaled_(false) { + } + + bool signaled() const { return signaled_; } + + void Signal() { + ASSERT_FALSE(signaled_); + signaled_ = true; + } + + private: + bool signaled_; +}; + +void RunTest_DelayTimer_NoCall(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + // If Delay is never called, the timer shouldn't go off. + DelayTimerTarget target; + base::DelayTimer<DelayTimerTarget> timer(FROM_HERE, + TimeDelta::FromMilliseconds(1), &target, &DelayTimerTarget::Signal); + + bool did_run = false; + OneShotTimerTester tester(&did_run); + tester.Start(); + base::MessageLoop::current()->Run(); + + ASSERT_FALSE(target.signaled()); +} + +void RunTest_DelayTimer_OneCall(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + DelayTimerTarget target; + base::DelayTimer<DelayTimerTarget> timer(FROM_HERE, + TimeDelta::FromMilliseconds(1), &target, &DelayTimerTarget::Signal); + timer.Reset(); + + bool did_run = false; + OneShotTimerTester tester(&did_run, 100 /* milliseconds */); + tester.Start(); + base::MessageLoop::current()->Run(); + + ASSERT_TRUE(target.signaled()); +} + +struct ResetHelper { + ResetHelper(base::DelayTimer<DelayTimerTarget>* timer, + DelayTimerTarget* target) + : timer_(timer), + target_(target) { + } + + void Reset() { + ASSERT_FALSE(target_->signaled()); + timer_->Reset(); + } + + private: + base::DelayTimer<DelayTimerTarget> *const timer_; + DelayTimerTarget *const target_; +}; + +void RunTest_DelayTimer_Reset(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + // If Delay is never called, the timer shouldn't go off. + DelayTimerTarget target; + base::DelayTimer<DelayTimerTarget> timer(FROM_HERE, + TimeDelta::FromMilliseconds(50), &target, &DelayTimerTarget::Signal); + timer.Reset(); + + ResetHelper reset_helper(&timer, &target); + + base::OneShotTimer<ResetHelper> timers[20]; + for (size_t i = 0; i < arraysize(timers); ++i) { + timers[i].Start(FROM_HERE, TimeDelta::FromMilliseconds(i * 10), + &reset_helper, &ResetHelper::Reset); + } + + bool did_run = false; + OneShotTimerTester tester(&did_run, 300); + tester.Start(); + base::MessageLoop::current()->Run(); + + ASSERT_TRUE(target.signaled()); +} + +class DelayTimerFatalTarget { + public: + void Signal() { + ASSERT_TRUE(false); + } +}; + + +void RunTest_DelayTimer_Deleted(base::MessageLoop::Type message_loop_type) { + base::MessageLoop loop(message_loop_type); + + DelayTimerFatalTarget target; + + { + base::DelayTimer<DelayTimerFatalTarget> timer( + FROM_HERE, TimeDelta::FromMilliseconds(50), &target, + &DelayTimerFatalTarget::Signal); + timer.Reset(); + } + + // When the timer is deleted, the DelayTimerFatalTarget should never be + // called. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); +} + +} // namespace + +//----------------------------------------------------------------------------- +// Each test is run against each type of MessageLoop. That way we are sure +// that timers work properly in all configurations. + +TEST(TimerTest, OneShotTimer) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OneShotTimer(testing_message_loops[i]); + } +} + +TEST(TimerTest, OneShotTimer_Cancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OneShotTimer_Cancel(testing_message_loops[i]); + } +} + +// If underline timer does not handle properly, we will crash or fail +// in full page heap environment. +TEST(TimerTest, OneShotSelfDeletingTimer) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_OneShotSelfDeletingTimer(testing_message_loops[i]); + } +} + +TEST(TimerTest, RepeatingTimer) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_RepeatingTimer(testing_message_loops[i]); + } +} + +TEST(TimerTest, RepeatingTimer_Cancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_RepeatingTimer_Cancel(testing_message_loops[i]); + } +} + +TEST(TimerTest, DelayTimer_NoCall) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_NoCall(testing_message_loops[i]); + } +} + +TEST(TimerTest, DelayTimer_OneCall) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_OneCall(testing_message_loops[i]); + } +} + +// It's flaky on the buildbot, http://crbug.com/25038. +TEST(TimerTest, DISABLED_DelayTimer_Reset) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_Reset(testing_message_loops[i]); + } +} + +TEST(TimerTest, DelayTimer_Deleted) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_DelayTimer_Deleted(testing_message_loops[i]); + } +} + +TEST(TimerTest, MessageLoopShutdown) { + // This test is designed to verify that shutdown of the + // message loop does not cause crashes if there were pending + // timers not yet fired. It may only trigger exceptions + // if debug heap checking is enabled. + bool did_run = false; + { + OneShotTimerTester a(&did_run); + OneShotTimerTester b(&did_run); + OneShotTimerTester c(&did_run); + OneShotTimerTester d(&did_run); + { + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + a.Start(); + b.Start(); + } // MessageLoop destructs by falling out of scope. + } // OneShotTimers destruct. SHOULD NOT CRASH, of course. + + EXPECT_FALSE(did_run); +} + +void TimerTestCallback() { +} + +TEST(TimerTest, NonRepeatIsRunning) { + { + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + base::Timer timer(false, false); + EXPECT_FALSE(timer.IsRunning()); + timer.Start(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback)); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + EXPECT_TRUE(timer.user_task().is_null()); + } + + { + base::Timer timer(true, false); + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + EXPECT_FALSE(timer.IsRunning()); + timer.Start(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback)); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + ASSERT_FALSE(timer.user_task().is_null()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); + } +} + +TEST(TimerTest, NonRepeatMessageLoopDeath) { + base::Timer timer(false, false); + { + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + EXPECT_FALSE(timer.IsRunning()); + timer.Start(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback)); + EXPECT_TRUE(timer.IsRunning()); + } + EXPECT_FALSE(timer.IsRunning()); + EXPECT_TRUE(timer.user_task().is_null()); +} + +TEST(TimerTest, RetainRepeatIsRunning) { + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + base::Timer timer(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback), true); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); +} + +TEST(TimerTest, RetainNonRepeatIsRunning) { + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + base::Timer timer(FROM_HERE, TimeDelta::FromDays(1), + base::Bind(&TimerTestCallback), false); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + timer.Reset(); + EXPECT_TRUE(timer.IsRunning()); +} + +namespace { + +bool g_callback_happened1 = false; +bool g_callback_happened2 = false; + +void ClearAllCallbackHappened() { + g_callback_happened1 = false; + g_callback_happened2 = false; +} + +void SetCallbackHappened1() { + g_callback_happened1 = true; + base::MessageLoop::current()->QuitWhenIdle(); +} + +void SetCallbackHappened2() { + g_callback_happened2 = true; + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST(TimerTest, ContinuationStopStart) { + { + ClearAllCallbackHappened(); + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + base::Timer timer(false, false); + timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), + base::Bind(&SetCallbackHappened1)); + timer.Stop(); + timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(40), + base::Bind(&SetCallbackHappened2)); + base::MessageLoop::current()->Run(); + EXPECT_FALSE(g_callback_happened1); + EXPECT_TRUE(g_callback_happened2); + } +} + +TEST(TimerTest, ContinuationReset) { + { + ClearAllCallbackHappened(); + base::MessageLoop loop(base::MessageLoop::TYPE_DEFAULT); + base::Timer timer(false, false); + timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), + base::Bind(&SetCallbackHappened1)); + timer.Reset(); + // Since Reset happened before task ran, the user_task must not be cleared: + ASSERT_FALSE(timer.user_task().is_null()); + base::MessageLoop::current()->Run(); + EXPECT_TRUE(g_callback_happened1); + } +} + +} // namespace |