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 <tuple>
9#include <utility>
10
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 {
23 public:
24 VariableSnapshotPtr(VariableSnapshotPtr&&) = delete;
25 VariableSnapshotPtr& operator=(VariableSnapshotPtr&&) = delete;
26
27 const VariableType& operator*() const& { return *variable_; }
28 const VariableType& operator*() && { ReportMisuse(); }
29
30 const VariableType* operator->() const& { return variable_; }
31 const VariableType* operator->() && { ReportMisuse(); }
32
33 private:
34 [[noreturn]] static void ReportMisuse() {
35 static_assert(!sizeof(VariableType),
36 "keep the pointer before using, please");
37 }
38
39 explicit VariableSnapshotPtr(Snapshot&& snapshot,
40 const Key<VariableType>& key)
41 : snapshot_(std::move(snapshot)), variable_(&snapshot_[key]) {}
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
62// clang-format off
63
64/// @ingroup userver_clients
65///
66/// @brief A client for easy dynamic config fetching in components.
67///
68/// After construction, dynamic_config::Source
69/// can be copied around and passed to clients or child helper classes.
70///
71/// Usually retrieved from components::DynamicConfig component.
72///
73/// Typical usage:
74/// @snippet components/component_sample_test.cpp Sample user component runtime config source
75
76// clang-format on
77class Source final {
78 public:
79 using SnapshotEventSource = concurrent::AsyncEventSource<const Snapshot&>;
80 using DiffEventSource = concurrent::AsyncEventSource<const Diff&>;
81
82 /// For internal use only. Obtain using components::DynamicConfig or
83 /// dynamic_config::StorageMock instead.
84 explicit Source(impl::StorageData& storage);
85
86 // trivially copyable
87 Source(const Source&) = default;
88 Source(Source&&) = default;
89 Source& operator=(const Source&) = default;
90 Source& operator=(Source&&) = default;
91
92 Snapshot GetSnapshot() const;
93
94 template <typename VariableType>
95 VariableSnapshotPtr<VariableType> GetSnapshot(
96 const Key<VariableType>& key) const {
97 return VariableSnapshotPtr{GetSnapshot(), key};
98 }
99
100 template <typename VariableType>
101 VariableType GetCopy(const Key<VariableType>& 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 const Keys&... keys) {
203 auto wrapper = [obj, func, keys = std::make_tuple(std::cref(keys)...)](
204 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, const 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