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/concurrent/async_event_source.hpp>
11#include <userver/dynamic_config/snapshot.hpp>
12#include <userver/utils/assert.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace dynamic_config {
17
18/// Owns a snapshot of a config variable. You may use operator* or operator->
19/// to access the config variable.
20template <typename VariableType>
21class VariableSnapshotPtr final {
22public:
23 VariableSnapshotPtr(VariableSnapshotPtr&&) = delete;
24 VariableSnapshotPtr& operator=(VariableSnapshotPtr&&) = delete;
25
26 const VariableType& operator*() const& { return *variable_; }
27 const VariableType& operator*() && { ReportMisuse(); }
28
29 const VariableType* operator->() const& { return variable_; }
30 const VariableType* operator->() && { ReportMisuse(); }
31
32private:
33 [[noreturn]] static void ReportMisuse() {
34 static_assert(!sizeof(VariableType), "keep the pointer before using, please");
35 }
36
37 explicit VariableSnapshotPtr(Snapshot&& snapshot, const Key<VariableType>& key)
38 : snapshot_(std::move(snapshot)),
39 variable_(&snapshot_[key])
40 {}
41
42 // for the constructor
43 friend class Source;
44
45 Snapshot snapshot_;
46 const VariableType* variable_;
47};
48
49/// @brief Helper class for subscribing to dynamic-config updates with a custom
50/// callback.
51///
52/// Stores information about the last update that occurred.
53///
54/// @param previous `dynamic_config::Snapshot` of the previous config or
55/// `std::nullopt` if this update event is the first for the subscriber.
56struct Diff final {
57 std::optional<Snapshot> previous;
58 Snapshot current;
59};
60
61// clang-format off
62
63/// @ingroup userver_clients
64///
65/// @brief A client for easy dynamic config fetching in components.
66///
67/// After construction, dynamic_config::Source
68/// can be copied around and passed to clients or child helper classes.
69///
70/// Usually retrieved from components::DynamicConfig component.
71///
72/// Typical usage:
73/// @snippet components/component_sample_test.cpp Sample user component runtime config source
74
75// clang-format on
76class Source final {
77public:
78 using SnapshotEventSource = concurrent::AsyncEventSource<const Snapshot&>;
79 using DiffEventSource = concurrent::AsyncEventSource<const Diff&>;
80
81 /// For internal use only. Obtain using components::DynamicConfig or
82 /// dynamic_config::StorageMock instead.
83 explicit Source(impl::StorageData& storage);
84
85 // trivially copyable
86 Source(const Source&) = default;
87 Source(Source&&) = default;
88 Source& operator=(const Source&) = default;
89 Source& operator=(Source&&) = default;
90
91 Snapshot GetSnapshot() const;
92
93 template <typename VariableType>
94 VariableSnapshotPtr<VariableType> GetSnapshot(const Key<VariableType>& key) const {
95 return VariableSnapshotPtr{GetSnapshot(), key};
96 }
97
98 template <typename VariableType>
99 VariableType GetCopy(const Key<VariableType>& key) const {
100 const auto snapshot = GetSnapshot();
101 return snapshot[key];
102 }
103
104 /// Subscribes to dynamic-config updates using a member function. Also
105 /// immediately invokes the function with the current config snapshot (this
106 /// invocation will be executed synchronously).
107 ///
108 /// @note Callbacks occur in full accordance with
109 /// `components::DynamicConfigClientUpdater` options.
110 ///
111 /// @param obj the subscriber, which is the owner of the listener method, and
112 /// is also used as the unique identifier of the subscription
113 /// @param name the name of the subscriber, for diagnostic purposes
114 /// @param func the listener method, named `OnConfigUpdate` by convention.
115 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
116 /// subscription, which should be stored as a member in the subscriber;
117 /// `Unsubscribe` should be called explicitly
118 ///
119 /// @see based on concurrent::AsyncEventSource engine
120 template <typename Class>
121 concurrent::AsyncEventSubscriberScope UpdateAndListen(
122 Class* obj,
123 std::string_view name,
124 void (Class::*func)(const dynamic_config::Snapshot& config)
125 ) {
126 return DoUpdateAndListen(
127 concurrent::FunctionId(obj),
128 name,
129 [obj, func](const dynamic_config::Snapshot& config) { (obj->*func)(config); }
130 );
131 }
132
133 // clang-format off
134
135 /// @brief Subscribes to dynamic-config updates with information about the
136 /// current and previous states.
137 ///
138 /// Subscribes to dynamic-config updates using a member function, named
139 /// `OnConfigUpdate` by convention. Also constructs `dynamic_config::Diff`
140 /// object using `std::nullopt` and current config snapshot, then immediately
141 /// invokes the function with it (this invocation will be executed
142 /// synchronously).
143 ///
144 /// @note Callbacks occur in full accordance with
145 /// `components::DynamicConfigClientUpdater` options.
146 ///
147 /// @warning In debug mode the last notification for any subscriber will be
148 /// called with `std::nullopt` and current config snapshot.
149 ///
150 /// Example usage:
151 /// @snippet dynamic_config/config_test.cpp Custom subscription for dynamic config update
152 ///
153 /// @param obj the subscriber, which is the owner of the listener method, and
154 /// is also used as the unique identifier of the subscription
155 /// @param name the name of the subscriber, for diagnostic purposes
156 /// @param func the listener method, named `OnConfigUpdate` by convention.
157 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
158 /// subscription, which should be stored as a member in the subscriber;
159 /// `Unsubscribe` should be called explicitly
160 ///
161 /// @see based on concurrent::AsyncEventSource engine
162 ///
163 /// @see dynamic_config::Diff
164
165 // clang-format on
166 template <typename Class>
167 concurrent::AsyncEventSubscriberScope UpdateAndListen(
168 Class* obj,
169 std::string_view name,
170 void (Class::*func)(const dynamic_config::Diff& diff)
171 ) {
172 return DoUpdateAndListen(concurrent::FunctionId(obj), name, [obj, func](const dynamic_config::Diff& diff) {
173 (obj->*func)(diff);
174 });
175 }
176
177 /// @brief Subscribes to updates of a subset of all configs.
178 ///
179 /// Subscribes to dynamic-config updates using a member function, named
180 /// `OnConfigUpdate` by convention. The function will be invoked if at least
181 /// one of the configs has been changed since the previous invocation. So at
182 /// the first time immediately invokes the function with the current config
183 /// snapshot (this invocation will be executed synchronously).
184 ///
185 /// @note Callbacks occur only if one of the passed config is changed. This is
186 /// true under any components::DynamicConfigClientUpdater options.
187 ///
188 /// @warning To use this function, configs must have the `operator==`.
189 ///
190 /// @param obj the subscriber, which is the owner of the listener method, and
191 /// is also used as the unique identifier of the subscription
192 /// @param name the name of the subscriber, for diagnostic purposes
193 /// @param func the listener method, named `OnConfigUpdate` by convention.
194 /// @param keys config objects, specializations of `dynamic_config::Key`.
195 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
196 /// subscription, which should be stored as a member in the subscriber;
197 /// `Unsubscribe` should be called explicitly
198 ///
199 /// @see based on concurrent::AsyncEventSource engine
200 template <typename Class, typename... Keys>
201 concurrent::AsyncEventSubscriberScope UpdateAndListen(
202 Class* obj,
203 std::string_view name,
204 void (Class::*func)(const dynamic_config::Snapshot& config),
205 const Keys&... keys
206 ) {
207 auto wrapper = [obj, func, &keys...](const Diff& diff) {
208 if (!HasChanged(diff, keys...)) {
209 return;
210 }
211 (obj->*func)(diff.current);
212 };
213 return DoUpdateAndListen(concurrent::FunctionId(obj), name, std::move(wrapper));
214 }
215
216 SnapshotEventSource& GetEventChannel();
217
218private:
219 template <typename... Keys>
220 static bool HasChanged(const Diff& diff, const Keys&... keys) {
221 if (!diff.previous) {
222 return true;
223 }
224
225 const auto& previous = *diff.previous;
226 const auto& current = diff.current;
227
228 UASSERT(!current.GetData().IsEmpty());
229 UASSERT(!previous.GetData().IsEmpty());
230
231 const bool is_equal = (true && ... && (previous[keys] == current[keys]));
232 return !is_equal;
233 }
234
235 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
236 concurrent::FunctionId id,
237 std::string_view name,
238 SnapshotEventSource::Function&& func
239 );
240
241 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
242 concurrent::FunctionId id,
243 std::string_view name,
244 DiffEventSource::Function&& func
245 );
246
247 impl::StorageData* storage_;
248};
249
250} // namespace dynamic_config
251
252USERVER_NAMESPACE_END