Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
CacheLoader.hpp
1#pragma once
2
3#include "data/BackendInterface.hpp"
4#include "etl/ETLHelpers.hpp"
5#include "etl/impl/BaseCursorProvider.hpp"
6#include "util/async/AnyExecutionContext.hpp"
7#include "util/async/AnyOperation.hpp"
8#include "util/log/Logger.hpp"
9
10#include <boost/algorithm/string/predicate.hpp>
11#include <boost/context/detail/config.hpp>
12#include <fmt/format.h>
13#include <xrpl/basics/Blob.h>
14#include <xrpl/basics/base_uint.h>
15#include <xrpl/basics/strHex.h>
16
17#include <algorithm>
18#include <atomic>
19#include <chrono>
20#include <cstddef>
21#include <cstdint>
22#include <exception>
23#include <functional>
24#include <memory>
25#include <optional>
26#include <ranges>
27#include <string>
28#include <utility>
29#include <vector>
30
31struct CacheLoaderImplTests;
32
33namespace etl::impl {
34
35template <typename CacheType>
36class CacheLoaderImpl {
37 util::Logger log_{"ETL"};
38
40 std::shared_ptr<BackendInterface> backend_;
41 std::reference_wrapper<CacheType> cache_;
42
44 std::atomic_int16_t remaining_;
45
46 std::chrono::steady_clock::time_point startTime_ = std::chrono::steady_clock::now();
47 std::vector<util::async::AnyOperation<void>> tasks_;
48
49public:
50 template <typename CtxType>
51 CacheLoaderImpl(
52 CtxType& ctx,
53 std::shared_ptr<BackendInterface> backend,
54 CacheType& cache,
55 uint32_t const seq,
56 std::size_t const numCacheMarkers,
57 std::size_t const cachePageFetchSize,
58 std::vector<CursorPair> const& cursors
59 )
60 : ctx_{ctx}
61 , backend_{std::move(backend)}
62 , cache_{std::ref(cache)}
63 , queue_{cursors.size()}
64 , remaining_{cursors.size()}
65 {
66 std::ranges::for_each(cursors, [this](auto const& cursor) { queue_.push(cursor); });
67 load(seq, numCacheMarkers, cachePageFetchSize);
68 }
69
70 ~CacheLoaderImpl()
71 {
72 stop();
73 wait();
74 }
75
76 void
77 stop() noexcept
78 {
79 for (auto& t : tasks_)
80 t.abort();
81 }
82
83 void
84 wait() noexcept
85 {
86 for (auto& t : tasks_)
87 t.wait();
88 }
89
90private:
91 void
92 load(uint32_t const seq, size_t numCacheMarkers, size_t cachePageFetchSize)
93 {
94 namespace vs = std::views;
95
96 LOG(log_.info()) << "Loading cache. Num cursors = " << queue_.size();
97 tasks_.reserve(numCacheMarkers);
98
99 for ([[maybe_unused]] auto taskId : vs::iota(0u, numCacheMarkers))
100 tasks_.push_back(spawnWorker(seq, cachePageFetchSize));
101 }
102
103 [[nodiscard]] auto
104 spawnWorker(uint32_t const seq, size_t cachePageFetchSize)
105 {
106 return ctx_.execute([this, seq, cachePageFetchSize](auto token) {
107 runGuarded([this, seq, cachePageFetchSize, token] {
108 loadCacheFromCursors(token, seq, cachePageFetchSize);
109 });
110 });
111 }
112
113 template <typename Work>
114 void
115 runGuarded(Work&& work)
116 {
117 std::optional<std::string> failure;
118 try {
119 std::forward<Work>(work)();
120 } catch (std::exception const& e) {
121 failure = fmt::format("Cache loading failed: {}", e.what());
122 } catch (...) {
123 failure = "Cache loading failed with an unknown (non-std) error";
124 }
125
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();
131 }
132 }
133
134 template <typename TokenType>
135 void
136 loadCacheFromCursors(TokenType token, uint32_t const seq, size_t cachePageFetchSize)
137 {
138 while (not token.isStopRequested() and not cache_.get().isDisabled()) {
139 auto cursor = queue_.tryPop();
140 if (not cursor.has_value()) {
141 return; // queue is empty
142 }
143
144 auto [start, end] = *cursor;
145 LOG(log_.debug()) << "Starting a cursor: " << xrpl::strHex(start);
146
147 while (not token.isStopRequested() and not cache_.get().isDisabled()) {
148 auto res = data::retryOnTimeout([this, seq, cachePageFetchSize, &start, token]() {
149 return backend_->fetchLedgerPage(start, seq, cachePageFetchSize, false, token);
150 });
151
152 cache_.get().update(res.objects, seq, true);
153
154 if (not res.cursor or res.cursor > end) {
155 if (--remaining_ <= 0) {
156 auto endTime = std::chrono::steady_clock::now();
157 auto duration =
158 std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime_);
159
160 LOG(log_.info())
161 << "Finished loading cache. Cache size = " << cache_.get().size()
162 << ". Took " << duration.count() << " seconds";
163
164 cache_.get().setFull();
165 } else {
166 LOG(log_.debug()) << "Finished a cursor. Remaining = " << remaining_;
167 }
168
169 break; // pick up the next cursor if available
170 }
171
172 start = *std::move(res.cursor);
173 }
174 }
175 }
176
177 // Grants tests access to the private guard/loading members above.
178 friend struct ::CacheLoaderImplTests;
179};
180
181} // namespace etl::impl
Generic thread-safe queue with a max capacity.
Definition ETLHelpers.hpp:26
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:78
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