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
25#include <boost/asio/error.hpp>
26#include <boost/asio/spawn.hpp>
27#include <boost/asio/steady_timer.hpp>
28#include <boost/signals2/connection.hpp>
29#include <boost/signals2/signal.hpp>
30#include <boost/signals2/variadic_signal.hpp>
31
32#include <atomic>
33#include <concepts>
34#include <expected>
35#include <functional>
36#include <optional>
37#include <shared_mutex>
38#include <utility>
39
40namespace util {
41
48template <typename ValueType, typename ErrorType>
49 requires(not std::same_as<ValueType, ErrorType>)
51public:
55 enum class State { NoValue, Updating, HasValue };
56
57private:
58 std::atomic<State> state_{State::NoValue};
59 util::Mutex<std::optional<ValueType>, std::shared_mutex> value_;
60 boost::signals2::signal<void(std::expected<ValueType, ErrorType>)> updateFinished_;
61
62public:
66 BlockingCache() = default;
67
72 explicit BlockingCache(ValueType initialValue) : state_{State::HasValue}, value_(std::move(initialValue))
73 {
74 }
75
76 BlockingCache(BlockingCache&&) = delete;
77 BlockingCache(BlockingCache const&) = delete;
79 operator=(BlockingCache&&) = delete;
81 operator=(BlockingCache const&) = delete;
82
87 using Updater = std::function<std::expected<ValueType, ErrorType>(boost::asio::yield_context)>;
88
93 using Verifier = std::function<bool(ValueType const&)>;
94
108 [[nodiscard]] std::expected<ValueType, ErrorType>
109 asyncGet(boost::asio::yield_context yield, Updater updater, Verifier verifier)
110 {
111 switch (state_) {
112 case State::Updating: {
113 return wait(yield, std::move(updater), std::move(verifier));
114 }
115 case State::HasValue: {
116 auto const value = value_.template lock<std::shared_lock>();
117 ASSERT(value->has_value(), "Value should be presented when the cache is full");
118 return value->value();
119 }
120 case State::NoValue: {
121 return update(yield, std::move(updater), std::move(verifier));
122 }
123 };
124 std::unreachable();
125 }
126
138 [[nodiscard]] std::expected<ValueType, ErrorType>
139 update(boost::asio::yield_context yield, Updater updater, Verifier verifier)
140 {
141 if (state_ == State::Updating) {
142 return asyncGet(yield, std::move(updater), std::move(verifier));
143 }
144 state_ = State::Updating;
145
146 auto const result = updater(yield);
147 auto const shouldBeCached = result.has_value() and verifier(result.value());
148
149 if (shouldBeCached) {
150 value_.lock().get() = result.value();
151 state_ = State::HasValue;
152 } else {
153 state_ = State::NoValue;
154 value_.lock().get() = std::nullopt;
155 }
156
157 updateFinished_(result);
158 return result;
159 }
160
167 void
169 {
170 if (state_ == State::HasValue) {
171 state_ = State::NoValue;
172 value_.lock().get() = std::nullopt;
173 }
174 }
175
180 [[nodiscard]] State
181 state() const
182 {
183 return state_;
184 }
185
186private:
197 std::expected<ValueType, ErrorType>
198 wait(boost::asio::yield_context yield, Updater updater, Verifier verifier)
199 {
200 boost::asio::steady_timer timer{yield.get_executor(), boost::asio::steady_timer::duration::max()};
201 boost::system::error_code errorCode;
202
203 std::optional<std::expected<ValueType, ErrorType>> result;
204 boost::signals2::scoped_connection slot =
205 updateFinished_.connect([yield, &timer, &result](std::expected<ValueType, ErrorType> value) {
206 boost::asio::spawn(yield, [&timer, &result, value = std::move(value)](auto&&) {
207 result = std::move(value);
208 timer.cancel();
209 });
210 });
211
212 if (state_ == State::Updating) {
213 timer.async_wait(yield[errorCode]);
214 ASSERT(result.has_value(), "There should be some value after waiting");
215 return std::move(result).value();
216 }
217 return asyncGet(yield, std::move(updater), std::move(verifier));
218 }
219};
220
221} // namespace util
A thread-safe cache that blocks getting operations until the cache is updated.
Definition BlockingCache.hpp:50
void invalidate()
Invalidates the currently cached value if present.
Definition BlockingCache.hpp:168
BlockingCache(ValueType initialValue)
Construct a cache with an initial value.
Definition BlockingCache.hpp:72
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:109
State state() const
Returns the current state of the cache.
Definition BlockingCache.hpp:181
State
Possible states of the cache.
Definition BlockingCache.hpp:55
std::function< std::expected< ValueType, ErrorType >(boost::asio::yield_context)> Updater
Function type for cache update operations.
Definition BlockingCache.hpp:87
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:139
std::function< bool(ValueType const &)> Verifier
Function type to verify if a value should be cached.
Definition BlockingCache.hpp:93
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