userver: userver/cache/caching_component_base.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
caching_component_base.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/cache/caching_component_base.hpp
4/// @brief @copybrief components::CachingComponentBase
5
6#include <memory>
7#include <string>
8#include <utility>
9
10#include <fmt/format.h>
11
12#include <userver/cache/cache_update_trait.hpp>
13#include <userver/cache/exceptions.hpp>
14#include <userver/compiler/demangle.hpp>
15#include <userver/components/component_fwd.hpp>
16#include <userver/components/loggable_component_base.hpp>
17#include <userver/concurrent/async_event_channel.hpp>
18#include <userver/dump/helpers.hpp>
19#include <userver/dump/meta.hpp>
20#include <userver/dump/operations.hpp>
21#include <userver/engine/async.hpp>
22#include <userver/rcu/rcu.hpp>
23#include <userver/utils/assert.hpp>
24#include <userver/utils/impl/wait_token_storage.hpp>
25#include <userver/utils/meta.hpp>
26#include <userver/utils/shared_readable_ptr.hpp>
27#include <userver/yaml_config/schema.hpp>
28
29USERVER_NAMESPACE_BEGIN
30
31namespace components {
32
33// clang-format off
34
35/// @ingroup userver_components userver_base_classes
36///
37/// @brief Base class for caching components
38///
39/// Provides facilities for creating periodically updated caches.
40/// You need to override cache::CacheUpdateTrait::Update
41/// then call cache::CacheUpdateTrait::StartPeriodicUpdates after setup
42/// and cache::CacheUpdateTrait::StopPeriodicUpdates before teardown.
43/// You can also override cache::CachingComponentBase::PreAssignCheck and set
44/// has-pre-assign-check: true in the static config to enable check.
45///
46/// Caching components must be configured in service config (see options below)
47/// and may be reconfigured dynamically via components::DynamicConfig.
48///
49/// @ref scripts/docs/en/userver/caches.md provide a more detailed introduction.
50///
51/// ## Dynamic config
52/// * @ref USERVER_CACHES
53/// * @ref USERVER_DUMPS
54///
55/// ## Static options:
56/// Name | Description | Default value
57/// ---- | ----------- | -------------
58/// update-types | specifies whether incremental and/or full updates will be used | see below
59/// update-interval | (*required*) interval between Update invocations | --
60/// update-jitter | max. amount of time by which interval may be adjusted for requests dispersal | update_interval / 10
61/// full-update-interval | interval between full updates | --
62/// updates-enabled | if false, cache updates are disabled (except for the first one if !first-update-fail-ok) | true
63/// first-update-fail-ok | whether first update failure is non-fatal | false
64/// task-processor | the name of the TaskProcessor for running DoWork | main-task-processor
65/// config-settings | enables dynamic reconfiguration with CacheConfigSet | true
66/// exception-interval | Used instead of `update-interval` in case of exception | update_interval
67/// additional-cleanup-interval | how often to run background RCU garbage collector | 10 seconds
68/// is-strong-period | whether to include Update execution time in update-interval | false
69/// testsuite-force-periodic-update | override testsuite-periodic-update-enabled in TestsuiteSupport component config | --
70/// failed-updates-before-expiration | the number of consecutive failed updates for data expiration | --
71/// has-pre-assign-check | enables the check before changing the value in the cache, by default it is the check that the new value is not empty | false
72///
73/// ### Update types
74/// * `full-and-incremental`: both `update-interval` and `full-update-interval`
75/// must be specified. Updates with UpdateType::kIncremental will be triggered
76/// each `update-interval` (adjusted by jitter) unless `full-update-interval`
77/// has passed and UpdateType::kFull is triggered.
78/// * `only-full`: only `update-interval` must be specified. UpdateType::kFull
79/// will be triggered each `update-interval` (adjusted by jitter).
80/// * `only-incremental`: only `update-interval` must be specified. UpdateType::kFull is triggered
81/// on the first update, afterwards UpdateType::kIncremental will be triggered
82/// each `update-interval` (adjusted by jitter).
83///
84/// ### testsuite-force-periodic-update
85/// use it to enable periodic cache update for a component in testsuite environment
86/// where testsuite-periodic-update-enabled from TestsuiteSupport config is false
87///
88/// By default, update types are guessed based on update intervals presence.
89/// If both `update-interval` and `full-update-interval` are present,
90/// `full-and-incremental` types is assumed. Otherwise `only-full` is used.
91///
92/// @see `dump::Dumper` for more info on persistent cache dumps and
93/// corresponding config options.
94///
95/// @see @ref scripts/docs/en/userver/caches.md. pytest_userver.client.Client.invalidate_caches()
96/// for a function to force cache update from testsuite.
97
98// clang-format on
99
100template <typename T>
101// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
103 protected cache::CacheUpdateTrait {
104 public:
105 CachingComponentBase(const ComponentConfig& config, const ComponentContext&);
106 ~CachingComponentBase() override;
107
108 using cache::CacheUpdateTrait::Name;
109
110 using DataType = T;
111
112 /// @return cache contents. May be nullptr if and only if MayReturnNull()
113 /// returns true.
114 utils::SharedReadablePtr<T> Get() const;
115
116 /// @return cache contents. May be nullptr regardless of MayReturnNull().
117 utils::SharedReadablePtr<T> GetUnsafe() const;
118
119 /// Subscribes to cache updates using a member function. Also immediately
120 /// invokes the function with the current cache contents.
121 template <class Class>
122 concurrent::AsyncEventSubscriberScope UpdateAndListen(
123 Class* obj, std::string name,
124 void (Class::*func)(const std::shared_ptr<const T>&));
125
126 concurrent::AsyncEventChannel<const std::shared_ptr<const T>&>&
127 GetEventChannel();
128
129 static yaml_config::Schema GetStaticConfigSchema();
130
131 protected:
132 void Set(std::unique_ptr<const T> value_ptr);
133 void Set(T&& value);
134
135 template <typename... Args>
136 void Emplace(Args&&... args);
137
138 void Clear();
139
140 /// Whether Get() is expected to return nullptr.
141 /// If MayReturnNull() returns false, Get() throws an exception instead of
142 /// returning nullptr.
143 virtual bool MayReturnNull() const;
144
145 /// @{
146 /// Override to use custom serialization for cache dumps
147 virtual void WriteContents(dump::Writer& writer, const T& contents) const;
148
149 virtual std::unique_ptr<const T> ReadContents(dump::Reader& reader) const;
150 /// @}
151
152 private:
153 void OnAllComponentsLoaded() final;
154
155 void Cleanup() final;
156
157 void MarkAsExpired() final;
158
159 void GetAndWrite(dump::Writer& writer) const final;
160 void ReadAndSet(dump::Reader& reader) final;
161
162 /// @brief If the option has-pre-assign-check is set true in static config,
163 /// this function is called before assigning the new value to the cache
164 /// @note old_value_ptr and new_value_ptr can be nullptr.
165 virtual void PreAssignCheck(const T* old_value_ptr,
166 const T* new_value_ptr) const;
167
168 rcu::Variable<std::shared_ptr<const T>> cache_;
169 concurrent::AsyncEventChannel<const std::shared_ptr<const T>&> event_channel_;
170 utils::impl::WaitTokenStorage wait_token_storage_;
171};
172
173template <typename T>
174CachingComponentBase<T>::CachingComponentBase(const ComponentConfig& config,
175 const ComponentContext& context)
176 : LoggableComponentBase(config, context),
177 cache::CacheUpdateTrait(config, context),
178 event_channel_(components::GetCurrentComponentName(config),
179 [this](auto& function) {
180 const auto ptr = cache_.ReadCopy();
181 if (ptr) function(ptr);
182 }) {
183 const auto initial_config = GetConfig();
184}
185
186template <typename T>
187CachingComponentBase<T>::~CachingComponentBase() {
188 // Avoid a deadlock in WaitForAllTokens
189 cache_.Assign(nullptr);
190 // We must wait for destruction of all instances of T to finish, otherwise
191 // it's UB if T's destructor accesses dependent components
192 wait_token_storage_.WaitForAllTokens();
193}
194
195template <typename T>
196utils::SharedReadablePtr<T> CachingComponentBase<T>::Get() const {
197 auto ptr = GetUnsafe();
198 if (!ptr && !MayReturnNull()) {
199 throw cache::EmptyCacheError(Name());
200 }
201 return ptr;
202}
203
204template <typename T>
205template <typename Class>
206concurrent::AsyncEventSubscriberScope CachingComponentBase<T>::UpdateAndListen(
207 Class* obj, std::string name,
208 void (Class::*func)(const std::shared_ptr<const T>&)) {
209 return event_channel_.DoUpdateAndListen(obj, std::move(name), func, [&] {
210 auto ptr = Get(); // TODO: extra ref
211 (obj->*func)(ptr);
212 });
213}
214
215template <typename T>
216concurrent::AsyncEventChannel<const std::shared_ptr<const T>&>&
217CachingComponentBase<T>::GetEventChannel() {
218 return event_channel_;
219}
220
221template <typename T>
222utils::SharedReadablePtr<T> CachingComponentBase<T>::GetUnsafe() const {
223 return utils::SharedReadablePtr<T>(cache_.ReadCopy());
224}
225
226template <typename T>
227void CachingComponentBase<T>::Set(std::unique_ptr<const T> value_ptr) {
228 auto deleter = [token = wait_token_storage_.GetToken(),
229 &cache_task_processor =
230 GetCacheTaskProcessor()](const T* raw_ptr) mutable {
231 std::unique_ptr<const T> ptr{raw_ptr};
232
233 // Kill garbage asynchronously as T::~T() might be very slow
234 engine::CriticalAsyncNoSpan(cache_task_processor, [ptr = std::move(ptr),
235 token = std::move(
236 token)]() mutable {
237 // Make sure *ptr is deleted before token is destroyed
238 ptr.reset();
239 }).Detach();
240 };
241
242 const std::shared_ptr<const T> new_value(value_ptr.release(),
243 std::move(deleter));
244
245 if (HasPreAssignCheck()) {
246 auto old_value = cache_.Read();
247 PreAssignCheck(old_value->get(), new_value.get());
248 }
249
250 cache_.Assign(new_value);
251 event_channel_.SendEvent(new_value);
253}
254
255template <typename T>
256void CachingComponentBase<T>::Set(T&& value) {
257 Emplace(std::move(value));
258}
259
260template <typename T>
261template <typename... Args>
262void CachingComponentBase<T>::Emplace(Args&&... args) {
263 Set(std::make_unique<T>(std::forward<Args>(args)...));
264}
265
266template <typename T>
267void CachingComponentBase<T>::Clear() {
268 cache_.Assign(std::make_unique<const T>());
269}
270
271template <typename T>
273 return false;
274}
275
276template <typename T>
277void CachingComponentBase<T>::GetAndWrite(dump::Writer& writer) const {
278 const auto contents = GetUnsafe();
279 if (!contents) throw cache::EmptyCacheError(Name());
280 WriteContents(writer, *contents);
281}
282
283template <typename T>
284void CachingComponentBase<T>::ReadAndSet(dump::Reader& reader) {
285 Set(ReadContents(reader));
286}
287
288template <typename T>
290 const T& contents) const {
291 if constexpr (dump::kIsDumpable<T>) {
292 writer.Write(contents);
293 } else {
294 dump::ThrowDumpUnimplemented(Name());
295 }
296}
297
298template <typename T>
299std::unique_ptr<const T> CachingComponentBase<T>::ReadContents(
300 dump::Reader& reader) const {
301 if constexpr (dump::kIsDumpable<T>) {
302 // To avoid an extra move and avoid including common_containers.hpp
303 return std::unique_ptr<const T>{new T(reader.Read<T>())};
304 } else {
305 dump::ThrowDumpUnimplemented(Name());
306 }
307}
308
309template <typename T>
310void CachingComponentBase<T>::OnAllComponentsLoaded() {
311 AssertPeriodicUpdateStarted();
312}
313
314template <typename T>
315void CachingComponentBase<T>::Cleanup() {
316 cache_.Cleanup();
317}
318
319template <typename T>
320void CachingComponentBase<T>::MarkAsExpired() {
321 Set(std::unique_ptr<const T>{});
322}
323
324namespace impl {
325
326yaml_config::Schema GetCachingComponentBaseSchema();
327
328}
329
330template <typename T>
331yaml_config::Schema CachingComponentBase<T>::GetStaticConfigSchema() {
332 return impl::GetCachingComponentBaseSchema();
333}
334
335template <typename T>
336void CachingComponentBase<T>::PreAssignCheck(
337 const T*, [[maybe_unused]] const T* new_value_ptr) const {
339 meta::kIsSizable<T>,
340 fmt::format("{} type does not support std::size(), add implementation of "
341 "the method size() for this type or "
342 "override cache::CachingComponentBase::PreAssignCheck.",
343 compiler::GetTypeName<T>()));
344
345 if constexpr (meta::kIsSizable<T>) {
346 if (!new_value_ptr || std::size(*new_value_ptr) == 0) {
347 throw cache::EmptyDataError(Name());
348 }
349 }
350}
351
352} // namespace components
353
354USERVER_NAMESPACE_END