mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-04 20:05:51 +00:00
fix: Improve Repeat implementation (#1775)
This commit is contained in:
@@ -27,12 +27,16 @@ Repeat::Repeat(boost::asio::io_context& ioc) : timer_(ioc)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Repeat::~Repeat()
|
||||||
|
{
|
||||||
|
*stopped_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Repeat::stop()
|
Repeat::stop()
|
||||||
{
|
{
|
||||||
stopping_ = true;
|
*stopped_ = true;
|
||||||
timer_.cancel();
|
timer_.cancel();
|
||||||
semaphore_.acquire();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/Assert.hpp"
|
||||||
|
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <boost/asio/post.hpp>
|
#include <boost/asio/post.hpp>
|
||||||
#include <boost/asio/steady_timer.hpp>
|
#include <boost/asio/steady_timer.hpp>
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <semaphore>
|
#include <memory>
|
||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
@@ -36,8 +38,7 @@ namespace util {
|
|||||||
*/
|
*/
|
||||||
class Repeat {
|
class Repeat {
|
||||||
boost::asio::steady_timer timer_;
|
boost::asio::steady_timer timer_;
|
||||||
std::atomic_bool stopping_{false};
|
std::shared_ptr<std::atomic_bool> stopped_ = std::make_shared<std::atomic_bool>(true);
|
||||||
std::binary_semaphore semaphore_{0};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +48,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
Repeat(boost::asio::io_context& ioc);
|
Repeat(boost::asio::io_context& ioc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Repeat object
|
||||||
|
*/
|
||||||
|
~Repeat();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stop repeating
|
* @brief Stop repeating
|
||||||
* @note This method will block to ensure the repeating is actually stopped. But blocking time should be very short.
|
* @note This method will block to ensure the repeating is actually stopped. But blocking time should be very short.
|
||||||
@@ -56,6 +62,7 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start asynchronously repeating
|
* @brief Start asynchronously repeating
|
||||||
|
* @note stop() must be called before start() is called for the second time
|
||||||
*
|
*
|
||||||
* @tparam Action The action type
|
* @tparam Action The action type
|
||||||
* @param interval The interval to repeat
|
* @param interval The interval to repeat
|
||||||
@@ -65,7 +72,9 @@ public:
|
|||||||
void
|
void
|
||||||
start(std::chrono::steady_clock::duration interval, Action&& action)
|
start(std::chrono::steady_clock::duration interval, Action&& action)
|
||||||
{
|
{
|
||||||
stopping_ = false;
|
ASSERT(*stopped_, "Repeat should be stopped before the next use");
|
||||||
|
// Create a new variable for each start() to make each start()-stop() session independent
|
||||||
|
stopped_ = std::make_shared<std::atomic_bool>(false);
|
||||||
startImpl(interval, std::forward<Action>(action));
|
startImpl(interval, std::forward<Action>(action));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +84,10 @@ private:
|
|||||||
startImpl(std::chrono::steady_clock::duration interval, Action&& action)
|
startImpl(std::chrono::steady_clock::duration interval, Action&& action)
|
||||||
{
|
{
|
||||||
timer_.expires_after(interval);
|
timer_.expires_after(interval);
|
||||||
timer_.async_wait([this, interval, action = std::forward<Action>(action)](auto const&) mutable {
|
timer_.async_wait([this, interval, stopping = stopped_, action = std::forward<Action>(action)](
|
||||||
if (stopping_) {
|
auto const& errorCode
|
||||||
semaphore_.release();
|
) mutable {
|
||||||
|
if (errorCode or *stopping) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
action();
|
action();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
using namespace util;
|
using namespace util;
|
||||||
using testing::AtLeast;
|
using testing::AtLeast;
|
||||||
|
|
||||||
struct RepeatTests : SyncAsioContextTest {
|
struct RepeatTest : SyncAsioContextTest {
|
||||||
Repeat repeat{ctx};
|
Repeat repeat{ctx};
|
||||||
testing::StrictMock<testing::MockFunction<void()>> handlerMock;
|
testing::StrictMock<testing::MockFunction<void()>> handlerMock;
|
||||||
|
|
||||||
@@ -51,14 +51,14 @@ struct RepeatTests : SyncAsioContextTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(RepeatTests, CallsHandler)
|
TEST_F(RepeatTest, CallsHandler)
|
||||||
{
|
{
|
||||||
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
|
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
|
||||||
EXPECT_CALL(handlerMock, Call).Times(AtLeast(10));
|
EXPECT_CALL(handlerMock, Call).Times(AtLeast(10));
|
||||||
runContextFor(std::chrono::milliseconds{20});
|
runContextFor(std::chrono::milliseconds{20});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(RepeatTests, StopsOnStop)
|
TEST_F(RepeatTest, StopsOnStop)
|
||||||
{
|
{
|
||||||
withRunningContext([this]() {
|
withRunningContext([this]() {
|
||||||
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
|
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
|
||||||
@@ -68,10 +68,10 @@ TEST_F(RepeatTests, StopsOnStop)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(RepeatTests, RunsAfterStop)
|
TEST_F(RepeatTest, RunsAfterStop)
|
||||||
{
|
{
|
||||||
withRunningContext([this]() {
|
withRunningContext([this]() {
|
||||||
for ([[maybe_unused]] auto _ : std::ranges::iota_view(0, 2)) {
|
for ([[maybe_unused]] auto i : std::ranges::iota_view(0, 2)) {
|
||||||
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
|
repeat.start(std::chrono::milliseconds{1}, handlerMock.AsStdFunction());
|
||||||
EXPECT_CALL(handlerMock, Call).Times(AtLeast(1));
|
EXPECT_CALL(handlerMock, Call).Times(AtLeast(1));
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds{10});
|
std::this_thread::sleep_for(std::chrono::milliseconds{10});
|
||||||
@@ -79,3 +79,16 @@ TEST_F(RepeatTests, RunsAfterStop)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RepeatDeathTest : RepeatTest {};
|
||||||
|
|
||||||
|
TEST_F(RepeatDeathTest, DiesWhenStartCalledTwice)
|
||||||
|
{
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
repeat.start(std::chrono::seconds{1}, []() {});
|
||||||
|
repeat.start(std::chrono::seconds{1}, []() {});
|
||||||
|
},
|
||||||
|
"Assertion .* failed.*"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user