userver: userver/concurrent/lazy_value.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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