mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-01 17:15:52 +00:00
feat: ETLng cleanup and graceful shutdown (#2232)
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include "etlng/ETLService.hpp"
|
||||
#include "etlng/ExtractorInterface.hpp"
|
||||
#include "etlng/InitialLoadObserverInterface.hpp"
|
||||
#include "etlng/LoadBalancerInterface.hpp"
|
||||
#include "etlng/LoaderInterface.hpp"
|
||||
#include "etlng/Models.hpp"
|
||||
#include "etlng/MonitorInterface.hpp"
|
||||
@@ -100,7 +101,7 @@ struct MockExtractor : etlng::ExtractorInterface {
|
||||
};
|
||||
|
||||
struct MockLoader : etlng::LoaderInterface {
|
||||
using ExpectedType = std::expected<void, etlng::Error>;
|
||||
using ExpectedType = std::expected<void, etlng::LoaderError>;
|
||||
MOCK_METHOD(ExpectedType, load, (etlng::model::LedgerData const&), (override));
|
||||
MOCK_METHOD(std::optional<ripple::LedgerHeader>, loadInitialLedger, (etlng::model::LedgerData const&), (override));
|
||||
};
|
||||
@@ -488,9 +489,7 @@ TEST_F(ETLServiceTests, GiveUpWriterAfterWriteConflict)
|
||||
EXPECT_FALSE(systemState_->writeConflict); // and removes write conflict flag
|
||||
}
|
||||
|
||||
struct ETLServiceAssertTests : common::util::WithMockAssert, ETLServiceTests {};
|
||||
|
||||
TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger)
|
||||
TEST_F(ETLServiceTests, CancelledLoadInitialLedger)
|
||||
{
|
||||
EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt));
|
||||
EXPECT_CALL(*ledgers_, getMostRecent()).WillRepeatedly(testing::Return(kSEQ));
|
||||
@@ -501,10 +500,10 @@ TEST_F(ETLServiceAssertTests, FailToLoadInitialLedger)
|
||||
EXPECT_CALL(*loader_, loadInitialLedger).Times(0);
|
||||
EXPECT_CALL(*taskManagerProvider_, make).Times(0);
|
||||
|
||||
EXPECT_CLIO_ASSERT_FAIL({ service_.run(); });
|
||||
service_.run();
|
||||
}
|
||||
|
||||
TEST_F(ETLServiceAssertTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadInitialLedger)
|
||||
TEST_F(ETLServiceTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadInitialLedger)
|
||||
{
|
||||
testing::Sequence const s;
|
||||
EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt));
|
||||
@@ -517,5 +516,27 @@ TEST_F(ETLServiceAssertTests, WaitForValidatedLedgerIsAbortedLeadToFailToLoadIni
|
||||
EXPECT_CALL(*loader_, loadInitialLedger).Times(0);
|
||||
EXPECT_CALL(*taskManagerProvider_, make).Times(0);
|
||||
|
||||
EXPECT_CLIO_ASSERT_FAIL({ service_.run(); });
|
||||
service_.run();
|
||||
}
|
||||
|
||||
TEST_F(ETLServiceTests, RunStopsIfInitialLoadIsCancelledByBalancer)
|
||||
{
|
||||
constexpr uint32_t kMOCK_START_SEQUENCE = 123u;
|
||||
systemState_->isStrictReadonly = false;
|
||||
|
||||
testing::Sequence const s;
|
||||
EXPECT_CALL(*backend_, hardFetchLedgerRange).WillOnce(testing::Return(std::nullopt));
|
||||
EXPECT_CALL(*ledgers_, getMostRecent).InSequence(s).WillOnce(testing::Return(kMOCK_START_SEQUENCE));
|
||||
EXPECT_CALL(*ledgers_, getMostRecent).InSequence(s).WillOnce(testing::Return(kMOCK_START_SEQUENCE + 10));
|
||||
|
||||
auto const dummyLedgerData = createTestData(kMOCK_START_SEQUENCE);
|
||||
EXPECT_CALL(*extractor_, extractLedgerOnly(kMOCK_START_SEQUENCE)).WillOnce(testing::Return(dummyLedgerData));
|
||||
EXPECT_CALL(*balancer_, loadInitialLedger(testing::_, testing::_, testing::_))
|
||||
.WillOnce(testing::Return(std::unexpected{etlng::InitialLedgerLoadError::Cancelled}));
|
||||
|
||||
service_.run();
|
||||
|
||||
EXPECT_TRUE(systemState_->isWriting);
|
||||
EXPECT_FALSE(service_.isAmendmentBlocked());
|
||||
EXPECT_FALSE(service_.isCorruptionDetected());
|
||||
}
|
||||
|
||||
@@ -21,14 +21,17 @@
|
||||
#include "etl/ETLHelpers.hpp"
|
||||
#include "etl/impl/GrpcSource.hpp"
|
||||
#include "etlng/InitialLoadObserverInterface.hpp"
|
||||
#include "etlng/LoadBalancerInterface.hpp"
|
||||
#include "etlng/Models.hpp"
|
||||
#include "etlng/impl/GrpcSource.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/Assert.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/MockXrpLedgerAPIService.hpp"
|
||||
#include "util/Mutex.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <grpcpp/server_context.h>
|
||||
#include <grpcpp/support/status.h>
|
||||
@@ -39,9 +42,11 @@
|
||||
#include <xrpl/basics/strHex.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -62,7 +67,7 @@ struct MockLoadObserver : etlng::InitialLoadObserverInterface {
|
||||
);
|
||||
};
|
||||
|
||||
struct GrpcSourceNgTests : NoLoggerFixture, tests::util::WithMockXrpLedgerAPIService {
|
||||
struct GrpcSourceNgTests : virtual NoLoggerFixture, tests::util::WithMockXrpLedgerAPIService {
|
||||
GrpcSourceNgTests()
|
||||
: WithMockXrpLedgerAPIService("localhost:0"), grpcSource_("localhost", std::to_string(getXRPLMockPort()))
|
||||
{
|
||||
@@ -184,9 +189,8 @@ TEST_F(GrpcSourceNgLoadInitialLedgerTests, GetLedgerDataNotFound)
|
||||
return grpc::Status{grpc::StatusCode::NOT_FOUND, "Not found"};
|
||||
});
|
||||
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_);
|
||||
EXPECT_TRUE(data.empty());
|
||||
EXPECT_FALSE(success);
|
||||
auto const res = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST_F(GrpcSourceNgLoadInitialLedgerTests, ObserverCalledCorrectly)
|
||||
@@ -219,12 +223,12 @@ TEST_F(GrpcSourceNgLoadInitialLedgerTests, ObserverCalledCorrectly)
|
||||
EXPECT_EQ(data.size(), 1);
|
||||
});
|
||||
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_);
|
||||
auto const res = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_);
|
||||
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_EQ(data.size(), numMarkers_);
|
||||
EXPECT_TRUE(res.has_value());
|
||||
EXPECT_EQ(res.value().size(), numMarkers_);
|
||||
|
||||
EXPECT_EQ(data, std::vector<std::string>(4, keyStr));
|
||||
EXPECT_EQ(res.value(), std::vector<std::string>(4, keyStr));
|
||||
}
|
||||
|
||||
TEST_F(GrpcSourceNgLoadInitialLedgerTests, DataTransferredAndObserverCalledCorrectly)
|
||||
@@ -284,12 +288,73 @@ TEST_F(GrpcSourceNgLoadInitialLedgerTests, DataTransferredAndObserverCalledCorre
|
||||
total += data.size();
|
||||
});
|
||||
|
||||
auto const [data, success] = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_);
|
||||
auto const res = grpcSource_.loadInitialLedger(sequence_, numMarkers_, observer_);
|
||||
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_EQ(data.size(), numMarkers_);
|
||||
EXPECT_TRUE(res.has_value());
|
||||
EXPECT_EQ(res.value().size(), numMarkers_);
|
||||
EXPECT_EQ(total, totalKeys);
|
||||
EXPECT_EQ(totalWithLastKey + totalWithoutLastKey, numMarkers_ * batchesPerMarker);
|
||||
EXPECT_EQ(totalWithoutLastKey, numMarkers_);
|
||||
EXPECT_EQ(totalWithLastKey, (numMarkers_ - 1) * batchesPerMarker);
|
||||
}
|
||||
|
||||
struct GrpcSourceStopTests : GrpcSourceNgTests, SyncAsioContextTest {};
|
||||
|
||||
TEST_F(GrpcSourceStopTests, LoadInitialLedgerStopsWhenRequested)
|
||||
{
|
||||
uint32_t const sequence = 123u;
|
||||
uint32_t const numMarkers = 1;
|
||||
|
||||
std::mutex mtx;
|
||||
std::condition_variable cvGrpcCallActive;
|
||||
std::condition_variable cvStopCalled;
|
||||
bool grpcCallIsActive = false;
|
||||
bool stopHasBeenCalled = false;
|
||||
|
||||
EXPECT_CALL(mockXrpLedgerAPIService, GetLedgerData)
|
||||
.WillOnce([&](grpc::ServerContext*,
|
||||
org::xrpl::rpc::v1::GetLedgerDataRequest const* request,
|
||||
org::xrpl::rpc::v1::GetLedgerDataResponse* response) {
|
||||
EXPECT_EQ(request->ledger().sequence(), sequence);
|
||||
EXPECT_EQ(request->user(), "ETL");
|
||||
|
||||
{
|
||||
std::unique_lock lk(mtx);
|
||||
grpcCallIsActive = true;
|
||||
}
|
||||
cvGrpcCallActive.notify_one();
|
||||
|
||||
{
|
||||
std::unique_lock lk(mtx);
|
||||
cvStopCalled.wait(lk, [&] { return stopHasBeenCalled; });
|
||||
}
|
||||
|
||||
response->set_is_unlimited(true);
|
||||
return grpc::Status::OK;
|
||||
});
|
||||
|
||||
EXPECT_CALL(observer_, onInitialLoadGotMoreObjects).Times(0);
|
||||
|
||||
auto loadTask = std::async(std::launch::async, [&]() {
|
||||
return grpcSource_.loadInitialLedger(sequence, numMarkers, observer_);
|
||||
});
|
||||
|
||||
{
|
||||
std::unique_lock lk(mtx);
|
||||
cvGrpcCallActive.wait(lk, [&] { return grpcCallIsActive; });
|
||||
}
|
||||
|
||||
runSyncOperation([&](boost::asio::yield_context yield) {
|
||||
grpcSource_.stop(yield);
|
||||
{
|
||||
std::unique_lock lk(mtx);
|
||||
stopHasBeenCalled = true;
|
||||
}
|
||||
cvStopCalled.notify_one();
|
||||
});
|
||||
|
||||
auto const res = loadTask.get();
|
||||
|
||||
ASSERT_FALSE(res.has_value());
|
||||
EXPECT_EQ(res.error(), etlng::InitialLedgerLoadError::Cancelled);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "etlng/InitialLoadObserverInterface.hpp"
|
||||
#include "etlng/LoadBalancer.hpp"
|
||||
#include "etlng/LoadBalancerInterface.hpp"
|
||||
#include "etlng/Models.hpp"
|
||||
#include "etlng/Source.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
@@ -459,7 +460,7 @@ struct LoadBalancerLoadInitialLedgerNgTests : LoadBalancerOnConnectHookNgTests {
|
||||
protected:
|
||||
uint32_t const sequence_ = 123;
|
||||
uint32_t const numMarkers_ = 16;
|
||||
std::pair<std::vector<std::string>, bool> const response_ = {{"1", "2", "3"}, true};
|
||||
InitialLedgerLoadResult const response_{std::vector<std::string>{"1", "2", "3"}};
|
||||
testing::StrictMock<InitialLoadObserverMock> observer_;
|
||||
};
|
||||
|
||||
@@ -469,7 +470,7 @@ TEST_F(LoadBalancerLoadInitialLedgerNgTests, load)
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0DoesntHaveLedger)
|
||||
@@ -479,7 +480,7 @@ TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0DoesntHaveLedger)
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_bothSourcesDontHaveLedger)
|
||||
@@ -489,26 +490,26 @@ TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_bothSourcesDontHaveLedger)
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value());
|
||||
}
|
||||
|
||||
TEST_F(LoadBalancerLoadInitialLedgerNgTests, load_source0ReturnsStatusFalse)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(std::make_pair(std::vector<std::string>{}, false)));
|
||||
.WillOnce(Return(std::unexpected{InitialLedgerLoadError::Errored}));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), hasLedger(sequence_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
EXPECT_EQ(loadBalancer_->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value());
|
||||
}
|
||||
|
||||
struct LoadBalancerLoadInitialLedgerCustomNumMarkersNgTests : LoadBalancerConstructorNgTests {
|
||||
protected:
|
||||
uint32_t const numMarkers_ = 16;
|
||||
uint32_t const sequence_ = 123;
|
||||
std::pair<std::vector<std::string>, bool> const response_ = {{"1", "2", "3"}, true};
|
||||
InitialLedgerLoadResult const response_{std::vector<std::string>{"1", "2", "3"}};
|
||||
testing::StrictMock<InitialLoadObserverMock> observer_;
|
||||
};
|
||||
|
||||
@@ -527,7 +528,7 @@ TEST_F(LoadBalancerLoadInitialLedgerCustomNumMarkersNgTests, loadInitialLedger)
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), loadInitialLedger(sequence_, numMarkers_, testing::_))
|
||||
.WillOnce(Return(response_));
|
||||
|
||||
EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.first);
|
||||
EXPECT_EQ(loadBalancer->loadInitialLedger(sequence_, observer_, std::chrono::milliseconds{1}), response_.value());
|
||||
}
|
||||
|
||||
struct LoadBalancerFetchLegerNgTests : LoadBalancerOnConnectHookNgTests {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "etlng/InitialLoadObserverInterface.hpp"
|
||||
#include "etlng/LoadBalancerInterface.hpp"
|
||||
#include "etlng/Models.hpp"
|
||||
#include "etlng/impl/SourceImpl.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
@@ -33,6 +34,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -51,8 +53,10 @@ struct GrpcSourceMock {
|
||||
using FetchLedgerReturnType = std::pair<grpc::Status, org::xrpl::rpc::v1::GetLedgerResponse>;
|
||||
MOCK_METHOD(FetchLedgerReturnType, fetchLedger, (uint32_t, bool, bool));
|
||||
|
||||
using LoadLedgerReturnType = std::pair<std::vector<std::string>, bool>;
|
||||
using LoadLedgerReturnType = etlng::InitialLedgerLoadResult;
|
||||
MOCK_METHOD(LoadLedgerReturnType, loadInitialLedger, (uint32_t, uint32_t, etlng::InitialLoadObserverInterface&));
|
||||
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||
};
|
||||
|
||||
struct SubscriptionSourceMock {
|
||||
@@ -127,6 +131,7 @@ TEST_F(SourceImplNgTest, run)
|
||||
TEST_F(SourceImplNgTest, stop)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, stop);
|
||||
EXPECT_CALL(grpcSourceMock_, stop);
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) { source_.stop(yield); });
|
||||
ctx.run();
|
||||
@@ -190,7 +195,7 @@ TEST_F(SourceImplNgTest, fetchLedger)
|
||||
EXPECT_EQ(actualStatus.error_code(), grpc::StatusCode::OK);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, loadInitialLedger)
|
||||
TEST_F(SourceImplNgTest, loadInitialLedgerErrorPath)
|
||||
{
|
||||
uint32_t const ledgerSeq = 123;
|
||||
uint32_t const numMarkers = 3;
|
||||
@@ -198,11 +203,25 @@ TEST_F(SourceImplNgTest, loadInitialLedger)
|
||||
auto observerMock = testing::StrictMock<InitialLoadObserverMock>();
|
||||
|
||||
EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_))
|
||||
.WillOnce(Return(std::make_pair(std::vector<std::string>{}, true)));
|
||||
auto const [actualLedgers, actualSuccess] = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock);
|
||||
.WillOnce(Return(std::unexpected{etlng::InitialLedgerLoadError::Errored}));
|
||||
auto const res = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock);
|
||||
|
||||
EXPECT_TRUE(actualLedgers.empty());
|
||||
EXPECT_TRUE(actualSuccess);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, loadInitialLedgerSuccessPath)
|
||||
{
|
||||
uint32_t const ledgerSeq = 123;
|
||||
uint32_t const numMarkers = 3;
|
||||
auto response = etlng::InitialLedgerLoadResult{{"1", "2", "3"}};
|
||||
|
||||
auto observerMock = testing::StrictMock<InitialLoadObserverMock>();
|
||||
|
||||
EXPECT_CALL(grpcSourceMock_, loadInitialLedger(ledgerSeq, numMarkers, testing::_)).WillOnce(Return(response));
|
||||
auto const res = source_.loadInitialLedger(ledgerSeq, numMarkers, observerMock);
|
||||
|
||||
EXPECT_TRUE(res.has_value());
|
||||
EXPECT_EQ(res, response);
|
||||
}
|
||||
|
||||
TEST_F(SourceImplNgTest, forwardToRippled)
|
||||
|
||||
@@ -62,7 +62,7 @@ struct MockExtractor : etlng::ExtractorInterface {
|
||||
};
|
||||
|
||||
struct MockLoader : etlng::LoaderInterface {
|
||||
using ExpectedType = std::expected<void, etlng::Error>;
|
||||
using ExpectedType = std::expected<void, etlng::LoaderError>;
|
||||
MOCK_METHOD(ExpectedType, load, (LedgerData const&), (override));
|
||||
MOCK_METHOD(std::optional<ripple::LedgerHeader>, loadInitialLedger, (LedgerData const&), (override));
|
||||
};
|
||||
@@ -142,11 +142,11 @@ TEST_F(TaskManagerTests, LoaderGetsDataIfNextSequenceIsExtracted)
|
||||
|
||||
EXPECT_CALL(*mockLoaderPtr_, load(testing::_))
|
||||
.Times(kTOTAL)
|
||||
.WillRepeatedly([&](LedgerData data) -> std::expected<void, etlng::Error> {
|
||||
.WillRepeatedly([&](LedgerData data) -> std::expected<void, etlng::LoaderError> {
|
||||
loaded.push_back(data.seq);
|
||||
if (loaded.size() == kTOTAL) {
|
||||
if (loaded.size() == kTOTAL)
|
||||
done.release();
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
@@ -157,9 +157,8 @@ TEST_F(TaskManagerTests, LoaderGetsDataIfNextSequenceIsExtracted)
|
||||
taskManager_.stop();
|
||||
|
||||
EXPECT_EQ(loaded.size(), kTOTAL);
|
||||
for (std::size_t i = 0; i < loaded.size(); ++i) {
|
||||
for (std::size_t i = 0; i < loaded.size(); ++i)
|
||||
EXPECT_EQ(loaded[i], kSEQ + i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TaskManagerTests, WriteConflictHandling)
|
||||
@@ -187,19 +186,17 @@ TEST_F(TaskManagerTests, WriteConflictHandling)
|
||||
|
||||
// First kCONFLICT_AFTER calls succeed, then we get a write conflict
|
||||
EXPECT_CALL(*mockLoaderPtr_, load(testing::_))
|
||||
.WillRepeatedly([&](LedgerData data) -> std::expected<void, etlng::Error> {
|
||||
.WillRepeatedly([&](LedgerData data) -> std::expected<void, etlng::LoaderError> {
|
||||
loaded.push_back(data.seq);
|
||||
|
||||
if (loaded.size() == kCONFLICT_AFTER) {
|
||||
conflictOccurred = true;
|
||||
done.release();
|
||||
return std::unexpected("write conflict");
|
||||
return std::unexpected(etlng::LoaderError::WriteConflict);
|
||||
}
|
||||
|
||||
// Only release semaphore if we reach kTOTAL without conflict
|
||||
if (loaded.size() == kTOTAL) {
|
||||
if (loaded.size() == kTOTAL)
|
||||
done.release();
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
@@ -214,7 +211,59 @@ TEST_F(TaskManagerTests, WriteConflictHandling)
|
||||
EXPECT_EQ(loaded.size(), kCONFLICT_AFTER);
|
||||
EXPECT_TRUE(conflictOccurred);
|
||||
|
||||
for (std::size_t i = 0; i < loaded.size(); ++i) {
|
||||
for (std::size_t i = 0; i < loaded.size(); ++i)
|
||||
EXPECT_EQ(loaded[i], kSEQ + i);
|
||||
}
|
||||
|
||||
TEST_F(TaskManagerTests, AmendmentBlockedHandling)
|
||||
{
|
||||
static constexpr auto kTOTAL = 64uz;
|
||||
static constexpr auto kAMENDMENT_BLOCKED_AFTER = 20uz; // Amendment block after 20 ledgers
|
||||
static constexpr auto kEXTRACTORS = 2uz;
|
||||
|
||||
std::atomic_uint32_t seq = kSEQ;
|
||||
std::vector<uint32_t> loaded;
|
||||
std::binary_semaphore done{0};
|
||||
bool amendmentBlockedOccurred = false;
|
||||
|
||||
EXPECT_CALL(*mockSchedulerPtr_, next()).WillRepeatedly([&]() {
|
||||
return Task{.priority = Task::Priority::Higher, .seq = seq++};
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockExtractorPtr_, extractLedgerWithDiff(testing::_))
|
||||
.WillRepeatedly([](uint32_t seq) -> std::optional<LedgerData> {
|
||||
if (seq > kSEQ + kTOTAL - 1)
|
||||
return std::nullopt;
|
||||
|
||||
return createTestData(seq);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockLoaderPtr_, load(testing::_))
|
||||
.WillRepeatedly([&](LedgerData data) -> std::expected<void, etlng::LoaderError> {
|
||||
loaded.push_back(data.seq);
|
||||
|
||||
if (loaded.size() == kAMENDMENT_BLOCKED_AFTER) {
|
||||
amendmentBlockedOccurred = true;
|
||||
done.release();
|
||||
return std::unexpected(etlng::LoaderError::AmendmentBlocked);
|
||||
}
|
||||
|
||||
if (loaded.size() == kTOTAL)
|
||||
done.release();
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockMonitorPtr_, notifySequenceLoaded(testing::_)).Times(kAMENDMENT_BLOCKED_AFTER - 1);
|
||||
EXPECT_CALL(*mockMonitorPtr_, notifyWriteConflict(testing::_)).Times(0);
|
||||
|
||||
taskManager_.run(kEXTRACTORS);
|
||||
done.acquire();
|
||||
taskManager_.stop();
|
||||
|
||||
EXPECT_EQ(loaded.size(), kAMENDMENT_BLOCKED_AFTER);
|
||||
EXPECT_TRUE(amendmentBlockedOccurred);
|
||||
|
||||
for (std::size_t i = 0; i < loaded.size(); ++i)
|
||||
EXPECT_EQ(loaded[i], kSEQ + i);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user