diff --git a/src/util/Repeat.cpp b/src/util/Repeat.cpp index 725f008a..c8ee12a8 100644 --- a/src/util/Repeat.cpp +++ b/src/util/Repeat.cpp @@ -19,6 +19,8 @@ #include "util/Repeat.hpp" +#include + namespace util { void @@ -27,8 +29,11 @@ Repeat::stop() if (control_->stopping) return; - control_->stopping = true; - control_->timer.cancel(); + boost::asio::post(control_->strand, [control = control_] { + control->stopping = true; + control->timer.cancel(); + }); + control_->semaphore.acquire(); } diff --git a/src/util/Repeat.hpp b/src/util/Repeat.hpp index 1746f87d..de21e02c 100644 --- a/src/util/Repeat.hpp +++ b/src/util/Repeat.hpp @@ -21,9 +21,11 @@ #include "util/Assert.hpp" +#include #include #include #include +#include #include #include @@ -41,10 +43,11 @@ namespace util { class Repeat { struct Control { boost::asio::steady_timer timer; + boost::asio::strand strand; std::atomic_bool stopping{true}; std::binary_semaphore semaphore{0}; - Control(auto& ctx) : timer(ctx) + Control(auto& ctx) : timer(ctx), strand(boost::asio::make_strand(ctx)) { } }; @@ -98,15 +101,24 @@ private: static void startImpl(std::shared_ptr control, std::chrono::steady_clock::duration interval, Action&& action) { - control->timer.expires_after(interval); - control->timer.async_wait([control, interval, action = std::forward(action)](auto const& ec) mutable { - if (ec or control->stopping) { + boost::asio::post(control->strand, [control, interval, action = std::forward(action)]() mutable { + if (control->stopping) { control->semaphore.release(); return; } - action(); - startImpl(std::move(control), interval, std::forward(action)); + control->timer.expires_after(interval); + control->timer.async_wait( + [control, interval, action = std::forward(action)](auto const& ec) mutable { + if (ec or control->stopping) { + control->semaphore.release(); + return; + } + action(); + + startImpl(std::move(control), interval, std::forward(action)); + } + ); }); } }; diff --git a/tests/unit/util/RepeatTests.cpp b/tests/unit/util/RepeatTests.cpp index 122307ca..e686fff7 100644 --- a/tests/unit/util/RepeatTests.cpp +++ b/tests/unit/util/RepeatTests.cpp @@ -53,16 +53,16 @@ struct RepeatTests : SyncAsioContextTest { TEST_F(RepeatTests, CallsHandler) { - repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction()); EXPECT_CALL(handlerMock, Call).Times(testing::AtMost(22)); + repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction()); runContextFor(std::chrono::milliseconds{20}); } TEST_F(RepeatTests, StopsOnStop) { withRunningContext([this]() { - repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction()); EXPECT_CALL(handlerMock, Call).Times(AtLeast(1)); + repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction()); std::this_thread::sleep_for(std::chrono::milliseconds{10}); repeat.stop(); }); @@ -72,8 +72,8 @@ TEST_F(RepeatTests, RunsAfterStop) { withRunningContext([this]() { for ([[maybe_unused]] auto i : std::ranges::iota_view(0, 2)) { - repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction()); EXPECT_CALL(handlerMock, Call).Times(AtLeast(1)); + repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction()); std::this_thread::sleep_for(std::chrono::milliseconds{10}); repeat.stop(); }