userver: userver/utils/lazy_shared_ptr.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_shared_ptr.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/lazy_shared_ptr.hpp
4/// @brief @copybrief utils::LazySharedPtr
5
6#include <atomic>
7#include <functional>
8#include <memory>
9#include <utility>
10
11#include <userver/engine/sleep.hpp>
12#include <userver/utils/assert.hpp>
13#include <userver/utils/shared_readable_ptr.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace utils {
18
19/// @ingroup userver_containers
20///
21/// @brief A lazy wrapper around utils::SharedReadablePtr that fetches the data
22/// on first access.
23///
24/// Provides standard thread-safety guarantee: `const` methods can be called
25/// concurrently. Once fetched, the same data is guaranteed to be returned
26/// unless the pointer is assigned to.
27template <typename T>
28class LazySharedPtr final {
29 public:
30 /// @brief The default constructor, initializes with `nullptr`.
31 LazySharedPtr() noexcept
32 : value_(nullptr), shared_filled_(true), shared_(), get_data_() {}
33
34 /// @brief The non-lazy constructor
35 LazySharedPtr(utils::SharedReadablePtr<T> ptr) noexcept
36 : value_(ptr.Get()), shared_filled_(true), shared_(std::move(ptr)) {}
37
38 /// @brief The lazy constructor
39 LazySharedPtr(std::function<utils::SharedReadablePtr<T>()> function) noexcept
40 : get_data_(std::move(function)) {}
41
42 /// @brief The copy constructor.
43 /// @note If `other` has not been fetched yet, `this` will not fetch
44 /// immediately, so `this` and `other` may end up pointing to different
45 /// objects.
46 LazySharedPtr(const LazySharedPtr& other) : get_data_(other.get_data_) {
47 if (other.shared_filled_.load()) {
48 value_.store(other.shared_.Get());
49 shared_ = other.shared_;
50 shared_filled_.store(true);
51 }
52 }
53
54 /// @brief The move constructor.
55 LazySharedPtr(LazySharedPtr&& other) noexcept
56 : value_(other.value_.load()),
57 shared_filled_(other.shared_filled_.load()),
58 shared_(std::move(other.shared_)),
59 get_data_(std::move(other.get_data_)) {}
60
61 /// @brief The copy assignment operator.
62 /// @note Like copy-constructor, it does not generate pointers if `other` do
63 /// not generate them.
64 LazySharedPtr& operator=(const LazySharedPtr& other) {
65 *this = LazySharedPtr(other);
66 return *this;
67 }
68
69 /// @brief The move assignment operator.
70 LazySharedPtr& operator=(LazySharedPtr&& other) noexcept {
71 value_.store(other.value_.load(), std::memory_order_relaxed);
72 shared_filled_.store(other.shared_filled_.load(),
73 std::memory_order_relaxed);
74 shared_ = std::move(other.shared_);
75 get_data_ = std::move(other.get_data_);
76 }
77
78 /// @brief Get a pointer to the data (may be null). Fetches the data on the
79 /// first access.
80 const T* Get() const& {
81 if (const auto* current_value = value_.load(); current_value != kUnfilled) {
82 return current_value;
83 }
84 auto readable = get_data_();
85 if (const auto* expected = kUnfilled;
86 value_.compare_exchange_strong(expected, readable.Get())) {
87 shared_ = std::move(readable);
88 shared_filled_.store(true);
89 }
90 return value_;
91 }
92
93 /// @brief Get a smart pointer to the data (may be null). Fetches the data on
94 /// the first access.
95 const utils::SharedReadablePtr<T>& GetShared() const& {
96 if (value_ == kUnfilled) {
97 auto readable = get_data_();
98 if (const auto* expected = kUnfilled;
99 value_.compare_exchange_strong(expected, readable.Get())) {
100 shared_ = std::move(readable);
101 shared_filled_.store(true);
102 }
103 }
104 while (!shared_filled_.load()) {
105 // Another thread has filled 'value_', but not yet 'shared_'. It will be
106 // filled in a few CPU cycles. Discovering LazySharedPtr in such a state
107 // is expected to be rare.
108 engine::Yield();
109 }
110 return shared_;
111 }
112
113 ///@returns `*Get()`
114 ///@note `Get()` must not be `nullptr`.
115 const T& operator*() const& {
116 const auto* res = Get();
117 UASSERT(res);
118 return *res;
119 }
120
121 /// @returns `Get()`
122 /// @note `Get()` must not be `nullptr`.
123 const T* operator->() const& { return &**this; }
124
125 /// @returns `Get() != nullptr`
126 explicit operator bool() const& { return !!Get(); }
127
128 /// @returns `GetShared()`
129 operator const utils::SharedReadablePtr<T>&() const& { return GetShared(); }
130
131 /// @returns `GetShared()`
132 operator std::shared_ptr<const T>() const& { return GetShared(); }
133
134 private:
135 static inline const auto kUnfilled =
136 reinterpret_cast<const T*>(std::uintptr_t{1});
137
138 mutable std::atomic<const T*> value_{kUnfilled};
139 mutable std::atomic<bool> shared_filled_{false};
140 mutable utils::SharedReadablePtr<T> shared_{nullptr};
141 std::function<utils::SharedReadablePtr<T>()> get_data_{};
142};
143
144/// @brief Make a lazy pointer to the data of a cache.
145///
146/// The cache type must have:
147/// - `DataType` member type;
148/// - `Get() -> utils::SharedReadablePtr<DataType>` method.
149///
150/// For example, components::CachingComponentBase satisfies these requirements.
151template <typename Cache>
152LazySharedPtr<typename Cache::DataType> MakeLazyCachePtr(Cache& cache) {
153 return utils::LazySharedPtr<typename Cache::DataType>(
154 [&cache] { return cache.Get(); });
155}
156
157} // namespace utils
158
159USERVER_NAMESPACE_END