userver: userver/concurrent/lazy_value.hpp Source File
Loading...
Searching...
No Matches
lazy_value.hpp
1#pragma once
2
3/// @file userver/utils/lazy_value.hpp
4/// @brief @copybrief utils::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 {
23 public:
24 explicit LazyValue(std::function<T()> f) : f_(std::move(f)) { UASSERT(f_); }
25
26 /// @brief Get an already calculated result or calculate it.
27 /// It is guaranteed that `f` is called exactly once.
28 /// Can be called concurrently from multiple coroutines.
29 /// @throws anything `f` throws.
30 const T& operator()();
31
32 private:
33 std::function<T()> f_;
34 std::atomic<bool> started_{false};
35 utils::ResultStore<T> result_;
36
37 engine::ConditionVariable cv_finished_;
38 engine::Mutex m_finished_;
39 std::atomic<bool> finished_{false};
40};
41
42template <typename T>
43const T& LazyValue<T>::operator()() {
44 if (finished_) return result_.Get();
45
46 auto old = started_.exchange(true);
47 if (!old) {
48 try {
49 result_.SetValue(f_());
50
51 std::lock_guard lock(m_finished_);
52 finished_ = true;
53 cv_finished_.NotifyAll();
54 } catch (...) {
55 result_.SetException(std::current_exception());
56
57 std::lock_guard lock(m_finished_);
58 finished_ = true;
59 cv_finished_.NotifyAll();
60 throw;
61 }
62 } else {
63 std::unique_lock lock(m_finished_);
64 auto rc = cv_finished_.Wait(lock, [this]() { return finished_.load(); });
65 if (!rc)
66 throw engine::WaitInterruptedException(
68 }
69
70 return result_.Get();
71}
72
73} // namespace concurrent
74
75USERVER_NAMESPACE_END