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