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 {
22 public:
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
32 private:
33 [[noreturn]] static void ReportMisuse() {
34 static_assert(!sizeof(VariableType),
35 "keep the pointer before using, please");
36 }
37
38 explicit VariableSnapshotPtr(Snapshot&& snapshot,
39 const Key<VariableType>& key)
40 : snapshot_(std::move(snapshot)), variable_(&snapshot_[key]) {}
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 {
77 public:
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(
95 const Key<VariableType>& key) const {
96 return VariableSnapshotPtr{GetSnapshot(), key};
97 }
98
99 template <typename VariableType>
100 VariableType GetCopy(const Key<VariableType>& key) const {
101 const auto snapshot = GetSnapshot();
102 return snapshot[key];
103 }
104
105 /// Subscribes to dynamic-config updates using a member function. Also
106 /// immediately invokes the function with the current config snapshot (this
107 /// invocation will be executed synchronously).
108 ///
109 /// @note Callbacks occur in full accordance with
110 /// `components::DynamicConfigClientUpdater` options.
111 ///
112 /// @param obj the subscriber, which is the owner of the listener method, and
113 /// is also used as the unique identifier of the subscription
114 /// @param name the name of the subscriber, for diagnostic purposes
115 /// @param func the listener method, named `OnConfigUpdate` by convention.
116 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
117 /// subscription, which should be stored as a member in the subscriber;
118 /// `Unsubscribe` should be called explicitly
119 ///
120 /// @see based on concurrent::AsyncEventSource engine
121 template <typename Class>
122 concurrent::AsyncEventSubscriberScope UpdateAndListen(
123 Class* obj, std::string_view name,
124 void (Class::*func)(const dynamic_config::Snapshot& config)) {
125 return DoUpdateAndListen(
126 concurrent::FunctionId(obj), name,
127 [obj, func](const dynamic_config::Snapshot& config) {
128 (obj->*func)(config);
129 });
130 }
131
132 // clang-format off
133
134 /// @brief Subscribes to dynamic-config updates with information about the
135 /// current and previous states.
136 ///
137 /// Subscribes to dynamic-config updates using a member function, named
138 /// `OnConfigUpdate` by convention. Also constructs `dynamic_config::Diff`
139 /// object using `std::nullopt` and current config snapshot, then immediately
140 /// invokes the function with it (this invocation will be executed
141 /// synchronously).
142 ///
143 /// @note Callbacks occur in full accordance with
144 /// `components::DynamicConfigClientUpdater` options.
145 ///
146 /// @warning In debug mode the last notification for any subscriber will be
147 /// called with `std::nullopt` and current config snapshot.
148 ///
149 /// Example usage:
150 /// @snippet dynamic_config/config_test.cpp Custom subscription for dynamic config update
151 ///
152 /// @param obj the subscriber, which is the owner of the listener method, and
153 /// is also used as the unique identifier of the subscription
154 /// @param name the name of the subscriber, for diagnostic purposes
155 /// @param func the listener method, named `OnConfigUpdate` by convention.
156 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
157 /// subscription, which should be stored as a member in the subscriber;
158 /// `Unsubscribe` should be called explicitly
159 ///
160 /// @see based on concurrent::AsyncEventSource engine
161 ///
162 /// @see dynamic_config::Diff
163
164 // clang-format on
165 template <typename Class>
166 concurrent::AsyncEventSubscriberScope UpdateAndListen(
167 Class* obj, std::string_view name,
168 void (Class::*func)(const dynamic_config::Diff& diff)) {
169 return DoUpdateAndListen(
170 concurrent::FunctionId(obj), name,
171 [obj, func](const dynamic_config::Diff& diff) { (obj->*func)(diff); });
172 }
173
174 /// @brief Subscribes to updates of a subset of all configs.
175 ///
176 /// Subscribes to dynamic-config updates using a member function, named
177 /// `OnConfigUpdate` by convention. The function will be invoked if at least
178 /// one of the configs has been changed since the previous invocation. So at
179 /// the first time immediately invokes the function with the current config
180 /// snapshot (this invocation will be executed synchronously).
181 ///
182 /// @note Сallbacks occur only if one of the passed config is changed. This is
183 /// true under any components::DynamicConfigClientUpdater options.
184 ///
185 /// @warning To use this function, configs must have the `operator==`.
186 ///
187 /// @param obj the subscriber, which is the owner of the listener method, and
188 /// is also used as the unique identifier of the subscription
189 /// @param name the name of the subscriber, for diagnostic purposes
190 /// @param func the listener method, named `OnConfigUpdate` by convention.
191 /// @param keys config objects, specializations of `dynamic_config::Key`.
192 /// @returns a `concurrent::AsyncEventSubscriberScope` controlling the
193 /// subscription, which should be stored as a member in the subscriber;
194 /// `Unsubscribe` should be called explicitly
195 ///
196 /// @see based on concurrent::AsyncEventSource engine
197 template <typename Class, typename... Keys>
198 concurrent::AsyncEventSubscriberScope UpdateAndListen(
199 Class* obj, std::string_view name,
200 void (Class::*func)(const dynamic_config::Snapshot& config),
201 const Keys&... keys) {
202 auto wrapper = [obj, func, &keys...](const Diff& diff) {
203 if (!HasChanged(diff, keys...)) return;
204 (obj->*func)(diff.current);
205 };
206 return DoUpdateAndListen(concurrent::FunctionId(obj), name,
207 std::move(wrapper));
208 }
209
210 SnapshotEventSource& GetEventChannel();
211
212 private:
213 template <typename... Keys>
214 static bool HasChanged(const Diff& diff, const Keys&... keys) {
215 if (!diff.previous) return true;
216
217 const auto& previous = *diff.previous;
218 const auto& current = diff.current;
219
220 UASSERT(!current.GetData().IsEmpty());
221 UASSERT(!previous.GetData().IsEmpty());
222
223 const bool is_equal = (true && ... && (previous[keys] == current[keys]));
224 return !is_equal;
225 }
226
227 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
228 concurrent::FunctionId id, std::string_view name,
229 SnapshotEventSource::Function&& func);
230
231 concurrent::AsyncEventSubscriberScope DoUpdateAndListen(
232 concurrent::FunctionId id, std::string_view name,
233 DiffEventSource::Function&& func);
234
235 impl::StorageData* storage_;
236};
237
238} // namespace dynamic_config
239
240USERVER_NAMESPACE_END