Skip to content

Commit

Permalink
decrease memory usage event
Browse files Browse the repository at this point in the history
  • Loading branch information
maksimdrachov committed Dec 4, 2024
1 parent d8fe9ed commit 29ffc4c
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 30 deletions.
16 changes: 8 additions & 8 deletions include/olga_scheduler/olga_scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,21 @@ class Event : private cavl::Node<Event<TimePoint>>
void cancel() noexcept
{
/// It is guaranteed that while an event resides in the tree, it has a valid deadline set.
if (deadline_)
if (deadline_ != TimePoint::min())
{
// Removing a non-existent node from the tree is an UB that may lead to memory corruption,
// so we have to check first if the event is actually registered.
remove();
// This is used to mark the event as unregistered so we don't double-remove it.
// This can only be done after the event is removed from the tree.
deadline_.reset();
deadline_ = TimePoint::min();
}
}

/// Diagnostic accessor for testing. Not intended for normal use.
/// Empty option means that the event is not scheduled or canceled.
/// It is guaranteed that while an event resides in the tree, it has a valid deadline set.
[[nodiscard]] std::optional<TimePoint> getDeadline() const noexcept { return deadline_; }
[[nodiscard]] TimePoint getDeadline() const noexcept { return deadline_; }

// This method is necessary to store an Event in cetl::unbounded_variant
static constexpr std::array<std::uint8_t, 16> _get_type_id_() noexcept
Expand All @@ -115,14 +115,14 @@ class Event : private cavl::Node<Event<TimePoint>>
virtual ~Event()
{
cancel();
assert(!deadline_.has_value());
assert(deadline_ == TimePoint::min());
assert((this->getChildNode(false) == nullptr) && (this->getChildNode(true) == nullptr) &&
(this->getParentNode() == nullptr));
}

Event(Event&& other) noexcept :
cavl::Node<Event>{std::move(static_cast<cavl::Node<Event>&>(other))},
deadline_{std::exchange(other.deadline_, std::nullopt)}
deadline_{std::exchange(other.deadline_, TimePoint::min())}
{}

/// Ensure the event is in the tree and set the deadline to the specified absolute time point.
Expand All @@ -136,7 +136,7 @@ class Event : private cavl::Node<Event<TimePoint>>
[dead](const Event& other) {
/// No two deadlines compare equal, which allows us to have multiple nodes with the same deadline in
/// the tree. With two nodes sharing the same deadline, the one added later is considered to be later.
return (dead >= other.deadline_.value()) ? +1 : -1;
return (dead >= other.deadline_) ? +1 : -1;
},
[this] { return this; });
assert(std::get<0>(ptr_existing) == this);
Expand All @@ -151,7 +151,7 @@ class Event : private cavl::Node<Event<TimePoint>>
virtual void execute(const Arg<TimePoint>& args, Tree& tree) = 0;

private:
std::optional<TimePoint> deadline_;
TimePoint deadline_=TimePoint::min();
};

/// This information is returned by the spin() method to allow the caller to decide what to do next
Expand Down Expand Up @@ -323,7 +323,7 @@ class EventLoop final
while (auto* const evt = static_cast<EventProxy*>(tree_.min()))
{
// The deadline is guaranteed to be set because it is in the tree.
const auto deadline = evt->getDeadline().value();
const auto deadline = evt->getDeadline();
if (result.approx_now < deadline) // Too early -- either we need to sleep or the time sample is obsolete.
{
result.approx_now = Clock::now(); // The number of calls to Clock::now() is minimized.
Expand Down
44 changes: 22 additions & 22 deletions tests/test_olga_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ TEST(TestOlgaScheduler, EventLoopBasic)

// Register our handlers. Events with same deadline are ordered such that the one added later is processed later.
auto evt_a = evl.repeat(1000ms, [&](const auto& arg) { a.emplace(arg); });
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[1U], IsNull());
EXPECT_FALSE(evl.isEmpty());

Expand All @@ -114,27 +114,27 @@ TEST(TestOlgaScheduler, EventLoopBasic)

auto evt_b = evl.repeat(100ms, // Smaller deadline goes on the left.
[&](const auto& arg) { b.emplace(arg); });
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 10'100ms);
EXPECT_THAT(evl.getTree()[1U]->getDeadline().value().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 10'100ms);
EXPECT_THAT(evl.getTree()[1U]->getDeadline().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[2U], IsNull());

auto evt_c = evl.defer(SteadyClockMock::now() + 2000ms, [&](const auto& arg) { c.emplace(arg); });
// EXPECT_THAT(evt_c, Optional(_));
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 10'100ms);
EXPECT_THAT(evl.getTree()[1U]->getDeadline().value().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 10'100ms);
EXPECT_THAT(evl.getTree()[1U]->getDeadline().time_since_epoch(), 11'000ms);
const auto* const f3 = evl.getTree()[2U];
EXPECT_THAT(f3->getDeadline().value().time_since_epoch(), 12'000ms); // New entry.
EXPECT_THAT(f3->getDeadline().time_since_epoch(), 12'000ms); // New entry.
EXPECT_THAT(evl.getTree()[3U], IsNull());

auto evt_d = evl.defer(SteadyClockMock::now() + 2000ms, // Same deadline!
[&](const auto& arg) { d.emplace(arg); });
// EXPECT_THAT(evt_d, NotNull());
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 10'100ms);
EXPECT_THAT(evl.getTree()[1U]->getDeadline().value().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 10'100ms);
EXPECT_THAT(evl.getTree()[1U]->getDeadline().time_since_epoch(), 11'000ms);
EXPECT_THAT(evl.getTree()[2U], f3); // Entry added before this one.
const auto* const f4 = evl.getTree()[3U];
EXPECT_THAT(f3, Ne(f4));
EXPECT_THAT(f4->getDeadline().value().time_since_epoch(), 12'000ms); // New entry, same deadline added later.
EXPECT_THAT(f4->getDeadline().time_since_epoch(), 12'000ms); // New entry, same deadline added later.
EXPECT_THAT(evl.getTree()[4U], IsNull());

// Poll but there are no pending Events yet.
Expand Down Expand Up @@ -164,7 +164,7 @@ TEST(TestOlgaScheduler, EventLoopBasic)
EXPECT_FALSE(d);
a.reset();
b.reset();
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 11'200ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 11'200ms);

// Move on. Let C&D fire, they are canceled automatically.
SteadyClockMock::advance(900ms);
Expand All @@ -173,7 +173,7 @@ TEST(TestOlgaScheduler, EventLoopBasic)
EXPECT_THAT(out.next_deadline.time_since_epoch(), 12'100ms);
EXPECT_THAT(out.worst_lateness, 800ms);
EXPECT_THAT(out.approx_now.time_since_epoch(), 12'000ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 12'100ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 12'100ms);
EXPECT_THAT(evl.getTree().size(), 2); // C&D have left us.
EXPECT_TRUE(a);
EXPECT_THAT(a.value().deadline.time_since_epoch(), 12'000ms);
Expand All @@ -188,26 +188,26 @@ TEST(TestOlgaScheduler, EventLoopBasic)
c.reset();
d.reset();
// Ensure the deadline is cleared on those events that are canceled.
EXPECT_TRUE(evt_a.getDeadline());
EXPECT_TRUE(evt_b.getDeadline());
EXPECT_FALSE(evt_c.getDeadline());
EXPECT_FALSE(evt_d.getDeadline());
EXPECT_TRUE(evt_a.getDeadline() > Loop::time_point::min());
EXPECT_TRUE(evt_b.getDeadline() > Loop::time_point::min());
EXPECT_THAT(evt_c.getDeadline(), Loop::time_point::min());
EXPECT_THAT(evt_d.getDeadline(), Loop::time_point::min());

// Drop the second event and ensure it is removed from the tree immediately.
SteadyClockMock::advance(1050ms);
EXPECT_THAT(SteadyClockMock::now().time_since_epoch(), 13'050ms);
EXPECT_TRUE(evt_b.getDeadline());
EXPECT_TRUE(evt_b.getDeadline() > Loop::time_point::min());
evt_b.cancel();
EXPECT_FALSE(evt_b.getDeadline()); // Unregistered, cleared.
EXPECT_THAT(evt_b.getDeadline(), Loop::time_point::min()); // Unregistered, cleared.
EXPECT_THAT(evl.getTree().size(), 1); // Freed already.
evt_b.cancel(); // Idempotency.
EXPECT_FALSE(evt_b.getDeadline()); // Ditto.
EXPECT_THAT(evt_b.getDeadline(), Loop::time_point::min()); // Ditto.
EXPECT_THAT(evl.getTree().size(), 1); // Ditto.
out = evl.spin();
EXPECT_THAT(out.next_deadline.time_since_epoch(), 14'000ms); // B removed so the next one is A.
EXPECT_THAT(out.worst_lateness, 50ms);
EXPECT_THAT(out.approx_now.time_since_epoch(), 13'050ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 14'000ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 14'000ms);
EXPECT_THAT(1, evl.getTree().size()); // Second dropped.
EXPECT_TRUE(a);
EXPECT_THAT(a.value().deadline.time_since_epoch(), 13'000ms);
Expand Down Expand Up @@ -271,7 +271,7 @@ TEST(TestOlgaScheduler, EventLoopPoll)
EXPECT_FALSE(last_tp);
last_tp = arg.deadline;
});
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 110ms);
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 110ms);

// Check the type ID of return type poll().
const auto actual_type_id = decltype(evt)::_get_type_id_();
Expand All @@ -283,15 +283,15 @@ TEST(TestOlgaScheduler, EventLoopPoll)
EXPECT_TRUE(last_tp);
EXPECT_THAT(last_tp.value().time_since_epoch(), 110ms);
last_tp.reset();
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 140ms); // Skipped ahead!
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 140ms); // Skipped ahead!

SteadyClockMock::advance(70ms);
EXPECT_THAT(SteadyClockMock::now().time_since_epoch(), 200ms);
(void) evl.spin();
EXPECT_TRUE(last_tp);
EXPECT_THAT(last_tp.value().time_since_epoch(), 140ms);
last_tp.reset();
EXPECT_THAT(evl.getTree()[0U]->getDeadline().value().time_since_epoch(), 210ms); // Skipped ahead!
EXPECT_THAT(evl.getTree()[0U]->getDeadline().time_since_epoch(), 210ms); // Skipped ahead!
}

TEST(TestOlgaScheduler, EventLoopDefer_single_overdue)
Expand Down

0 comments on commit 29ffc4c

Please sign in to comment.