diff --git a/CMakeLists.txt b/CMakeLists.txt index ac4359b..f25b588 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,8 @@ target_sources( src/system/thread.cc src/task_queue/pending_task_safety_flag.cc src/task_queue/task_queue_base.cc + src/timer/task_queue_timeout.cc + src/timer/timer.cc src/units/time_delta.cc src/units/timestamp.cc src/operations_chain.cc diff --git a/include/sled/task_queue/task_queue_base.h b/include/sled/task_queue/task_queue_base.h index 7036a50..7fced21 100644 --- a/include/sled/task_queue/task_queue_base.h +++ b/include/sled/task_queue/task_queue_base.h @@ -53,6 +53,22 @@ public: PostDelayedTaskImpl(std::move(task), delay, traits, location); } + void + PostDelayedTaskWithPrecision(DelayPrecision precision, + std::function &&task, + TimeDelta delay, + const Location &location = Location::Current()) + { + switch (precision) { + case DelayPrecision::kLow: + PostDelayedTask(std::move(task), delay, location); + break; + case DelayPrecision::kHigh: + PostDelayedHighPrecisionTask(std::move(task), delay, location); + break; + } + } + static TaskQueueBase *Current(); bool IsCurrent() const { return Current() == this; }; diff --git a/include/sled/timer/task_queue_timeout.h b/include/sled/timer/task_queue_timeout.h new file mode 100644 index 0000000..1051e9a --- /dev/null +++ b/include/sled/timer/task_queue_timeout.h @@ -0,0 +1,54 @@ +#pragma once +#ifndef SLED_TIMER_QUEUE_TIMEOUT_H +#define SLED_TIMER_QUEUE_TIMEOUT_H + +#include "sled/task_queue/task_queue_base.h" +#include "sled/timer/timeout.h" +#include +#include + +namespace sled { +typedef uint64_t TimeMs; + +class TaskQueueTimeoutFactory { +public: + TaskQueueTimeoutFactory( + sled::TaskQueueBase &task_queue, + std::function get_time, + std::function on_expired) + : task_queue_(task_queue), + get_time_(get_time), + on_expired_(on_expired) + {} + + std::unique_ptr + CreateTimeout(sled::TaskQueueBase::DelayPrecision precision = + sled::TaskQueueBase::DelayPrecision::kHigh) + { + return std::unique_ptr( + new TaskQueueTimeout(*this, precision)); + } + +private: + class TaskQueueTimeout : public Timeout { + public: + TaskQueueTimeout(TaskQueueTimeoutFactory &parent, + sled::TaskQueueBase::DelayPrecision precision); + ~TaskQueueTimeout() override; + void Start(DurationMs duration, TimeoutID timeout_id) override; + void Stop() override; + + private: + TaskQueueTimeoutFactory &parent_; + const sled::TaskQueueBase::DelayPrecision precision_; + TimeMs posted_task_expiration_ = std::numeric_limits::max(); + TimeMs timeout_expiration_ = std::numeric_limits::max(); + TimeoutID timeout_id_ = TimeoutID(0); + }; + + sled::TaskQueueBase &task_queue_; + const std::function get_time_; + const std::function on_expired_; +}; +}// namespace sled +#endif// SLED_TIMER_QUEUE_TIMEOUT_H diff --git a/include/sled/timer/timeout.h b/include/sled/timer/timeout.h new file mode 100644 index 0000000..d7f3f5e --- /dev/null +++ b/include/sled/timer/timeout.h @@ -0,0 +1,25 @@ +#pragma once +#ifndef SLED_TIMER_TIMEOUT_H +#define SLED_TIMER_TIMEOUT_H + +#include + +namespace sled { +typedef uint32_t DurationMs; +typedef uint64_t TimeoutID; + +class Timeout { +public: + virtual ~Timeout() = default; + virtual void Start(DurationMs duration, TimeoutID timeout_id) = 0; + virtual void Stop() = 0; + + virtual void Restart(DurationMs duration, TimeoutID timeout_id) + { + Stop(); + Start(duration, timeout_id); + } +}; +}// namespace sled + // +#endif// SLED_TIMER_TIMEOUT_H diff --git a/include/sled/timer/timer.h b/include/sled/timer/timer.h new file mode 100644 index 0000000..3c7183d --- /dev/null +++ b/include/sled/timer/timer.h @@ -0,0 +1,74 @@ +#pragma once +#ifndef SLED_TIMER_TIMER_H +#define SLED_TIMER_TIMER_H + +#include "timeout.h" +#include +#include +#include +#include +#include + +namespace sled { +typedef uint64_t TimerID; +typedef uint32_t TimerGeneration; + +class Timer { +public: + using OnExpired = std::function()>; + Timer(const Timer &) = delete; + Timer &operator=(const Timer &) = delete; + ~Timer(); + void Start(); + void Stop(); + + void set_duration(DurationMs duration) { duration_ = duration; } + + const DurationMs &duration() const { return duration_; } + + int expireation_count() const { return expiration_count_; } + + bool is_running() const { return is_running_; } + +private: + friend class TimerManager; + using UnregisterHandler = std::function; + Timer(TimerID id, + const std::string &name, + OnExpired on_expired, + UnregisterHandler unregister_handler, + std::unique_ptr timeout); + + const TimerID id_; + const std::string name_; + const OnExpired on_expired_; + const UnregisterHandler unregister_handler_; + std::unique_ptr timeout_; + + DurationMs duration_; + + TimerGeneration generation_ = TimerGeneration(0); + bool is_running_ = false; + int expiration_count_ = 0; +}; + +class TimerManager { + using TimeoutCreator = std::function( + sled::TaskQueueBase::DelayPrecision)>; + +public: + explicit TimerManager(TimeoutCreator timeout_creator) + : timeout_creator_(timeout_creator) + {} + + std::unique_ptr CreateTimer(const std::string &name, + Timer::OnExpired on_expired); + void HandleTimeout(TimeoutID timeout_id); + +private: + const TimeoutCreator timeout_creator_; + std::map timers_; + TimerID next_id_ = TimerID(0); +}; +}// namespace sled +#endif// SLED_TIMER_TIMER_H diff --git a/src/timer/task_queue_timeout.cc b/src/timer/task_queue_timeout.cc new file mode 100644 index 0000000..0e8b978 --- /dev/null +++ b/src/timer/task_queue_timeout.cc @@ -0,0 +1,58 @@ +#include "sled/timer/task_queue_timeout.h" +#include "sled/log/log.h" +#include "sled/units/time_delta.h" + +namespace sled { +TaskQueueTimeoutFactory::TaskQueueTimeout::TaskQueueTimeout( + TaskQueueTimeoutFactory &parent, + sled::TaskQueueBase::DelayPrecision precision) + : parent_(parent), + precision_(precision) +{} + +TaskQueueTimeoutFactory::TaskQueueTimeout::~TaskQueueTimeout() {} + +void +TaskQueueTimeoutFactory::TaskQueueTimeout::Start(DurationMs duration_ms, + TimeoutID timeout_id) +{ + timeout_expiration_ = parent_.get_time_() + duration_ms; + timeout_id_ = timeout_id; + + if (timeout_expiration_ >= posted_task_expiration_) { return; } + if (posted_task_expiration_ != std::numeric_limits::max()) { + LOGV("timer", + "New timeout duration is less than scheduled - " + "ghosting old delayed task"); + } + + posted_task_expiration_ = timeout_expiration_; + parent_.task_queue_.PostDelayedTaskWithPrecision( + precision_, + [timeout_id, this]() { + posted_task_expiration_ = std::numeric_limits::max(); + if (timeout_expiration_ == std::numeric_limits::max()) { + // cancelled timer + // do nothing + } else { + DurationMs remaining = + timeout_expiration_ - parent_.get_time_(); + timeout_expiration_ = std::numeric_limits::max(); + if (remaining > 0) { + Start(remaining, timeout_id); + } else { + LOGD("", "Timeout Triggered: {}", timeout_id); + parent_.on_expired_(timeout_id_); + } + } + }, + sled::TimeDelta::Millis(duration_ms)); +} + +void +TaskQueueTimeoutFactory::TaskQueueTimeout::Stop() +{ + timeout_expiration_ = std::numeric_limits::max(); +} + +}// namespace sled diff --git a/src/timer/timer.cc b/src/timer/timer.cc new file mode 100644 index 0000000..6865efc --- /dev/null +++ b/src/timer/timer.cc @@ -0,0 +1,60 @@ +#include "sled/timer/timer.h" + +namespace sled { +namespace { +TimeoutID +MakeTimeoutId(TimerID timer_id, TimerGeneration generation) +{ + return TimeoutID(static_cast((timer_id << 32) | generation)); +} +}// namespace + +Timer::Timer(TimerID id, + const std::string &name, + OnExpired on_expired, + UnregisterHandler unregister_handler, + std::unique_ptr timeout) + : id_(id), + name_(name), + on_expired_(on_expired), + unregister_handler_(unregister_handler), + timeout_(std::move(timeout)) +{} + +Timer::~Timer() +{ + Stop(); + unregister_handler_(); +} + +void +Timer::Start() +{ + expiration_count_ = 0; + + if (!is_running()) { + is_running_ = true; + generation_ = TimerGeneration(generation_ + 1); + timeout_->Start(duration_, MakeTimeoutId(id_, generation_)); + } else { + generation_ = TimerGeneration(generation_ + 1); + timeout_->Restart(duration_, MakeTimeoutId(id_, generation_)); + } +} + +std::unique_ptr +TimerManager::CreateTimer(const std::string &name, Timer::OnExpired on_expired) +{ + next_id_ = TimerID(next_id_ + 1); + TimerID id = next_id_; + + std::unique_ptr timeout = + timeout_creator_(sled::TaskQueueBase::DelayPrecision::kHigh); + auto timer = std::unique_ptr(new Timer( + id, name, std::move(on_expired), + /* ungrgister_handler=*/[this, id]() { timers_.erase(id); }, + std::move(timeout))); + timers_[id] = timer.get(); + return timer; +} +}// namespace sled