Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
BlockingCache.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2025, the clio developers.
5
6 Permission to use, copy, modify, and distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#pragma once
21
22#include "util/Assert.hpp"
23#include "util/Mutex.hpp"
24#include "util/Spawn.hpp"
25
26#include <boost/asio/error.hpp>
27#include <boost/asio/spawn.hpp>
28#include <boost/asio/steady_timer.hpp>
29#include <boost/signals2/connection.hpp>
30#include <boost/signals2/signal.hpp>
31#include <boost/signals2/variadic_signal.hpp>
32
33#include <atomic>
34#include <concepts>
35#include <expected>
36#include <functional>
37#include <optional>
38#include <shared_mutex>
39#include <utility>
40
41namespace util {
42
49template <typename ValueType, typename ErrorType>
50 requires(not std::same_as<ValueType, ErrorType>)
52public:
56 enum class State { NoValue, Updating, HasValue };
57
58private:
59 std::atomic<State> state_{State::NoValue};
60 util::Mutex<std::optional<ValueType>, std::shared_mutex> value_;
61 boost::signals2::signal<void(std::expected<ValueType, ErrorType>)> updateFinished_;
62
63public:
67 BlockingCache() = default;
68
73 explicit BlockingCache(ValueType initialValue)
74 : state_{State::HasValue}, value_(std::move(initialValue))
75 {
76 }
77
78 BlockingCache(BlockingCache&&) = delete;
79 BlockingCache(BlockingCache const&) = delete;
81 operator=(BlockingCache&&) = delete;
83 operator=(BlockingCache const&) = delete;
84
89 using Updater = std::function<std::expected<ValueType, ErrorType>(boost::asio::yield_context)>;
90
95 using Verifier = std::function<bool(ValueType const&)>;
96
110 [[nodiscard]] std::expected<ValueType, ErrorType>
111 asyncGet(boost::asio::yield_context yield, Updater updater, Verifier verifier)
112 {
113 switch (state_) {
114 case State::Updating: {
115 return wait(yield, std::move(updater), std::move(verifier));
116 }
117 case State::HasValue: {
118 auto const value = value_.template lock<std::shared_lock>();
119 ASSERT(value->has_value(), "Value should be presented when the cache is full");
120 return value->value();
121 }
122 case State::NoValue: {
123 return update(yield, std::move(updater), std::move(verifier));
124 }
125 };
126 std::unreachable();
127 }
128
140 [[nodiscard]] std::expected<ValueType, ErrorType>
141 update(boost::asio::yield_context yield, Updater updater, Verifier verifier)
142 {
143 if (state_ == State::Updating) {
144 return asyncGet(yield, std::move(updater), std::move(verifier));
145 }
146 state_ = State::Updating;
147
148 auto const result = updater(yield);
149 auto const shouldBeCached = result.has_value() and verifier(result.value());
150
151 if (shouldBeCached) {
152 value_.lock().get() = result.value();
153 state_ = State::HasValue;
154 } else {
155 state_ = State::NoValue;
156 value_.lock().get() = std::nullopt;
157 }
158
159 updateFinished_(result);
160 return result;
161 }
162
169 void
171 {
172 if (state_ == State::HasValue) {
173 state_ = State::NoValue;
174 value_.lock().get() = std::nullopt;
175 }
176 }
177
182 [[nodiscard]] State
183 state() const
184 {
185 return state_;
186 }
187
188private:
199 std::expected<ValueType, ErrorType>
200 wait(boost::asio::yield_context yield, Updater updater, Verifier verifier)
201 {
202 struct SharedContext {
203 SharedContext(boost::asio::yield_context y)
204 : timer(y.get_executor(), boost::asio::steady_timer::duration::max())
205 {
206 }
207
208 boost::asio::steady_timer timer;
209 std::optional<std::expected<ValueType, ErrorType>> result;
210 };
211
212 auto sharedContext = std::make_shared<SharedContext>(yield);
213 boost::system::error_code errorCode;
214
215 boost::signals2::scoped_connection const slot =
216 updateFinished_.connect([yield,
217 sharedContext](std::expected<ValueType, ErrorType> value) {
219 yield,
220 [sharedContext = std::move(sharedContext), value = std::move(value)](auto&&) {
221 sharedContext->result = std::move(value);
222 sharedContext->timer.cancel();
223 }
224 );
225 });
226
227 if (state_ == State::Updating) {
228 sharedContext->timer.async_wait(yield[errorCode]);
229 ASSERT(sharedContext->result.has_value(), "There should be some value after waiting");
230 return std::move(sharedContext->result).value();
231 }
232 return asyncGet(yield, std::move(updater), std::move(verifier));
233 }
234};
235
236} // namespace util
A thread-safe cache that blocks getting operations until the cache is updated.
Definition BlockingCache.hpp:51
void invalidate()
Invalidates the currently cached value if present.
Definition BlockingCache.hpp:170
BlockingCache(ValueType initialValue)
Construct a cache with an initial value.
Definition BlockingCache.hpp:73
std::expected< ValueType, ErrorType > asyncGet(boost::asio::yield_context yield, Updater updater, Verifier verifier)
Asynchronously get a value from the cache, updating if necessary.
Definition BlockingCache.hpp:111
State state() const
Returns the current state of the cache.
Definition BlockingCache.hpp:183
State
Possible states of the cache.
Definition BlockingCache.hpp:56
std::function< std::expected< ValueType, ErrorType >(boost::asio::yield_context)> Updater
Function type for cache update operations.
Definition BlockingCache.hpp:89
BlockingCache()=default
Default constructor - creates an empty cache.
std::expected< ValueType, ErrorType > update(boost::asio::yield_context yield, Updater updater, Verifier verifier)
Force an update of the cache value.
Definition BlockingCache.hpp:141
std::function< bool(ValueType const &)> Verifier
Function type to verify if a value should be cached.
Definition BlockingCache.hpp:95
A container for data that is protected by a mutex. Inspired by Mutex in Rust.
Definition Mutex.hpp:101
This namespace contains various utilities.
Definition AccountUtils.hpp:30
void spawn(Ctx &&ctx, F &&func)
Spawns a coroutine using boost::asio::spawn.
Definition Spawn.hpp:72