blob: 3a90f28faeb484773f3157e8beb8bcf443491711 [file] [log] [blame] [edit]
#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