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) : state_{State::HasValue}, value_(std::move(initialValue))
74 {
75 }
76
77 BlockingCache(BlockingCache&&) = delete;
78 BlockingCache(BlockingCache const&) = delete;
80 operator=(BlockingCache&&) = delete;
82 operator=(BlockingCache const&) = delete;
83
88 using Updater = std::function<std::expected<ValueType, ErrorType>(boost::asio::yield_context)>;
89
94 using Verifier = std::function<bool(ValueType const&)>;
95
109 [[nodiscard]] std::expected<ValueType, ErrorType>
110 asyncGet(boost::asio::yield_context yield, Updater updater, Verifier verifier)
111 {
112 switch (state_) {
113 case State::Updating: {
114 return wait(yield, std::move(updater), std::move(verifier));
115 }
116 case State::HasValue: {
117 auto const value = value_.template lock<std::shared_lock>();
118 ASSERT(value->has_value(), "Value should be presented when the cache is full");
119 return value->value();
120 }
121 case State::NoValue: {
122 return update(yield, std::move(updater), std::move(verifier));
123 }
124 };
125 std::unreachable();
126 }
127
139 [[nodiscard]] std::expected<ValueType, ErrorType>
140 update(boost::asio::yield_context yield, Updater updater, Verifier verifier)
141 {
142 if (state_ == State::Updating) {
143 return asyncGet(yield, std::move(updater), std::move(verifier));
144 }
145 state_ = State::Updating;
146
147 auto const result = updater(yield);
148 auto const shouldBeCached = result.has_value() and verifier(result.value());
149
150 if (shouldBeCached) {
151 value_.lock().get() = result.value();
152 state_ = State::HasValue;
153 } else {
154 state_ = State::NoValue;
155 value_.lock().get() = std::nullopt;
156 }
157
158 updateFinished_(result);
159 return result;
160 }
161
168 void
170 {
171 if (state_ == State::HasValue) {
172 state_ = State::NoValue;
173 value_.lock().get() = std::nullopt;
174 }
175 }
176
181 [[nodiscard]] State
182 state() const
183 {
184 return state_;
185 }
186
187private:
198 std::expected<ValueType, ErrorType>
199 wait(boost::asio::yield_context yield, Updater updater, Verifier verifier)
200 {
201 struct SharedContext {
202 SharedContext(boost::asio::yield_context y)
203 : timer(y.get_executor(), boost::asio::steady_timer::duration::max())
204 {
205 }
206
207 boost::asio::steady_timer timer;
208 std::optional<std::expected<ValueType, ErrorType>> result;
209 };
210
211 auto sharedContext = std::make_shared<SharedContext>(yield);
212 boost::system::error_code errorCode;
213
214 boost::signals2::scoped_connection const slot =
215 updateFinished_.connect([yield, sharedContext](std::expected<ValueType, ErrorType> value) {
216 util::spawn(yield, [sharedContext = std::move(sharedContext), value = std::move(value)](auto&&) {
217 sharedContext->result = std::move(value);
218 sharedContext->timer.cancel();
219 });
220 });
221
222 if (state_ == State::Updating) {
223 sharedContext->timer.async_wait(yield[errorCode]);
224 ASSERT(sharedContext->result.has_value(), "There should be some value after waiting");
225 return std::move(sharedContext->result).value();
226 }
227 return asyncGet(yield, std::move(updater), std::move(verifier));
228 }
229};
230
231} // 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:169
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:110
State state() const
Returns the current state of the cache.
Definition BlockingCache.hpp:182
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:88
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:140
std::function< bool(ValueType const &)> Verifier
Function type to verify if a value should be cached.
Definition BlockingCache.hpp:94
A container for data that is protected by a mutex. Inspired by Mutex in Rust.
Definition Mutex.hpp:96
Lock< ProtectedDataType const, LockType, MutexType > lock() const
Lock the mutex and get a lock object allowing access to the protected data.
Definition Mutex.hpp:134
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:69