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 return backend_->fetchLedgerPage(start, seq, cachePageFetchSize,
false, token);
152 cache_.get().update(res.objects, seq,
true);
154 if (not res.cursor or res.cursor > end) {
155 if (--remaining_ <= 0) {
156 auto endTime = std::chrono::steady_clock::now();
158 std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime_);
161 <<
"Finished loading cache. Cache size = " << cache_.get().size()
162 <<
". Took " << duration.count() <<
" seconds";
164 cache_.get().setFull();
166 LOG(log_.debug()) <<
"Finished a cursor. Remaining = " << remaining_;
172 start = *std::move(res.cursor);
178 friend struct ::CacheLoaderImplTests;
A type-erased execution context.
Definition AnyExecutionContext.hpp:23
auto retryOnTimeout(FnType func, size_t waitMs=kDefaultWaitBetweenRetry)
A helper function that catches DatabaseTimeout exceptions and retries indefinitely.
Definition BackendInterface.hpp:63