mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +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
|
||||
Repeat::stop()
|
||||
{
|
||||
stopping_ = true;
|
||||
*stopped_ = true;
|
||||
timer_.cancel();
|
||||
semaphore_.acquire();
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/Assert.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
@@ -26,7 +28,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <concepts>
|
||||
#include <semaphore>
|
||||
#include <memory>
|
||||
|
||||
namespace util {
|
||||
|
||||
@@ -36,8 +38,7 @@ namespace util {
|
||||
*/
|
||||
class Repeat {
|
||||
boost::asio::steady_timer timer_;
|
||||
std::atomic_bool stopping_{false};
|
||||
std::binary_semaphore semaphore_{0};
|
||||
std::shared_ptr<std::atomic_bool> stopped_ = std::make_shared<std::atomic_bool>(true);
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -47,6 +48,11 @@ public:
|
||||
*/
|
||||
Repeat(boost::asio::io_context& ioc);
|
||||
|
||||
/**
|
||||
* @brief Destroy the Repeat object
|
||||
*/
|
||||
~Repeat();
|
||||
|
||||
/**
|
||||
* @brief Stop repeating
|
||||
* @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
|
||||
* @note stop() must be called before start() is called for the second time
|
||||
*
|
||||
* @tparam Action The action type
|
||||
* @param interval The interval to repeat
|
||||
@@ -65,7 +72,9 @@ public:
|
||||
void
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -75,9 +84,10 @@ private:
|
||||
startImpl(std::chrono::steady_clock::duration interval, Action&& action)
|
||||
{
|
||||
timer_.expires_after(interval);
|
||||
timer_.async_wait([this, interval, action = std::forward<Action>(action)](auto const&) mutable {
|
||||
if (stopping_) {
|
||||
semaphore_.release();
|
||||
timer_.async_wait([this, interval, stopping = stopped_, action = std::forward<Action>(action)](
|
||||
auto const& errorCode
|
||||
) mutable {
|
||||
if (errorCode or *stopping) {
|
||||
return;
|
||||
}
|
||||
action();
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
using namespace util;
|
||||
using testing::AtLeast;
|
||||
|
||||
struct RepeatTests : SyncAsioContextTest {
|
||||
struct RepeatTest : SyncAsioContextTest {
|
||||
Repeat repeat{ctx};
|
||||
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());
|
||||
EXPECT_CALL(handlerMock, Call).Times(AtLeast(10));
|
||||
runContextFor(std::chrono::milliseconds{20});
|
||||
}
|
||||
|
||||
TEST_F(RepeatTests, StopsOnStop)
|
||||
TEST_F(RepeatTest, StopsOnStop)
|
||||
{
|
||||
withRunningContext([this]() {
|
||||
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]() {
|
||||
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());
|
||||
EXPECT_CALL(handlerMock, Call).Times(AtLeast(1));
|
||||
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