mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-05 04:15:51 +00:00
78
src/util/MoveTracker.hpp
Normal file
78
src/util/MoveTracker.hpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2025, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A base-class that can be used to check whether the current instance was moved from
|
||||||
|
*/
|
||||||
|
class MoveTracker {
|
||||||
|
bool wasMoved_ = false;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief The function to be used by clients in order to check whether the instance was moved from
|
||||||
|
* @return true if moved from; false otherwise
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool
|
||||||
|
wasMoved() const noexcept
|
||||||
|
{
|
||||||
|
return wasMoved_;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveTracker() = default; // should only be used via inheritance
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~MoveTracker() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor sets the moved-from state on `other` and resets the state on `this`
|
||||||
|
* @param other The moved-from object
|
||||||
|
*/
|
||||||
|
MoveTracker(MoveTracker&& other)
|
||||||
|
{
|
||||||
|
*this = std::move(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move operator sets the moved-from state on `other` and resets the state on `this`
|
||||||
|
* @param other The moved-from object
|
||||||
|
* @return Reference to self
|
||||||
|
*/
|
||||||
|
MoveTracker&
|
||||||
|
operator=(MoveTracker&& other)
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
other.wasMoved_ = true;
|
||||||
|
wasMoved_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveTracker(MoveTracker const&) = default;
|
||||||
|
MoveTracker&
|
||||||
|
operator=(MoveTracker const&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/MoveTracker.hpp"
|
||||||
#include "util/Repeat.hpp"
|
#include "util/Repeat.hpp"
|
||||||
#include "util/async/Concepts.hpp"
|
#include "util/async/Concepts.hpp"
|
||||||
#include "util/async/Error.hpp"
|
|
||||||
#include "util/async/Outcome.hpp"
|
#include "util/async/Outcome.hpp"
|
||||||
#include "util/async/context/impl/Cancellation.hpp"
|
#include "util/async/context/impl/Cancellation.hpp"
|
||||||
#include "util/async/context/impl/Timer.hpp"
|
#include "util/async/context/impl/Timer.hpp"
|
||||||
@@ -36,7 +36,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace util::async {
|
namespace util::async {
|
||||||
namespace impl {
|
namespace impl {
|
||||||
@@ -71,7 +70,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename CtxType, typename OpType>
|
template <typename CtxType, typename OpType>
|
||||||
struct BasicScheduledOperation {
|
struct BasicScheduledOperation : util::MoveTracker {
|
||||||
class State {
|
class State {
|
||||||
std::mutex m_;
|
std::mutex m_;
|
||||||
std::condition_variable ready_;
|
std::condition_variable ready_;
|
||||||
@@ -105,6 +104,19 @@ struct BasicScheduledOperation {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~BasicScheduledOperation()
|
||||||
|
{
|
||||||
|
if (not wasMoved())
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicScheduledOperation(BasicScheduledOperation const&) = default;
|
||||||
|
BasicScheduledOperation&
|
||||||
|
operator=(BasicScheduledOperation const&) = default;
|
||||||
|
BasicScheduledOperation(BasicScheduledOperation&&) = default;
|
||||||
|
BasicScheduledOperation&
|
||||||
|
operator=(BasicScheduledOperation&&) = default;
|
||||||
|
|
||||||
[[nodiscard]] auto
|
[[nodiscard]] auto
|
||||||
get()
|
get()
|
||||||
{
|
{
|
||||||
@@ -149,7 +161,8 @@ struct BasicScheduledOperation {
|
|||||||
* @tparam StopSourceType The type of the stop source
|
* @tparam StopSourceType The type of the stop source
|
||||||
*/
|
*/
|
||||||
template <typename RetType, typename StopSourceType>
|
template <typename RetType, typename StopSourceType>
|
||||||
class StoppableOperation : public impl::BasicOperation<StoppableOutcome<RetType, StopSourceType>> {
|
class StoppableOperation : public impl::BasicOperation<StoppableOutcome<RetType, StopSourceType>>,
|
||||||
|
public util::MoveTracker {
|
||||||
using OutcomeType = StoppableOutcome<RetType, StopSourceType>;
|
using OutcomeType = StoppableOutcome<RetType, StopSourceType>;
|
||||||
|
|
||||||
StopSourceType stopSource_;
|
StopSourceType stopSource_;
|
||||||
@@ -165,6 +178,19 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~StoppableOperation()
|
||||||
|
{
|
||||||
|
if (not wasMoved())
|
||||||
|
requestStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
StoppableOperation(StoppableOperation const&) = delete;
|
||||||
|
StoppableOperation&
|
||||||
|
operator=(StoppableOperation const&) = delete;
|
||||||
|
StoppableOperation(StoppableOperation&&) = default;
|
||||||
|
StoppableOperation&
|
||||||
|
operator=(StoppableOperation&&) = default;
|
||||||
|
|
||||||
/** @brief Requests the operation to stop */
|
/** @brief Requests the operation to stop */
|
||||||
void
|
void
|
||||||
requestStop() noexcept
|
requestStop() noexcept
|
||||||
@@ -199,7 +225,7 @@ using ScheduledOperation = impl::BasicScheduledOperation<CtxType, OpType>;
|
|||||||
* @tparam CtxType The type of the execution context
|
* @tparam CtxType The type of the execution context
|
||||||
*/
|
*/
|
||||||
template <typename CtxType>
|
template <typename CtxType>
|
||||||
class RepeatingOperation {
|
class RepeatingOperation : public util::MoveTracker {
|
||||||
util::Repeat repeat_;
|
util::Repeat repeat_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -217,6 +243,12 @@ public:
|
|||||||
repeat_.start(interval, std::forward<decltype(fn)>(fn));
|
repeat_.start(interval, std::forward<decltype(fn)>(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~RepeatingOperation()
|
||||||
|
{
|
||||||
|
if (not wasMoved())
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
RepeatingOperation(RepeatingOperation const&) = delete;
|
RepeatingOperation(RepeatingOperation const&) = delete;
|
||||||
RepeatingOperation&
|
RepeatingOperation&
|
||||||
operator=(RepeatingOperation const&) = delete;
|
operator=(RepeatingOperation const&) = delete;
|
||||||
|
|||||||
@@ -114,8 +114,6 @@ target_sources(
|
|||||||
rpc/RPCHelpersTests.cpp
|
rpc/RPCHelpersTests.cpp
|
||||||
rpc/WorkQueueTests.cpp
|
rpc/WorkQueueTests.cpp
|
||||||
test_data/SslCert.cpp
|
test_data/SslCert.cpp
|
||||||
util/AccountUtilsTests.cpp
|
|
||||||
util/AssertTests.cpp
|
|
||||||
# Async framework
|
# Async framework
|
||||||
util/async/AnyExecutionContextTests.cpp
|
util/async/AnyExecutionContextTests.cpp
|
||||||
util/async/AnyOperationTests.cpp
|
util/async/AnyOperationTests.cpp
|
||||||
@@ -140,6 +138,10 @@ target_sources(
|
|||||||
util/requests/RequestBuilderTests.cpp
|
util/requests/RequestBuilderTests.cpp
|
||||||
util/requests/SslContextTests.cpp
|
util/requests/SslContextTests.cpp
|
||||||
util/requests/WsConnectionTests.cpp
|
util/requests/WsConnectionTests.cpp
|
||||||
|
# Common utils
|
||||||
|
util/AccountUtilsTests.cpp
|
||||||
|
util/AssertTests.cpp
|
||||||
|
util/MoveTrackerTests.cpp
|
||||||
util/RandomTests.cpp
|
util/RandomTests.cpp
|
||||||
util/RetryTests.cpp
|
util/RetryTests.cpp
|
||||||
util/RepeatTests.cpp
|
util/RepeatTests.cpp
|
||||||
|
|||||||
68
tests/unit/util/MoveTrackerTests.cpp
Normal file
68
tests/unit/util/MoveTrackerTests.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2025, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include "util/MoveTracker.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct MoveMe : util::MoveTracker {
|
||||||
|
using MoveTracker::wasMoved; // expose as public for tests
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(MoveTrackerTests, SimpleChecks)
|
||||||
|
{
|
||||||
|
auto moveMe = MoveMe();
|
||||||
|
EXPECT_FALSE(moveMe.wasMoved());
|
||||||
|
|
||||||
|
auto other = std::move(moveMe);
|
||||||
|
EXPECT_TRUE(moveMe.wasMoved());
|
||||||
|
EXPECT_FALSE(other.wasMoved());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MoveTrackerTests, SupportReuse)
|
||||||
|
{
|
||||||
|
auto original = MoveMe();
|
||||||
|
auto other = std::move(original);
|
||||||
|
|
||||||
|
original = std::move(other);
|
||||||
|
EXPECT_FALSE(original.wasMoved());
|
||||||
|
EXPECT_TRUE(other.wasMoved());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MoveTrackerTests, SelfMove)
|
||||||
|
{
|
||||||
|
auto original = MoveMe();
|
||||||
|
[&](MoveMe& from) { original = std::move(from); }(original); // avoids the compiler catching self-move
|
||||||
|
|
||||||
|
EXPECT_FALSE(original.wasMoved());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MoveTrackerTests, SelfMoveAfterWasMoved)
|
||||||
|
{
|
||||||
|
auto original = MoveMe();
|
||||||
|
[[maybe_unused]] auto fake = std::move(original);
|
||||||
|
|
||||||
|
[&](MoveMe& from) { original = std::move(from); }(original); // avoids the compiler catching self-move
|
||||||
|
|
||||||
|
EXPECT_TRUE(original.wasMoved());
|
||||||
|
}
|
||||||
@@ -34,8 +34,6 @@
|
|||||||
using namespace util::async;
|
using namespace util::async;
|
||||||
using ::testing::Types;
|
using ::testing::Types;
|
||||||
|
|
||||||
using ExecutionContextTypes = Types<CoroExecutionContext, PoolExecutionContext, SyncExecutionContext>;
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct ExecutionContextTests : public ::testing::Test {
|
struct ExecutionContextTests : public ::testing::Test {
|
||||||
using ExecutionContextType = T;
|
using ExecutionContextType = T;
|
||||||
@@ -48,7 +46,15 @@ struct ExecutionContextTests : public ::testing::Test {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Suite for tests to be ran against all context types but SyncExecutionContext
|
||||||
|
template <typename T>
|
||||||
|
using AsyncExecutionContextTests = ExecutionContextTests<T>;
|
||||||
|
|
||||||
|
using ExecutionContextTypes = Types<CoroExecutionContext, PoolExecutionContext, SyncExecutionContext>;
|
||||||
|
using AsyncExecutionContextTypes = Types<CoroExecutionContext, PoolExecutionContext>;
|
||||||
|
|
||||||
TYPED_TEST_CASE(ExecutionContextTests, ExecutionContextTypes);
|
TYPED_TEST_CASE(ExecutionContextTests, ExecutionContextTypes);
|
||||||
|
TYPED_TEST_CASE(AsyncExecutionContextTests, AsyncExecutionContextTypes);
|
||||||
|
|
||||||
TYPED_TEST(ExecutionContextTests, move)
|
TYPED_TEST(ExecutionContextTests, move)
|
||||||
{
|
{
|
||||||
@@ -149,6 +155,26 @@ TYPED_TEST(ExecutionContextTests, timerCancel)
|
|||||||
EXPECT_EQ(value, 42);
|
EXPECT_EQ(value, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(ExecutionContextTests, timerAutoCancels)
|
||||||
|
{
|
||||||
|
auto value = 0;
|
||||||
|
std::binary_semaphore sem{0};
|
||||||
|
{
|
||||||
|
auto res = this->ctx.scheduleAfter(
|
||||||
|
std::chrono::milliseconds(1),
|
||||||
|
[&value, &sem]([[maybe_unused]] auto stopRequested, auto cancelled) {
|
||||||
|
if (cancelled)
|
||||||
|
value = 42;
|
||||||
|
|
||||||
|
sem.release();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} // res goes out of scope and cancels the timer
|
||||||
|
|
||||||
|
sem.acquire();
|
||||||
|
EXPECT_EQ(value, 42);
|
||||||
|
}
|
||||||
|
|
||||||
TYPED_TEST(ExecutionContextTests, timerStdException)
|
TYPED_TEST(ExecutionContextTests, timerStdException)
|
||||||
{
|
{
|
||||||
auto res =
|
auto res =
|
||||||
@@ -247,6 +273,46 @@ TYPED_TEST(ExecutionContextTests, strandWithTimeout)
|
|||||||
EXPECT_EQ(res.get().value(), 42);
|
EXPECT_EQ(res.get().value(), 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(AsyncExecutionContextTests, executeAutoAborts)
|
||||||
|
{
|
||||||
|
auto value = 0;
|
||||||
|
std::binary_semaphore sem{0};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto res = this->ctx.execute([&](auto stopRequested) {
|
||||||
|
while (not stopRequested)
|
||||||
|
;
|
||||||
|
value = 42;
|
||||||
|
sem.release();
|
||||||
|
});
|
||||||
|
} // res goes out of scope and aborts operation
|
||||||
|
|
||||||
|
sem.acquire();
|
||||||
|
EXPECT_EQ(value, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(AsyncExecutionContextTests, repeatingOperationAutoAborts)
|
||||||
|
{
|
||||||
|
auto const repeatDelay = std::chrono::milliseconds{1};
|
||||||
|
auto const timeout = std::chrono::milliseconds{15};
|
||||||
|
auto callCount = 0uz;
|
||||||
|
auto timeSpentMs = 0u;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto res = this->ctx.executeRepeatedly(repeatDelay, [&] { ++callCount; });
|
||||||
|
timeSpentMs = util::timed([timeout] { std::this_thread::sleep_for(timeout); }); // calculate actual time spent
|
||||||
|
} // res goes out of scope and automatically aborts the repeating operation
|
||||||
|
|
||||||
|
// double the delay so that if abort did not happen we will fail below expectations
|
||||||
|
std::this_thread::sleep_for(timeout);
|
||||||
|
|
||||||
|
auto const expectedPureCalls = timeout.count() / repeatDelay.count();
|
||||||
|
auto const expectedActualCount = timeSpentMs / repeatDelay.count();
|
||||||
|
|
||||||
|
EXPECT_GE(callCount, expectedPureCalls / 2u); // expect at least half of the scheduled calls
|
||||||
|
EXPECT_LE(callCount, expectedActualCount); // never should be called more times than possible before timeout
|
||||||
|
}
|
||||||
|
|
||||||
using NoErrorHandlerSyncExecutionContext = BasicExecutionContext<
|
using NoErrorHandlerSyncExecutionContext = BasicExecutionContext<
|
||||||
impl::SameThreadContext,
|
impl::SameThreadContext,
|
||||||
impl::BasicStopSource,
|
impl::BasicStopSource,
|
||||||
|
|||||||
Reference in New Issue
Block a user