userver: userver/cache/caching_component_base.hpp Source File
Loading...
Searching...
No Matches
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