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