userver: userver/utils/lazy_shared_ptr.hpp Source File
Loading...
Searching...
No Matches
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 {
29public:
30 /// @brief The default constructor, initializes with `nullptr`.
31 LazySharedPtr() noexcept : value_(nullptr), shared_filled_(true), get_data_() {}
32
33 /// @brief The non-lazy constructor
34 LazySharedPtr(utils::SharedReadablePtr<T> ptr) noexcept
35 : value_(ptr.Get()), shared_filled_(true), shared_(std::move(ptr)) {}
36
37 /// @brief The lazy constructor
38 LazySharedPtr(std::function<utils::SharedReadablePtr<T>()> function) noexcept : get_data_(std::move(function)) {}
39
40 /// @brief The copy constructor.
41 /// @note If `other` has not been fetched yet, `this` will not fetch
42 /// immediately, so `this` and `other` may end up pointing to different
43 /// objects.
44 LazySharedPtr(const LazySharedPtr& other)
45 : get_data_(other.get_data_)
46 {
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(), std::memory_order_relaxed);
73 shared_ = std::move(other.shared_);
74 get_data_ = std::move(other.get_data_);
75 }
76
77 /// @brief Get a pointer to the data (may be null). Fetches the data on the
78 /// first access.
79 const T* Get() const& {
80 if (const auto* current_value = value_.load(); current_value != kUnfilled) {
81 return current_value;
82 }
83 auto readable = get_data_();
84 if (const auto* expected = kUnfilled; value_.compare_exchange_strong(expected, readable.Get())) {
85 shared_ = std::move(readable);
86 shared_filled_.store(true);
87 }
88 return value_;
89 }
90
91 /// @brief Get a smart pointer to the data (may be null). Fetches the data on
92 /// the first access.
93 const utils::SharedReadablePtr<T>& GetShared() const& {
94 if (value_ == kUnfilled) {
95 auto readable = get_data_();
96 if (const auto* expected = kUnfilled; value_.compare_exchange_strong(expected, readable.Get())) {
97 shared_ = std::move(readable);
98 shared_filled_.store(true);
99 }
100 }
101 while (!shared_filled_.load()) {
102 // Another thread has filled 'value_', but not yet 'shared_'. It will be
103 // filled in a few CPU cycles. Discovering LazySharedPtr in such a state
104 // is expected to be rare.
105 engine::Yield();
106 }
107 return shared_;
108 }
109
110 ///@returns `*Get()`
111 ///@note `Get()` must not be `nullptr`.
112 const T& operator*() const& {
113 const auto* res = Get();
114 UASSERT(res);
115 return *res;
116 }
117
118 /// @returns `Get()`
119 /// @note `Get()` must not be `nullptr`.
120 const T* operator->() const& { return &**this; }
121
122 /// @returns `Get() != nullptr`
123 explicit operator bool() const& { return !!Get(); }
124
125 /// @returns `GetShared()`
126 operator const utils::SharedReadablePtr<T>&() const& { return GetShared(); }
127
128 /// @returns `GetShared()`
129 operator std::shared_ptr<const T>() const& { return GetShared(); }
130
131private:
132 static inline const auto kUnfilled = reinterpret_cast<const T*>(std::uintptr_t{1});
133
134 mutable std::atomic<const T*> value_{kUnfilled};
135 mutable std::atomic<bool> shared_filled_{false};
136 mutable utils::SharedReadablePtr<T> shared_{nullptr};
137 std::function<utils::SharedReadablePtr<T>()> get_data_{};
138};
139
140/// @brief Make a lazy pointer to the data of a cache.
141///
142/// The cache type must have:
143/// - `DataType` member type;
144/// - `Get() -> utils::SharedReadablePtr<DataType>` method.
145///
146/// For example, components::CachingComponentBase satisfies these requirements.
147template <typename Cache>
148LazySharedPtr<typename Cache::DataType> MakeLazyCachePtr(Cache& cache) {
149 return utils::LazySharedPtr<typename Cache::DataType>([&cache] { return cache.Get(); });
150}
151
152} // namespace utils
153
154USERVER_NAMESPACE_END