Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made the lib optionally thread-safe, added running state #9

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 115 additions & 51 deletions include/everest/timer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,86 +5,162 @@

#include <boost/asio.hpp>
#include <chrono>
#include <condition_variable>
#include <date/date.h>
#include <date/tz.h>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>

namespace Everest {

template <typename Type, bool Exists> struct OptionalTimerMember {
Type data;
};

template <typename Type> struct OptionalTimerMember<Type, false> {};

template <template <typename> typename Guard, typename Mutex, bool Enabled> struct OptionalGuard {
OptionalGuard(OptionalTimerMember<Mutex, Enabled>& in_mutex) {

Check notice on line 24 in include/everest/timer.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/everest/timer.hpp#L24

Struct 'OptionalGuard' has a constructor with 1 argument that is not explicit.
if constexpr (Enabled) {
guard.data = std::move(Guard<Mutex>(in_mutex.data));
}
}

OptionalTimerMember<Guard<Mutex>, Enabled> guard;
};

// template <typename TimerClock = date::steady_clock> class Timer {
template <typename TimerClock = date::utc_clock> class Timer {
template <typename TimerClock = date::utc_clock, bool ThreadSafe = false, bool SharedContext = false> class Timer {
private:
boost::asio::basic_waitable_timer<TimerClock>* timer = nullptr;
std::unique_ptr<boost::asio::basic_waitable_timer<TimerClock>> timer = nullptr;
std::function<void()> callback;
std::function<void(const boost::system::error_code& e)> callback_wrapper;
std::chrono::nanoseconds interval_nanoseconds;
std::condition_variable cv;
std::mutex wait_mutex;
bool running = false;

boost::asio::io_context io_context;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work;
std::thread* timer_thread = nullptr;
std::unique_ptr<std::thread> timer_thread = nullptr;

OptionalTimerMember<std::mutex, ThreadSafe> mutex;

public:
/// This timer will initialize a boost::asio::io_context
explicit Timer() : work(boost::asio::make_work_guard(this->io_context)) {
this->timer = new boost::asio::basic_waitable_timer<TimerClock>(this->io_context);
this->timer_thread = new std::thread([this]() { this->io_context.run(); });
this->timer = std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(this->io_context);
this->timer_thread = std::make_unique<std::thread>([this]() { this->io_context.run(); });
}

explicit Timer(const std::function<void()>& callback) : work(boost::asio::make_work_guard(this->io_context)) {
this->timer = new boost::asio::basic_waitable_timer<TimerClock>(this->io_context);
this->timer_thread = new std::thread([this]() { this->io_context.run(); });
this->timer = std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(this->io_context);
this->timer_thread = std::make_unique<std::thread>([this]() { this->io_context.run(); });
this->callback = callback;
}

explicit Timer(boost::asio::io_context* io_context) : work(boost::asio::make_work_guard(*io_context)) {
this->timer = new boost::asio::basic_waitable_timer<TimerClock>(*io_context);
this->timer = std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(*io_context);
}

explicit Timer(boost::asio::io_context* io_context, const std::function<void()>& callback) :
work(boost::asio::make_work_guard(*io_context)) {
this->timer = new boost::asio::basic_waitable_timer<TimerClock>(*io_context);
this->timer = std::make_unique<boost::asio::basic_waitable_timer<TimerClock>>(*io_context);
this->callback = callback;
}

~Timer() {
if (this->timer != nullptr) {
if (this->timer) {
// stop asio timer
this->timer->cancel();

if (this->timer_thread != nullptr) {
if (this->timer_thread) {
this->io_context.stop();
this->timer_thread->join();
}

delete this->timer;
delete this->timer_thread;
}
}

/// Executes the given callback at the given timepoint
template <class Clock, class Duration = typename Clock::duration>
void at(const std::function<void()>& callback, const std::chrono::time_point<Clock, Duration>& time_point) {
this->stop();
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

Check notice on line 86 in include/everest/timer.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/everest/timer.hpp#L86

Variable 'optional_guard' is assigned a value that is never used.

this->stop_internal();
this->callback = callback;

this->at(time_point);
this->at_internal(time_point);
}

/// Executes the at the given timepoint
template <class Clock, class Duration = typename Clock::duration>
void at(const std::chrono::time_point<Clock, Duration>& time_point) {
this->stop();
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

at_internal<Clock, Duration>(time_point);
}

/// Execute the given callback peridically from now in the given interval
template <class Rep, class Period>
void interval(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

this->stop_internal();
this->callback = callback;

this->interval_internal(interval);
}

/// Execute peridically from now in the given interval
template <class Rep, class Period> void interval(const std::chrono::duration<Rep, Period>& interval) {
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

this->interval_internal(interval);
}

// Execute the given callback once after the given interval
template <class Rep, class Period>
void timeout(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

this->stop_internal();
this->callback = callback;

this->timeout_internal(interval);
}

// Execute the given callback once after the given interval
template <class Rep, class Period> void timeout(const std::chrono::duration<Rep, Period>& interval) {
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

this->timeout_internal(interval);
}

/// Stop timer from excuting its callback
void stop() {
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

stop_internal();
}

bool is_running() {
OptionalGuard<std::unique_lock, std::mutex, ThreadSafe> optional_guard(this->mutex);

return running;
}

private:
/// Executes the at the given timepoint
template <class Clock, class Duration = typename Clock::duration>
void at_internal(const std::chrono::time_point<Clock, Duration>& time_point) {
this->stop_internal();

if (this->callback == nullptr) {
return;
}

if (this->timer != nullptr) {
if (this->timer) {
running = true;

// use asio timer
this->timer->expires_at(time_point);
this->timer->async_wait([this](const boost::system::error_code& e) {
Expand All @@ -93,23 +169,14 @@
}

this->callback();
running = false;
});
}
}

/// Execute the given callback peridically from now in the given interval
template <class Rep, class Period>
void interval(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
this->stop();

this->callback = callback;

this->interval(interval);
}
template <class Rep, class Period> void interval_internal(const std::chrono::duration<Rep, Period>& interval) {
this->stop_internal();

/// Execute peridically from now in the given interval
template <class Rep, class Period> void interval(const std::chrono::duration<Rep, Period>& interval) {
this->stop();
this->interval_nanoseconds = interval;
if (interval_nanoseconds == std::chrono::nanoseconds(0)) {
return;
Expand All @@ -119,10 +186,13 @@
return;
}

if (this->timer != nullptr) {
if (this->timer) {
running = true;

// use asio timer
this->callback_wrapper = [this](const boost::system::error_code& e) {
if (e) {
running = false;
return;
}

Expand All @@ -137,43 +207,37 @@
}
}

// Execute the given callback once after the given interval
template <class Rep, class Period>
void timeout(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
this->stop();

this->callback = callback;

this->timeout(interval);
}

// Execute the given callback once after the given interval
template <class Rep, class Period> void timeout(const std::chrono::duration<Rep, Period>& interval) {
this->stop();
template <class Rep, class Period> void timeout_internal(const std::chrono::duration<Rep, Period>& interval) {
this->stop_internal();

if (this->callback == nullptr) {
return;
}

if (this->timer != nullptr) {
if (this->timer) {
running = true;

// use asio timer
this->timer->expires_from_now(interval);
this->timer->async_wait([this](const boost::system::error_code& e) {
if (e) {
running = false;
return;
}

this->callback();
running = false;
});
}
}

/// Stop timer from excuting its callback
void stop() {
if (this->timer != nullptr) {
void stop_internal() {
if (this->timer) {
// asio based timer
this->timer->cancel();
}

running = false;
}
};

Expand Down
Loading