Skip to content

Commit

Permalink
Implement watchdog
Browse files Browse the repository at this point in the history
  • Loading branch information
abhaybd committed Aug 18, 2023
1 parent 03e0786 commit 4f8b91b
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 9 deletions.
92 changes: 84 additions & 8 deletions src/utils/scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@

namespace util {

template <typename Clock> class PeriodicScheduler;

namespace impl {
template <typename Clock> void notifyScheduler(PeriodicScheduler<Clock>& scheduler);
}

class Notifiable {
public:
virtual void notify() = 0;
};

template <typename T> void notifyScheduler(T& scheduler);
} // namespace impl

/**
* @brief Uses a single thread to periodically invoke callbacks at a given frequency.
Expand All @@ -24,7 +28,8 @@ template <typename Clock> void notifyScheduler(PeriodicScheduler<Clock>& schedul
*
* @tparam Clock The clock to use for scheduling.
*/
template <typename Clock = std::chrono::steady_clock> class PeriodicScheduler {
template <typename Clock = std::chrono::steady_clock>
class PeriodicScheduler : private impl::Notifiable {
public:
/**
* @brief The type of event ids.
Expand Down Expand Up @@ -108,6 +113,10 @@ template <typename Clock = std::chrono::steady_clock> class PeriodicScheduler {
private:
friend void util::impl::notifyScheduler<>(PeriodicScheduler&);

void notify() override {
scheduleCV.notify_all();
}

/**
* @brief Method executed by the scheduler thread.
*/
Expand Down Expand Up @@ -166,6 +175,72 @@ template <typename Clock = std::chrono::steady_clock> class PeriodicScheduler {
std::unordered_set<eventid_t> toRemove;
};

template <typename Clock = std::chrono::steady_clock>
class Watchdog : private impl::Notifiable {
public:
explicit Watchdog(std::chrono::milliseconds duration,
const std::function<void()>& callback, bool keepCallingOnDeath = false)
: duration(duration), callback(callback), keepCallingOnDeath(keepCallingOnDeath),
fed(false), quitting(false), thread(std::bind(&Watchdog::threadFn, this)) {}

Watchdog(const Watchdog&) = delete;

~Watchdog() {
{
std::unique_lock lock(mutex);
quitting = true;
// wake up the thread with the cv so it can quit
fed = true;
}
cv.notify_one();
thread.join();
}

Watchdog& operator=(const Watchdog&) = delete;

void feed() {
{
std::lock_guard lock(mutex);
fed = true;
}
cv.notify_one();
}

private:
friend void util::impl::notifyScheduler<>(Watchdog&);

std::chrono::milliseconds duration;
std::function<void()> callback;
const bool keepCallingOnDeath;
bool fed;
bool quitting;
std::mutex mutex;
std::condition_variable cv;
std::thread thread;

void notify() override {
cv.notify_all();
}

void threadFn() {
std::unique_lock lock(mutex);
while (!quitting) {
std::chrono::time_point<Clock> wakeTime = Clock::now() + duration;
if (cv.wait_until(lock, wakeTime, [&]() { return fed || quitting; })) {
if (quitting) {
break;
}
fed = false;
} else {
callback();
if (!keepCallingOnDeath) {
cv.wait(lock, [&]() { return fed; });
}
}
}
}
};

namespace impl {

/**
Expand All @@ -174,13 +249,14 @@ namespace impl {
* This is useful to trigger the scheduler to continue if the clock has been changed
* and the scheduler hasn't woken up. Overusing this method can degrade performance.
*
* @tparam Clock The clock of the scheduler.
* @tparam T The type of the scheduler.
* @param scheduler The scheduler to notify.
*
* @warning Client code should NOT use this.
*/
template <typename Clock> void notifyScheduler(PeriodicScheduler<Clock>& scheduler) {
scheduler.scheduleCV.notify_all();
template <typename T> void notifyScheduler(T& scheduler) {
Notifiable& n = scheduler;
n.notify();
}

} // namespace impl
Expand Down
61 changes: 60 additions & 1 deletion tests/util/SchedulerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ class fake_clock {
static inline time_point now_;
};

template <typename T>
void advanceFakeClock(std::chrono::milliseconds dur, std::chrono::milliseconds inc,
PeriodicScheduler<fake_clock>& scheduler) {
T& scheduler) {
for (int i = 0; i < dur / inc; i++) {
fake_clock::advance(inc);
util::impl::notifyScheduler(scheduler);
Expand Down Expand Up @@ -87,3 +88,61 @@ TEST_CASE("Test PeriodicScheduler Remove", "[util][scheduler]") {
advanceFakeClock(100ms, 5ms, sched);
REQUIRE_FALSE(i->wait_for(100ms));
}

TEST_CASE("Test Watchdog", "[util][scheduler]") {
// NOTE: after feeding the thread must wait for the watchdog thread to wake up and register
// it. Doing feed() -> advanceTime() -> checkStarved() doesn't work. It should be feed() ->
// checkFed() -> advanceTime() -> checkStarved().
SECTION("Test regular") {
auto l = std::make_shared<latch>(1);
Watchdog<fake_clock> wd(100ms, [&]() { l->count_down(); });
for (int i = 0; i < 5; i++) {
REQUIRE_FALSE(l->wait_for(10ms));
advanceFakeClock(50ms, 5ms, wd);
REQUIRE_FALSE(l->wait_for(10ms));
wd.feed();
}
REQUIRE_FALSE(l->wait_for(10ms));
advanceFakeClock(200ms, 5ms, wd);
REQUIRE(l->wait_for(10ms));

// check that callback is not called again while starved
l = std::make_shared<latch>(1);
advanceFakeClock(200ms, 5ms, wd);
REQUIRE_FALSE(l->wait_for(10ms));

// check that callback is called after feeding and starving again
wd.feed();
REQUIRE_FALSE(l->wait_for(10ms));
advanceFakeClock(100ms, 5ms, wd);
REQUIRE(l->wait_for(10ms));
}

SECTION("Test keep calling while starved") {
auto l = std::make_shared<latch>(1);
Watchdog<fake_clock> wd(100ms, [&]() { l->count_down(); }, true);
for (int i = 0; i < 5; i++) {
REQUIRE_FALSE(l->wait_for(10ms));
advanceFakeClock(50ms, 5ms, wd);
REQUIRE_FALSE(l->wait_for(10ms));
wd.feed();
}
REQUIRE_FALSE(l->wait_for(10ms));
advanceFakeClock(100ms, 5ms, wd);
REQUIRE(l->wait_for(10ms));

// check that callback is repeatedly called while starved
for (int i = 0; i < 3; i++) {
l = std::make_shared<latch>(1);
advanceFakeClock(100ms, 5ms, wd);
REQUIRE(l->wait_for(10ms));
}

// check that callback is called after feeding and starving again
l = std::make_shared<latch>(1);
wd.feed();
REQUIRE_FALSE(l->wait_for(10ms));
advanceFakeClock(100ms, 5ms, wd);
REQUIRE(l->wait_for(10ms));
}
}

0 comments on commit 4f8b91b

Please sign in to comment.