36class CacheLoaderImpl {
40 std::shared_ptr<BackendInterface> backend_;
41 std::reference_wrapper<CacheType> cache_;
44 std::atomic_int16_t remaining_;
46 std::chrono::steady_clock::time_point startTime_ = std::chrono::steady_clock::now();
47 std::vector<util::async::AnyOperation<void>> tasks_;
50 template <
typename CtxType>
53 std::shared_ptr<BackendInterface> backend,
56 std::size_t
const numCacheMarkers,
57 std::size_t
const cachePageFetchSize,
58 std::vector<CursorPair>
const& cursors
61 , backend_{std::move(backend)}
62 , cache_{std::ref(cache)}
63 , queue_{cursors.size()}
64 , remaining_{cursors.size()}
66 std::ranges::for_each(cursors, [
this](
auto const& cursor) { queue_.push(cursor); });
67 load(seq, numCacheMarkers, cachePageFetchSize);
79 for (
auto& t : tasks_)
86 for (
auto& t : tasks_)
92 load(uint32_t
const seq,
size_t numCacheMarkers,
size_t cachePageFetchSize)
94 namespace vs = std::views;
96 LOG(log_.info()) <<
"Loading cache. Num cursors = " << queue_.size();
97 tasks_.reserve(numCacheMarkers);
99 for ([[maybe_unused]]
auto taskId : vs::iota(0u, numCacheMarkers))
100 tasks_.push_back(spawnWorker(seq, cachePageFetchSize));
104 spawnWorker(uint32_t
const seq,
size_t cachePageFetchSize)
106 return ctx_.execute([
this, seq, cachePageFetchSize](
auto token) {
107 runGuarded([
this, seq, cachePageFetchSize, token] {
108 loadCacheFromCursors(token, seq, cachePageFetchSize);
113 template <
typename Work>
115 runGuarded(Work&& work)
117 std::optional<std::string> failure;
119 std::forward<Work>(work)();
120 }
catch (std::exception
const& e) {
121 failure = fmt::format(
"Cache loading failed: {}", e.what());
123 failure =
"Cache loading failed with an unknown (non-std) error";
126 if (failure.has_value()) {
127 LOG(log_.error()) << *failure
128 <<
"; disabling cache and continuing without it (reads will be "
129 "served from the database).";
130 cache_.get().setDisabled();
134 template <
typename TokenType>
136 loadCacheFromCursors(TokenType token, uint32_t
const seq,
size_t cachePageFetchSize)
138 while (not token.isStopRequested() and not cache_.get().isDisabled()) {
139 auto cursor = queue_.tryPop();
140 if (not cursor.has_value()) {
144 auto [start, end] = *cursor;
145 LOG(log_.debug()) <<
"Starting a cursor: " << xrpl::strHex(start);
147 while (not token.isStopRequested() and not cache_.get().isDisabled()) {
149 [
this, seq, cachePageFetchSize, &start, token]() {
150 return backend_->fetchLedgerPage(
151 start, seq, cachePageFetchSize,
false, token
156 .initial = backend_->initialRetryDelay(), .max = backend_->maxRetryDelay()
160 cache_.get().update(res.objects, seq,
true);
162 if (not res.cursor or res.cursor > end) {
163 if (--remaining_ <= 0) {
164 auto endTime = std::chrono::steady_clock::now();
166 std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime_);
169 <<
"Finished loading cache. Cache size = " << cache_.get().size()
170 <<
". Took " << duration.count() <<
" seconds";
172 cache_.get().setFull();
174 LOG(log_.debug()) <<
"Finished a cursor. Remaining = " << remaining_;
180 start = *std::move(res.cursor);
186 friend struct ::CacheLoaderImplTests;
A type-erased execution context.
Definition AnyExecutionContext.hpp:23
auto retryOnTimeout(FnType func, boost::asio::yield_context yield, util::Retry::Delays delays=kDefaultRetryDelays)
Retry func while it throws DatabaseError, suspending the calling coroutine in between.
Definition BackendInterface.hpp:95