| #include "utils/timer.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/synchronization/mutex.h" |
| #include "absl/time/time.h" |
| #include "utils/std_thread.h" |
| |
| namespace milotic { |
| |
| std::string TimerStateToString(TimerState state) { |
| switch (state) { |
| case kIdle: |
| return "Idle"; |
| case kTicking: |
| return "Ticking"; |
| case kRestart: |
| return "Restart"; |
| case kCallbackRunning: |
| return "CallbackRunning"; |
| case kCallbackDone: |
| return "CallbackDone"; |
| case kEnd: |
| return "End"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| absl::Status Timer::Start(absl::Duration duration, Ticker ticker) { |
| LOG(INFO) << timer_name_ << " Start: " << duration; |
| |
| absl::MutexLock guard(timer_mutex_); |
| |
| if (state_ != kIdle) { |
| LOG(ERROR) << timer_name_ << " Timer already started/ended, state: " |
| << TimerStateToString(state_); |
| return absl::FailedPreconditionError("Timer already started/ended"); |
| } |
| |
| duration_ = duration; |
| if (ticker == nullptr) { |
| tick_thread_ = std::make_unique<StdThread>("TickThread", [this] { Tick(); }); |
| } else { |
| tick_thread_ = std::make_unique<StdThread>("TickThread", std::move(ticker)); |
| } |
| absl::Status status = tick_thread_->Start(); |
| if (!status.ok()) { |
| LOG(ERROR) << timer_name_ |
| << " Failed to start tick thread, status: " << status; |
| return status; |
| } |
| absl::Condition cond(this, &Timer::CheckTickingOrEnd); |
| timer_mutex_.Await(cond); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status Timer::Restart(absl::Duration duration) { |
| LOG(INFO) << timer_name_ << " Restart: " << duration; |
| |
| absl::MutexLock guard(timer_mutex_); |
| |
| // Callback already completed |
| if (state_ == kCallbackDone) { |
| LOG(ERROR) << timer_name_ << " Callback already done, state: " |
| << TimerStateToString(state_); |
| return absl::FailedPreconditionError("Callback already done"); |
| } |
| |
| // Timer ended |
| if (state_ == kEnd) { |
| LOG(ERROR) << timer_name_ |
| << " Timer already ended, state: " << TimerStateToString(state_); |
| return absl::FailedPreconditionError("Timer already ended"); |
| } |
| |
| // Start has not been called |
| if (state_ != kTicking) { |
| LOG(ERROR) << timer_name_ << " Timer has not started, state: " |
| << TimerStateToString(state_); |
| return absl::FailedPreconditionError("Timer has not started"); |
| } |
| |
| duration_ = duration; |
| state_ = kRestart; |
| absl::Condition cond(this, &Timer::CheckTickingOrEnd); |
| timer_mutex_.Await(cond); |
| |
| return absl::OkStatus(); |
| } |
| |
| // Ends the timer forcefully |
| void Timer::End() { |
| { |
| absl::MutexLock guard(timer_mutex_); |
| if (state_ == kEnd) { |
| LOG(INFO) << timer_name_ << " Timer already ended, state: " |
| << TimerStateToString(state_); |
| return; |
| } |
| LOG(INFO) << timer_name_ << ": End"; |
| state_ = kEnd; |
| } |
| |
| if (tick_thread_ != nullptr) { |
| tick_thread_->Join(); |
| tick_thread_ = nullptr; |
| } |
| } |
| |
| void Timer::Reset() { |
| LOG(INFO) << timer_name_ << ": Reset"; |
| End(); |
| |
| absl::MutexLock guard(timer_mutex_); |
| if (state_ == kIdle) { |
| LOG(INFO) << timer_name_ |
| << " Timer already idle, state: " << TimerStateToString(state_); |
| return; |
| } |
| callback_done_ = false; |
| state_ = kIdle; |
| } |
| |
| Timer::~Timer() { End(); } |
| |
| bool Timer::CheckRestartOrEnd() { |
| timer_mutex_.AssertReaderHeld(); |
| return state_ == kEnd || state_ == kRestart; |
| } |
| |
| bool Timer::CheckTickingOrEnd() { |
| timer_mutex_.AssertReaderHeld(); |
| return state_ == kTicking || state_ == kEnd; |
| } |
| |
| void Timer::CallbackWrapper() { |
| timer_mutex_.AssertHeld(); |
| if (state_ == kEnd) return; |
| |
| state_ = kCallbackRunning; |
| callback_(); |
| state_ = kCallbackDone; |
| callback_done_ = true; |
| } |
| |
| void Timer::Tick() { |
| LOG(INFO) << timer_name_ << ": Tick started"; |
| absl::MutexLock guard(timer_mutex_); |
| while (state_ != kEnd) { |
| state_ = kTicking; |
| absl::Condition cond(this, &Timer::CheckRestartOrEnd); |
| if (!clock_->AwaitWithDeadline(&timer_mutex_, cond, |
| clock_->TimeNow() + duration_)) { |
| // AwaitWithDeadline returns true is cond is true, false on timeout |
| // In case of timeout should trigger callback() and exit |
| CallbackWrapper(); |
| break; |
| } |
| // in Restart() case we'll continue with the next iteration |
| // in End() case the loop will break without callback |
| } |
| LOG(INFO) << timer_name_ << ": Tick ended"; |
| } |
| |
| } // namespace milotic |