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 <string_view>
7#include <tuple>
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.
20///
21/// `VariableSnapshotPtr` in only intended to be used locally. Don't store it
22/// as a class member or pass it between functions. Use `Snapshot` for that
23/// purpose.
24template <typename Key>
25class VariableSnapshotPtr final {
26 public:
27 VariableSnapshotPtr(VariableSnapshotPtr&&) = delete;
28 VariableSnapshotPtr& operator=(VariableSnapshotPtr&&) = delete;
29
30 const VariableOfKey<Key>& operator*() const& { return variable_; }
31 const VariableOfKey<Key>& operator*() && { ReportMisuse(); }
32
33 const VariableOfKey<Key>* operator->() const& { return &variable_; }
34 const VariableOfKey<Key>* operator->() && { ReportMisuse(); }
35
36 private:
37 [[noreturn]] static void ReportMisuse() {
38 static_assert(!sizeof(Key), "keep the pointer before using, please");
39 }
40
41 explicit VariableSnapshotPtr(Snapshot&& snapshot, Key key)
42 : snapshot_(std::move(snapshot)), variable_(snapshot_[key]) {}
43
44 // for the constructor
45 friend class Source;
46
47 Snapshot snapshot_;
48 const VariableOfKey<Key>& 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
63// clang-format off
64
65/// @ingroup userver_clients
66///
67/// @brief A client for easy dynamic config fetching in components.
68///
69/// After construction, dynamic_config::Source
70/// can be copied around and passed to clients or child helper classes.
71///
72/// Usually retrieved from components::DynamicConfig component.
73///
74/// Typical usage:
75/// @snippet components/component_sample_test.cpp Sample user component runtime config source
76
77// clang-format on
78class Source final {
79 public:
80 using SnapshotEventSource = concurrent::AsyncEventSource<const Snapshot&>;
81 using DiffEventSource = concurrent::AsyncEventSource<const Diff&>;
82
83 /// For internal use only. Obtain using components::DynamicConfig or
84 /// dynamic_config::StorageMock instead.
85 explicit Source(impl::StorageData& storage);
86
87 // trivially copyable
88 Source(const Source&) = default;
89 Source(Source&&) = default;
90 Source& operator=(const Source&) = default;
91 Source& operator=(Source&&) = default;
92
93 Snapshot GetSnapshot() const;
94
95 template <typename Key>
96 VariableSnapshotPtr<Key> GetSnapshot(Key key) const {
97 return VariableSnapshotPtr{GetSnapshot(), key};
98 }
99
100 template <typename Key>
101 VariableOfKey<Key> GetCopy(Key key) const {
102 const auto snapshot = GetSnapshot();
103 return snapshot[key];
104 }
105
106 /// Subscribes to dynamic-config updates using a member function. Also
107 /// immediately invokes the function with the current config snapshot (this
108 /// invocation will be executed synchronously).
109 ///
110 /// @note Callbacks occur in full accordance with
111 /// `components::DynamicConfigClientUpdater` options.
112 ///
113 /// @param obj the subscriber, which is the owner of the listener method, and
114 /// is also used as the unique identifier of the subscription
115 /// @param name the name of the subscriber, for diagnostic purposes
116 /// @param func the listener method, named `OnConfigUpdate` by convention.
117 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
118 /// subscription, which should be stored as a member in the subscriber;
119 /// `Unsubscribe` should be called explicitly
120 ///
121 /// @see based on concurrent::AsyncEventSource engine
122 template <typename Class>
123 concurrent::AsyncEventSubscriberScope UpdateAndListen(
124 Class* obj, std::string_view name,
125 void (Class::*func)(const dynamic_config::Snapshot& config)) {
126 return DoUpdateAndListen(
127 concurrent::FunctionId(obj), name,
128 [obj, func](const dynamic_config::Snapshot& config) {
129 (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, std::string_view name,
169 void (Class::*func)(const dynamic_config::Diff& diff)) {
170 return DoUpdateAndListen(
171 concurrent::FunctionId(obj), name,
172 [obj, func](const dynamic_config::Diff& diff) { (obj->*func)(diff); });
173 }
174
175 /// @brief Subscribes to updates of a subset of all configs.
176 ///
177 /// Subscribes to dynamic-config updates using a member function, named
178 /// `OnConfigUpdate` by convention. The function will be invoked if at least
179 /// one of the configs has been changed since the previous invocation. So at
180 /// the first time immediately invokes the function with the current config
181 /// snapshot (this invocation will be executed synchronously).
182 ///
183 /// @note Сallbacks occur only if one of the passed config is changed. This is
184 /// true under any components::DynamicConfigClientUpdater options.
185 ///
186 /// @warning To use this function, configs must have the `operator==`.
187 ///
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 /// @param keys config objects, specializations of `dynamic_config::Key`.
193 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
194 /// subscription, which should be stored as a member in the subscriber;
195 /// `Unsubscribe` should be called explicitly
196 ///
197 /// @see based on concurrent::AsyncEventSource engine
198 template <typename Class, typename... Keys>
199 concurrent::AsyncEventSubscriberScope UpdateAndListen(
200 Class* obj, std::string_view name,
201 void (Class::*func)(const dynamic_config::Snapshot& config),
202 Keys... keys) {
203 auto wrapper = [obj, func,
204 keys = std::make_tuple(keys...)](const Diff& diff) {
205 const auto args = std::tuple_cat(std::tie(diff), keys);
206 if (!std::apply(HasChanged<Keys...>, args)) return;
207 (obj->*func)(diff.current);
208 };
209 return DoUpdateAndListen(concurrent::FunctionId(obj), name,
210 std::move(wrapper));
211 }
212
213 SnapshotEventSource& GetEventChannel();
214
215 private:
216 template <typename... Keys>
217 static bool HasChanged(const Diff& diff, Keys... keys) {
218 if (!diff.previous) return true;
219
220 const auto& previous = *diff.previous;
221 const auto& current = diff.current;
222
223 UASSERT(!current.GetData().IsEmpty());
224 UASSERT(!previous.GetData().IsEmpty());
225
226 const bool is_equal = (true && ... && (previous[keys] == current[keys]));
227 return !is_equal;
228 }
229
230 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
231 concurrent::FunctionId id, std::string_view name,
232 SnapshotEventSource::Function&& func);
233
234 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
235 concurrent::FunctionId id, std::string_view name,
236 DiffEventSource::Function&& func);
237
238 impl::StorageData* storage_;
239};
240
241} // namespace dynamic_config
242
243USERVER_NAMESPACE_END