Skip to content

Commit

Permalink
Make RefreshLoop constructor more generic, create add_member method. (#…
Browse files Browse the repository at this point in the history
…670)

- Allows using different containers in the refreshloop consturctor argument, for example std::vector<>.
- Adds an add_member(...) call to the refreshloop API to facilitate dynamically creating the member list.
- Modernizes the unit tests infrastructure with fake clock such that the test does not take that much time.

===

Modernizes the unit tests infrastructure with fake clock such that the test does not take that much time.
  • Loading branch information
balazsracz authored Oct 24, 2022
1 parent b878ef6 commit 7960363
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 15 deletions.
32 changes: 29 additions & 3 deletions src/openlcb/RefreshLoop.cxxtest
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "utils/async_if_test_helper.hxx"
#include "openlcb/RefreshLoop.hxx"

#include "utils/async_if_test_helper.hxx"
#include "os/FakeClock.hxx"

namespace openlcb
{
namespace
Expand Down Expand Up @@ -29,14 +31,16 @@ protected:
MockPolling mp1_;
MockPolling mp2_;
std::unique_ptr<RefreshLoop> loop_;
FakeClock clk_;
};

TEST_F(RefreshLoopTest, Calls)
{
EXPECT_CALL(mp1_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly(
WithArg<1>(Invoke(&InvokeNotification)));
loop_.reset(new RefreshLoop(node_, {&mp1_}));
usleep(150000);
clk_.advance(USEC_TO_NSEC(150000));
wait();
}

TEST_F(RefreshLoopTest, CallsBoth)
Expand All @@ -46,7 +50,29 @@ TEST_F(RefreshLoopTest, CallsBoth)
EXPECT_CALL(mp2_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly(
WithArg<1>(Invoke(&InvokeNotification)));
loop_.reset(new RefreshLoop(node_, {&mp1_, &mp2_}));
usleep(120000);
clk_.advance(USEC_TO_NSEC(120000));
wait();
}

TEST_F(RefreshLoopTest, VectorConsturct)
{
EXPECT_CALL(mp1_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly(
WithArg<1>(Invoke(&InvokeNotification)));
std::vector<Polling*> v;
v.push_back(&mp1_);
loop_.reset(new RefreshLoop(node_, v));
clk_.advance(USEC_TO_NSEC(150000));
wait();
}

TEST_F(RefreshLoopTest, Add)
{
EXPECT_CALL(mp1_, poll_33hz(_, _)).Times(AtLeast(3)).WillRepeatedly(
WithArg<1>(Invoke(&InvokeNotification)));
loop_.reset(new RefreshLoop(node_, {}));
loop_->add_member(&mp1_);
clk_.advance(USEC_TO_NSEC(150000));
wait();
}

} // namespace
Expand Down
50 changes: 38 additions & 12 deletions src/openlcb/RefreshLoop.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,51 @@ public:
/// Usage: Instantiate your objects from descendants of the Polling
/// class. Create a RefreshLoop object and pass in the list of Polling object
/// pointers to the constructor.
class RefreshLoop : public StateFlowBase
class RefreshLoop : public StateFlowBase, private Atomic
{
public:
RefreshLoop(Node *node, const std::initializer_list<Polling *> &members)
/// Constructor
///
/// @param node openlcb Node whose interface/executor we will be using for
/// the polling loop.
/// @param members container of Polling* objects
/// (e.g. std::initializer_list<Polling*> or std::vector<Polling*> which
/// need to be polled by this refresh loop.
template <class Container = std::initializer_list<Polling*> >
RefreshLoop(Node *node, const Container &members)
: StateFlowBase(node->iface())
, timer_(this)
, lastTimeout_(os_get_time_monotonic())
, members_(members)
, members_(members.begin(), members.end())
{
start_flow(STATE(wait_for_tick));
}

/** Stops the refresh loop. If you call this funciton, then wait for the
* executor, then it is safe to delete *this. */
/// Stops the refresh loop. If you call this funciton, then wait for the
/// executor, then it is safe to delete *this.
void stop()
{
set_terminated();
timer_.ensure_triggered();
}

/// Adds a new member to the polling loop.
///
/// @param new_member the member to be polled. The object ownership is
/// retained by the caller. The object must outlive this RefreshLoop
/// object.
void add_member(Polling *new_member)
{
AtomicHolder h(this);
members_.push_back(new_member);
}

/// State which gets called after the loop is complete. Initializes the
/// next loop and sleeps until it's time to go.
Action wait_for_tick()
{
lastTimeout_ += MSEC_TO_NSEC(30);
nextMember_ = members_.begin();
nextMember_ = 0;
// If we have overflowed our timer, this call will happen immediately.
return sleep_and_call(&timer_, lastTimeout_ - os_get_time_monotonic(),
STATE(call_members));
Expand All @@ -92,13 +113,18 @@ public:
{
while (true)
{
if (nextMember_ == members_.end())
Polling* member = nullptr;
{
return call_immediately(STATE(wait_for_tick));
AtomicHolder h(this);
if (nextMember_ >= members_.size())
{
return call_immediately(STATE(wait_for_tick));
}
member = members_[nextMember_];
++nextMember_;
}
bn_.reset(this);
(*nextMember_)->poll_33hz(&helper_, bn_.new_child());
++nextMember_;
member->poll_33hz(&helper_, bn_.new_child());
if (bn_.abort_if_almost_done())
{
// The polling member called done notifiable inline. Short
Expand All @@ -123,8 +149,8 @@ private:
typedef vector<Polling*> members_type;
/// The actual members.
members_type members_;
/// Iterator for going through the members list.
members_type::iterator nextMember_;
/// Index for iterating through the members list.
unsigned nextMember_;
};

} // namespace openlcb
Expand Down

0 comments on commit 7960363

Please sign in to comment.