userver: userver/concurrent/lazy_value.hpp Source File
Loading...
Searching...
No Matches
lazy_value.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/concurrent/lazy_value.hpp
4/// @brief @copybrief concurrent::LazyValue
5
6#include <atomic>
7#include <exception>
8#include <utility>
9
10#include <userver/engine/multi_consumer_event.hpp>
11#include <userver/utils/assert.hpp>
12#include <userver/utils/move_only_function.hpp>
13#include <userver/utils/result_store.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace concurrent {
18
19/// @brief Lazy value computation with multiple consumers.
20template <typename T>
21class LazyValue final {
22public:
23 explicit LazyValue(utils::move_only_function<T()> f)
24 : f_(std::move(f))
25 {
26 UASSERT(f_);
27 }
28
29 LazyValue(const LazyValue&) = delete;
30 LazyValue(LazyValue&&) = delete;
31 LazyValue& operator=(const LazyValue&) = delete;
32 LazyValue& operator=(LazyValue&&) = delete;
33
34 /// @brief Get an already calculated result or calculate it. It is guaranteed that `f` is called exactly once.
35 /// Can be called concurrently from multiple coroutines.
36 ///
37 /// @note If `f` throws, it is not re-evaluated on subsequent calls (unlike with `std::once_flag`).
38 ///
39 /// @throws Anything `f` throws.
40 const T& operator()();
41
42private:
43 utils::move_only_function<T()> f_;
44 std::atomic<bool> started_{false};
45 utils::ResultStore<T> result_;
46
47 engine::MultiConsumerEvent finished_event_;
48};
49
50template <typename T>
51const T& LazyValue<T>::operator()() {
52 if (finished_event_.IsReady()) {
53 return result_.Get();
54 }
55
56 const bool old = started_.exchange(true, std::memory_order_relaxed);
57 if (!old) {
58 try {
59 result_.SetValue(f_());
60 } catch (...) {
61 result_.SetException(std::current_exception());
62 finished_event_.Send();
63 throw;
64 }
65 finished_event_.Send();
66 } else {
67 finished_event_.Wait();
68 }
69
70 return result_.Get();
71}
72
73} // namespace concurrent
74
75USERVER_NAMESPACE_END