diff options
Diffstat (limited to 'base/timer.cc')
-rw-r--r-- | base/timer.cc | 181 |
1 files changed, 167 insertions, 14 deletions
diff --git a/base/timer.cc b/base/timer.cc index 5c2aa21..e51660c 100644 --- a/base/timer.cc +++ b/base/timer.cc @@ -4,28 +4,181 @@ #include "base/timer.h" -#include "base/bind.h" -#include "base/bind_helpers.h" +#include "base/logging.h" #include "base/message_loop.h" namespace base { -void BaseTimer_Helper::OrphanDelayedTask() { - if (delayed_task_) { - delayed_task_->timer_ = NULL; - delayed_task_ = NULL; +// BaseTimerTaskInternal is a simple delegate for scheduling a callback to +// Timer in the MessageLoop. It also handles the following edge +// cases: +// - deleted by MessageLoop. +// - abandoned (orphaned) by Timer. +class BaseTimerTaskInternal { + public: + BaseTimerTaskInternal(Timer* timer) + : timer_(timer) { } + + ~BaseTimerTaskInternal() { + // This task may be getting cleared because the MessageLoop 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 MessageLoop, 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 BaseTimer_Helper::InitiateDelayedTask(TimerTask* timer_task) { - OrphanDelayedTask(); +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); + MessageLoop::current()->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(); + // MessageLoop 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(); - delayed_task_ = timer_task; - delayed_task_->timer_ = this; - MessageLoop::current()->PostDelayedTask( - timer_task->posted_from_, - base::Bind(&TimerTask::Run, base::Owned(timer_task)), - timer_task->delay_); + // No more member accesses here: *this could be deleted at this point. } } // namespace base |