userver: userver/dynamic_config/source.hpp Source File
Loading...
Searching...
No Matches
source.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/dynamic_config/source.hpp
4/// @brief @copybrief dynamic_config::Source
5
6#include <optional>
7#include <string_view>
8#include <utility>
9
10#include <userver/compiler/impl/lifetime.hpp>
11#include <userver/concurrent/async_event_source.hpp>
12#include <userver/dynamic_config/snapshot.hpp>
13#include <userver/utils/assert.hpp>
14#include <userver/utils/resource_scopes_fwd.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace dynamic_config {
19
20/// Owns a snapshot of a config variable. You may use operator* or operator->
21/// to access the config variable.
22template <typename VariableType>
23class VariableSnapshotPtr final {
24public:
25 VariableSnapshotPtr(VariableSnapshotPtr&&) = delete;
26 VariableSnapshotPtr& operator=(VariableSnapshotPtr&&) = delete;
27
28 const VariableType& operator*() const& USERVER_IMPL_LIFETIME_BOUND { return *variable_; }
29 const VariableType& operator*() && { ReportMisuse(); }
30
31 const VariableType* operator->() const& USERVER_IMPL_LIFETIME_BOUND { return variable_; }
32 const VariableType* operator->() && { ReportMisuse(); }
33
34private:
35 [[noreturn]] static void ReportMisuse() {
36 static_assert(!sizeof(VariableType), "keep the pointer before using, please");
37 }
38
39 explicit VariableSnapshotPtr(Snapshot&& snapshot, const Key<VariableType>& key)
40 : snapshot_(std::move(snapshot)),
41 variable_(&snapshot_[key])
42 {}
43
44 // for the constructor
45 friend class Source;
46
47 Snapshot snapshot_;
48 const VariableType* variable_;
49};
50
51/// @brief Helper class for subscribing to dynamic-config updates with a custom
52/// callback.
53///
54/// Stores information about the last update that occurred.
55///
56/// @param previous `dynamic_config::Snapshot` of the previous config or
57/// `std::nullopt` if this update event is the first for the subscriber.
58struct Diff final {
59 std::optional<Snapshot> previous;
60 Snapshot current;
61
62 template <typename... Keys>
63 bool HasConfigsChanged(const Keys&... keys) const {
64 if (!previous) {
65 return true;
66 }
67
68 UASSERT(!current.GetData().IsEmpty());
69 UASSERT(!previous->GetData().IsEmpty());
70
71 const bool is_equal = (true && ... && ((*previous)[keys] == current[keys]));
72 return !is_equal;
73 }
74};
75
76/// @ingroup userver_clients
77///
78/// @brief A client for easy dynamic config fetching in components.
79///
80/// After construction, dynamic_config::Source
81/// can be copied around and passed to clients or child helper classes.
82///
83/// Usually retrieved from components::DynamicConfig component.
84///
85/// Typical usage:
86/// @snippet core/src/components/component_sample_test.cpp Sample user component runtime config source
87class Source final {
88public:
89 using SnapshotEventSource = concurrent::AsyncEventSource<const Snapshot&>;
90 using DiffEventSource = concurrent::AsyncEventSource<const Diff&>;
91
92 /// For internal use only. Obtain using components::DynamicConfig or
93 /// dynamic_config::StorageMock instead.
94 explicit Source(impl::StorageData& storage);
95
96 // trivially copyable
97 Source(const Source&) = default;
98 Source(Source&&) = default;
99 Source& operator=(const Source&) = default;
100 Source& operator=(Source&&) = default;
101
102 Snapshot GetSnapshot() const;
103
104 template <typename VariableType>
105 VariableSnapshotPtr<VariableType> GetSnapshot(const Key<VariableType>& key) const {
106 return VariableSnapshotPtr{GetSnapshot(), key};
107 }
108
109 template <typename VariableType>
110 VariableType GetCopy(const Key<VariableType>& key) const {
111 const auto snapshot = GetSnapshot();
112 return snapshot[key];
113 }
114
115 /// Subscribes to dynamic-config updates using a member function. Also
116 /// immediately invokes the function with the current config snapshot (this
117 /// invocation will be executed synchronously).
118 ///
119 /// Further updates are delivered after @ref utils::ResourceScopeStorage::AfterConstruction, including those updates
120 /// that arrived before it. Unsubscribe runs in @ref utils::ResourceScopeStorage::BeforeDestruction.
121 ///
122 /// @note Callbacks occur in full accordance with
123 /// `components::DynamicConfigClientUpdater` options.
124 ///
125 /// @param scopes storage that owns the subscription lifetime. In a component constructor pass `context.Scopes()`
126 /// or @ref components::GetResourceScopes.
127 /// @param obj the subscriber, which is the owner of the listener method, and
128 /// is also used as the unique identifier of the subscription
129 /// @param name the name of the subscriber, for diagnostic purposes
130 /// @param func the listener method, named `OnConfigUpdate` by convention.
131 ///
132 /// @see based on concurrent::AsyncEventSource engine
133 template <typename Class>
135 utils::ResourceScopeStorage& scopes,
136 Class* obj,
137 std::string_view name,
138 void (Class::*func)(const dynamic_config::Snapshot& config)
139 ) {
140 DoUpdateAndListen(
141 scopes,
142 concurrent::FunctionId(obj),
143 name,
144 [obj, func](const dynamic_config::Snapshot& config) { (obj->*func)(config); }
145 );
146 }
147
148 /// @overload
149 /// @deprecated Use the overloads that take @ref utils::ResourceScopeStorage.
150 ///
151 /// Store the returned scope as a member and call `Unsubscribe` explicitly.
152 template <typename Class>
153 concurrent::AsyncEventSubscriberScope UpdateAndListen(
154 Class* obj,
155 std::string_view name,
156 void (Class::*func)(const dynamic_config::Snapshot& config)
157 ) {
158 return DoUpdateAndListen(
159 concurrent::FunctionId(obj),
160 name,
161 [obj, func](const dynamic_config::Snapshot& config) { (obj->*func)(config); }
162 );
163 }
164
165 /// @brief Subscribes to dynamic-config updates with information about the
166 /// current and previous states.
167 ///
168 /// Subscribes to dynamic-config updates using a member function, named
169 /// `OnConfigUpdate` by convention. Also constructs `dynamic_config::Diff`
170 /// object using `std::nullopt` and current config snapshot, then immediately
171 /// invokes the function with it (this invocation will be executed
172 /// synchronously).
173 ///
174 /// Further updates are delivered after @ref utils::ResourceScopeStorage::AfterConstruction, including those updates
175 /// that arrived before it. Unsubscribe runs in @ref utils::ResourceScopeStorage::BeforeDestruction.
176 ///
177 /// @note Callbacks occur in full accordance with
178 /// `components::DynamicConfigClientUpdater` options.
179 ///
180 /// @warning In debug mode the last notification for any subscriber will be
181 /// called with `std::nullopt` and current config snapshot.
182 ///
183 /// Example usage:
184 /// @snippet core/src/dynamic_config/config_test.cpp Custom subscription for dynamic config update
185 ///
186 /// @param scopes storage that owns the subscription lifetime. In a component constructor pass `context.Scopes()`
187 /// or @ref components::GetResourceScopes.
188 /// @param obj the subscriber, which is the owner of the listener method, and
189 /// is also used as the unique identifier of the subscription
190 /// @param name the name of the subscriber, for diagnostic purposes
191 /// @param func the listener method, named `OnConfigUpdate` by convention.
192 ///
193 /// @see based on concurrent::AsyncEventSource engine
194 ///
195 /// @see dynamic_config::Diff
196 template <typename Class>
198 utils::ResourceScopeStorage& scopes,
199 Class* obj,
200 std::string_view name,
201 void (Class::*func)(const dynamic_config::Diff& diff)
202 ) {
203 DoUpdateAndListen(scopes, concurrent::FunctionId(obj), name, [obj, func](const dynamic_config::Diff& diff) {
204 (obj->*func)(diff);
205 });
206 }
207
208 /// @overload
209 /// @deprecated Use the overloads that take @ref utils::ResourceScopeStorage.
210 ///
211 /// Store the returned scope as a member and call `Unsubscribe` explicitly.
212 template <typename Class>
213 concurrent::AsyncEventSubscriberScope UpdateAndListen(
214 Class* obj,
215 std::string_view name,
216 void (Class::*func)(const dynamic_config::Diff& diff)
217 ) {
218 return DoUpdateAndListen(concurrent::FunctionId(obj), name, [obj, func](const dynamic_config::Diff& diff) {
219 (obj->*func)(diff);
220 });
221 }
222
223 /// @brief Subscribes to updates of a subset of all configs.
224 ///
225 /// Subscribes to dynamic-config updates using a member function, named
226 /// `OnConfigUpdate` by convention. The function will be invoked if at least
227 /// one of the configs has been changed since the previous invocation. So at
228 /// the first time immediately invokes the function with the current config
229 /// snapshot (this invocation will be executed synchronously).
230 ///
231 /// Further updates are delivered after @ref utils::ResourceScopeStorage::AfterConstruction, including those updates
232 /// that arrived before it. Unsubscribe runs in @ref utils::ResourceScopeStorage::BeforeDestruction.
233 ///
234 /// @note Callbacks occur only if one of the passed config is changed. This is
235 /// true under any components::DynamicConfigClientUpdater options.
236 ///
237 /// @warning To use this function, configs must have the `operator==`.
238 ///
239 /// @param scopes storage that owns the subscription lifetime. In a component constructor pass `context.Scopes()`
240 /// or @ref components::GetResourceScopes.
241 /// @param obj the subscriber, which is the owner of the listener method, and
242 /// is also used as the unique identifier of the subscription
243 /// @param name the name of the subscriber, for diagnostic purposes
244 /// @param func the listener method, named `OnConfigUpdate` by convention.
245 /// @param keys config objects, specializations of `dynamic_config::Key`.
246 ///
247 /// @see based on concurrent::AsyncEventSource engine
248 template <typename Class, typename... Keys>
250 utils::ResourceScopeStorage& scopes,
251 Class* obj,
252 std::string_view name,
253 void (Class::*func)(const dynamic_config::Snapshot& config),
254 const Keys&... keys
255 ) {
256 auto wrapper = [obj, func, &keys...](const Diff& diff) {
257 if (!diff.HasConfigsChanged(keys...)) {
258 return;
259 }
260 (obj->*func)(diff.current);
261 };
262 DoUpdateAndListen(scopes, concurrent::FunctionId(obj), name, std::move(wrapper));
263 }
264
265 /// @overload
266 /// @deprecated Use the overloads that take @ref utils::ResourceScopeStorage.
267 ///
268 /// Store the returned scope as a member and call `Unsubscribe` explicitly.
269 template <typename Class, typename... Keys>
270 concurrent::AsyncEventSubscriberScope UpdateAndListen(
271 Class* obj,
272 std::string_view name,
273 void (Class::*func)(const dynamic_config::Snapshot& config),
274 const Keys&... keys
275 ) {
276 auto wrapper = [obj, func, &keys...](const Diff& diff) {
277 if (!diff.HasConfigsChanged(keys...)) {
278 return;
279 }
280 (obj->*func)(diff.current);
281 };
282 return DoUpdateAndListen(concurrent::FunctionId(obj), name, std::move(wrapper));
283 }
284
285 SnapshotEventSource& GetEventChannel();
286
287private:
288 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
289 concurrent::FunctionId id,
290 std::string_view name,
291 SnapshotEventSource::Function&& func
292 );
293
294 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
295 concurrent::FunctionId id,
296 std::string_view name,
297 DiffEventSource::Function&& func
298 );
299
300 void DoUpdateAndListen(
301 utils::ResourceScopeStorage& scopes,
302 concurrent::FunctionId id,
303 std::string_view name,
304 SnapshotEventSource::Function&& func
305 );
306
307 void DoUpdateAndListen(
308 utils::ResourceScopeStorage& scopes,
309 concurrent::FunctionId id,
310 std::string_view name,
311 DiffEventSource::Function&& func
312 );
313
314 impl::StorageData* storage_;
315};
316
317} // namespace dynamic_config
318
319USERVER_NAMESPACE_END