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/condition_variable.hpp>
11#include <userver/engine/exception.hpp>
12#include <userver/engine/task/cancel.hpp>
13#include <userver/utils/assert.hpp>
14#include <userver/utils/result_store.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace concurrent {
19
20/// @brief lazy value computation with multiple users
21template <typename T>
22class LazyValue final {
23public:
24 explicit LazyValue(std::function<T()> f)
25 : f_(std::move(f))
26 {
27 UASSERT(f_);
28 }
29
30 /// @brief Get an already calculated result or calculate it.
31 /// It is guaranteed that `f` is called exactly once.
32 /// Can be called concurrently from multiple coroutines.
33 /// @throws anything `f` throws.
34 const T& operator()();
35
36private:
37 std::function<T()> f_;
38 std::atomic<bool> started_{false};
39 utils::ResultStore<T> result_;
40
41 engine::ConditionVariable cv_finished_;
42 engine::Mutex m_finished_;
43 std::atomic<bool> finished_{false};
44};
45
46template <typename T>
47const T& LazyValue<T>::operator()() {
48 if (finished_) {
49 return result_.Get();
50 }
51
52 auto old = started_.exchange(true);
53 if (!old) {
54 try {
55 result_.SetValue(f_());
56
57 const std::lock_guard lock(m_finished_);
58 finished_ = true;
59 cv_finished_.NotifyAll();
60 } catch (...) {
61 result_.SetException(std::current_exception());
62
63 const std::lock_guard lock(m_finished_);
64 finished_ = true;
65 cv_finished_.NotifyAll();
66 throw;
67 }
68 } else {
69 std::unique_lock lock(m_finished_);
70 auto rc = cv_finished_.Wait(lock, [this]() { return finished_.load(); });
71 if (!rc) {
73 }
74 }
75
76 return result_.Get();
77}
78
79} // namespace concurrent
80
81USERVER_NAMESPACE_END