userver: userver/testsuite/cache_control.hpp Source File
Loading...
Searching...
No Matches
cache_control.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/testsuite/cache_control.hpp
4/// @brief @copybrief testsuite::CacheControl
5
6#include <functional>
7#include <memory>
8#include <string>
9#include <unordered_set>
10
11#include <userver/cache/update_type.hpp>
12#include <userver/components/component_fwd.hpp>
13#include <userver/utils/assert.hpp>
14#include <userver/utils/impl/internal_tag.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace cache {
20struct Config;
21} // namespace cache
22
23namespace components {
24class State;
25} // namespace components
26
27namespace testsuite {
28
29namespace impl {
30enum class PeriodicUpdatesMode { kDefault, kEnabled, kDisabled };
31
32using CacheReverseDependencies = std::unordered_set<std::string>;
33
34CacheReverseDependencies GetDefaultCacheReverseDependencies();
35
36} // namespace impl
37
38class CacheResetRegistration;
39
40/// @brief Testsuite interface for caches and cache-like components.
41///
42/// If a component stores transient state that may be carried between tests,
43/// or stores caches that may become stale, then it should register its resetter
44/// here. Example:
45///
46/// @snippet core/src/testsuite/cache_control_test.cpp sample
47///
48/// Testsuite will then call this hook in the beginning of each test.
49/// You can also reset a specific cache in testsuite explicitly as follows:
50///
51/// @code
52/// service_client.invalidate_caches(names=['your-cache-name'])
53/// @endcode
54///
55/// CacheControl is normally acquired through testsuite::FindCacheControl.
56///
57/// All methods are coro-safe.
58class CacheControl final {
59public:
60 /// @brief Reset all the registered caches.
61 ///
62 /// @a update_type is used by caches derived from
63 /// @a component::CachingComponentBase.
65 cache::UpdateType update_type,
66 const std::unordered_set<std::string>& force_incremental_names,
67 const std::unordered_set<std::string>& exclude_names
68 );
69
70 /// @brief Reset caches with the specified @a names.
71 ///
72 /// Every registered resetter whose name is in @a reset_only_names is
73 /// invoked. Several resetters may share a name: for example a periodic
74 /// @ref cache::CacheUpdateTrait resetter and a later custom
75 /// @ref RegisterCacheResetter.
76 ///
77 /// @a update_type is used by caches derived from
78 /// @a component::CachingComponentBase.
80 cache::UpdateType update_type,
81 std::unordered_set<std::string> reset_only_names,
82 const std::unordered_set<std::string>& force_incremental_names
83 );
84
85 CacheControl(CacheControl&&) = delete;
86 CacheControl& operator=(CacheControl&&) = delete;
87
88 /// @cond
89 // For internal use only.
90 struct UnitTests {
91 explicit UnitTests() = default;
92 };
93
94 enum class ExecPolicy {
95 kSequential,
96 kConcurrent,
97 };
98
99 CacheControl(impl::PeriodicUpdatesMode, UnitTests);
100 CacheControl(
101 impl::PeriodicUpdatesMode,
102 ExecPolicy,
103 components::State,
104 impl::CacheReverseDependencies reverse_dependencies
105 );
106 ~CacheControl();
107
108 // For internal use only.
109 bool IsPeriodicUpdateEnabled(const cache::Config& cache_config, const std::string& cache_name) const;
110
111 // For internal use only.
112 CacheResetRegistration RegisterPeriodicCache(cache::CacheUpdateTrait& cache);
113
114 // For internal use only. Use testsuite::RegisterCacheResetter instead
115 CacheResetRegistration RegisterCache(
116 utils::impl::InternalTag,
117 std::string_view name,
118 std::function<void(cache::UpdateType)> reset
119 );
120
121 struct CacheInfo final {
122 std::string name;
123 std::function<void(cache::UpdateType)> reset;
124 bool needs_span{true};
125 };
126 struct CacheInfoNode;
127 using CacheInfoIterator = CacheInfoNode*;
128
129 // For internal use only.
130 CacheInfoIterator DoRegisterCache(CacheInfo&& info);
131 /// @endcond
132private:
133 friend class CacheResetRegistration;
134
135 class CacheResetJob;
136
137 void DoResetCaches(
138 cache::UpdateType update_type,
139 const std::unordered_set<std::string>* reset_only_names,
140 const std::unordered_set<std::string>& force_incremental_names,
141 const std::unordered_set<std::string>* exclude_names
142 );
143
144 void DoResetCachesConcurrently(
145 cache::UpdateType update_type,
146 const std::unordered_set<std::string>* reset_only_names,
147 std::unordered_set<std::string>& names_left_to_encounter,
148 const std::unordered_set<std::string>& force_incremental_names,
149 const std::unordered_set<std::string>* exclude_names
150 );
151
152 void UnregisterCache(CacheInfoIterator) noexcept;
153
154 static void DoResetSingleCache(
155 const CacheInfo& info,
156 cache::UpdateType update_type,
157 const std::unordered_set<std::string>& force_incremental_names
158 );
159
160 struct Impl;
161 std::unique_ptr<Impl> impl_;
162};
163
164/// @brief RAII helper for testsuite registration.
165///
166/// Removes the associated resetter automatically on destruction.
167///
168/// Prefer @ref RegisterCacheResetter so that the resetter is registered after
169/// the component constructor and unregistered just before the destructor.
170/// Otherwise store the registration as a member after the rest of
171/// the component's fields.
172/// @see testsuite::CacheControl
173class [[nodiscard]] CacheResetRegistration final {
174public:
175 CacheResetRegistration() noexcept;
176
177 CacheResetRegistration(CacheResetRegistration&&) noexcept;
178 CacheResetRegistration& operator=(CacheResetRegistration&&) noexcept;
179 ~CacheResetRegistration();
180
181 /// Unregister the cache component explicitly.
182 /// `Unregister` is called in the destructor automatically.
183 void Unregister() noexcept;
184
185 /// @cond
186 // For internal use only.
187 CacheResetRegistration(CacheControl&, CacheControl::CacheInfoIterator);
188 /// @endcond
189
190private:
191 CacheControl* cache_control_{nullptr};
192 CacheControl::CacheInfoIterator cache_info_iterator_{};
193};
194
195/// The method for acquiring testsuite::CacheControl in the component system.
196///
197/// @see testsuite::RegisterCacheResetter
198CacheControl& FindCacheControl(const components::ComponentContext& context);
199
200namespace impl {
201
202void DoRegisterCacheScope(const components::ComponentContext& context, std::function<void(cache::UpdateType)> reset);
203
204template <typename Component>
205std::function<void(cache::UpdateType)> BindCacheResetter(Component* self, void (Component::*reset_method)()) {
206 UASSERT(self);
207 UASSERT(reset_method);
208 return [self, reset_method]([[maybe_unused]] cache::UpdateType update_type) { (self->*reset_method)(); };
209}
210
211template <typename Component>
212std::function<void(cache::UpdateType)> BindCacheResetter(
213 Component* self,
214 void (Component::*reset_method)(cache::UpdateType)
215) {
216 UASSERT(self);
217 UASSERT(reset_method);
218 return [self, reset_method](cache::UpdateType update_type) { (self->*reset_method)(update_type); };
219}
220
221} // namespace impl
222
223/// @brief Registers a cache resetter bound to the component lifetime.
224///
225/// The resetter is registered after the component constructor finishes
226/// and is unregistered just before the destructor runs.
227///
228/// Several cache resetters for the same component are invoked sequentially
229/// in registration order.
230///
231/// Typical usage:
232/// @code
233/// testsuite::RegisterCacheResetter(context, this, &MyCache::ResetCache);
234/// @endcode
235///
236/// @warning The function should be called in the component's constructor
237/// *after* all FindComponent calls. This ensures that reset will first be
238/// called for dependencies, then for dependent components.
239template <typename Component>
241 const components::ComponentContext& context,
242 Component* self,
243 void (Component::*reset_method)()
244) {
245 impl::DoRegisterCacheScope(context, impl::BindCacheResetter(self, reset_method));
246}
247
248/// @overload The resetter additionally receives the requested
249/// @ref cache::UpdateType.
250///
251/// Use this when the hook must distinguish a full invalidation from an
252/// incremental one. Typical cases:
253/// - a cache that supports both update types but is not periodic, so it is
254/// not a @ref components::CachingComponentBase: values are pushed in,
255/// for example by a handler called from a sidecar;
256/// - an extra resetter on a @ref components::CachingComponentBase cache that
257/// must know the requested update type to adjust incoming data for testsuite.
258template <typename Component>
260 const components::ComponentContext& context,
261 Component* self,
262 void (Component::*reset_method)(cache::UpdateType)
263) {
264 impl::DoRegisterCacheScope(context, impl::BindCacheResetter(self, reset_method));
265}
266
267/// @deprecated Use @ref RegisterCacheResetter instead.
268///
269/// Same as @ref RegisterCacheResetter for a `void()` hook. The testsuite
270/// `update_type` is ignored.
271template <typename Component>
273 const components::ComponentContext& context,
274 Component* self,
275 void (Component::*reset_method)()
276) {
277 RegisterCacheResetter(context, self, reset_method);
278}
279
280/// @deprecated Use @ref RegisterCacheResetter instead.
281/// The returned handle must be kept alive to keep supporting cache resetting.
282///
283/// @warning The function should be called in the component's constructor
284/// *after* all FindComponent calls. This ensures that reset will first be
285/// called for dependencies, then for dependent components.
286template <typename Component>
287CacheResetRegistration RegisterCache(
288 const components::ComponentContext& context,
289 Component* self,
290 void (Component::*reset_method)()
291) {
292 auto& cc = testsuite::FindCacheControl(context);
293 return cc.RegisterCache(
294 utils::impl::InternalTag{},
296 impl::BindCacheResetter(self, reset_method)
297 );
298}
299
300} // namespace testsuite
301
302USERVER_NAMESPACE_END