userver
C++ Async Framework
Toggle main menu visibility
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
/
data_provider
.
hpp
>
14
#
include
<
userver
/
cache
/
exceptions
.
hpp
>
15
#
include
<
userver
/
compiler
/
demangle
.
hpp
>
16
#
include
<
userver
/
components
/
component_base
.
hpp
>
17
#
include
<
userver
/
components
/
component_context
.
hpp
>
18
#
include
<
userver
/
components
/
component_fwd
.
hpp
>
19
#
include
<
userver
/
concurrent
/
async_event_channel
.
hpp
>
20
#
include
<
userver
/
dump
/
helpers
.
hpp
>
21
#
include
<
userver
/
dump
/
meta
.
hpp
>
22
#
include
<
userver
/
dump
/
operations
.
hpp
>
23
#
include
<
userver
/
engine
/
async
.
hpp
>
24
#
include
<
userver
/
rcu
/
rcu
.
hpp
>
25
#
include
<
userver
/
utils
/
assert
.
hpp
>
26
#
include
<
userver
/
utils
/
impl
/
wait_token_storage
.
hpp
>
27
#
include
<
userver
/
utils
/
meta
.
hpp
>
28
#
include
<
userver
/
utils
/
resource_scopes
.
hpp
>
29
#
include
<
userver
/
utils
/
shared_readable_ptr
.
hpp
>
30
#
include
<
userver
/
yaml_config
/
schema
.
hpp
>
31
32
USERVER_NAMESPACE_BEGIN
33
34
namespace
components
{
35
36
/// @ingroup userver_components userver_base_classes
37
///
38
/// @brief Base class for caching components
39
///
40
/// Provides facilities for creating periodically updated caches.
41
/// You need to override cache::CacheUpdateTrait::Update.
42
/// You can also override cache::CachingComponentBase::PreAssignCheck and set
43
/// has-pre-assign-check: true in the static config to enable check.
44
///
45
/// Caching components must be configured in service config (see options below)
46
/// and may be reconfigured dynamically via components::DynamicConfig.
47
///
48
/// @ref scripts/docs/en/userver/caches.md provide a more detailed introduction.
49
///
50
/// ## CachingComponentBase Dynamic config
51
/// * @ref USERVER_CACHES
52
/// * @ref USERVER_DUMPS
53
///
54
/// ## Static options of components::CachingComponentBase :
55
/// @include{doc} scripts/docs/en/components_schema/core/src/cache/caching_component_base.md
56
///
57
/// Options inherited from @ref components::ComponentBase :
58
/// @include{doc} scripts/docs/en/components_schema/core/src/components/impl/component_base.md
59
///
60
/// ### Update types
61
/// * `full-and-incremental`: both `update-interval` and `full-update-interval`
62
/// must be specified. Updates with UpdateType::kIncremental will be triggered
63
/// each `update-interval` (adjusted by jitter) unless `full-update-interval`
64
/// has passed and UpdateType::kFull is triggered.
65
/// * `only-full`: only `update-interval` must be specified. UpdateType::kFull
66
/// will be triggered each `update-interval` (adjusted by jitter).
67
/// * `only-incremental`: only `update-interval` must be specified. UpdateType::kFull is triggered
68
/// on the first update, afterwards UpdateType::kIncremental will be triggered
69
/// each `update-interval` (adjusted by jitter). Warning: use carefully.
70
/// If the cache loses any data, it is lost until service restart (in the worst case). If possible,
71
/// use `full-and-incremental` with rare full updates, and completely avoid `only-incremental`.
72
/// Also you have to explicitly remove outdated items from the cache container, otherwise
73
/// the cache might grow indefinitely and eventually will lead to OOM.
74
/// If not sure, just use `full-and-incremental`.
75
///
76
/// ### Avoiding memory leaks
77
///
78
/// If you don't implement the deletion of objects that are deleted from the data source and don't use full updates,
79
/// you may get an effective memory leak, because garbage objects will pile up in the cached data.
80
///
81
/// Calculation example:
82
/// * size of database: 1000 objects
83
/// * removal rate: 30 objects per minute (0.5 objects per second)
84
///
85
/// Let's say we allow 20% extra garbage objects in cache in addition to the actual objects from the database. In this
86
/// case we need:
87
///
88
/// full-update-interval = (size-of-database * 20% / removal-rate) = 400s
89
///
90
/// ### Dealing with nullptr data in CachingComponentBase
91
///
92
/// The cache can become `nullptr` through multiple ways:
93
///
94
/// * If the first cache update fails, and `first-update-fail-ok` config
95
/// option is set to `true` (otherwise the service shutdown at start)
96
/// * Through manually calling @ref Set with `nullptr` in @ref Update
97
/// * If `failed-updates-before-expiration` is set, and that many periodic
98
/// updates fail in a row
99
///
100
/// By default, the cache's user can expect that the pointer returned
101
/// from @ref Get will never be `nullptr`. If the cache for some reason is
102
/// in `nullptr` state, then @ref Get will throw. This is the safe default
103
/// behavior for most cases.
104
///
105
/// If all systems of a service are expected to work with a cache in `nullptr`
106
/// state, then such a cache should override `MayReturnNull` to return `true`.
107
/// It will also serve self-documentation purposes: if a cache defines
108
/// @ref MayReturnNull, then pointers returned from @ref Get should be checked
109
/// for `nullptr` before usage.
110
///
111
/// ### `first-update-mode` modes
112
///
113
/// Further customizes the behavior of @ref dump::Dumper "cache dumps".
114
///
115
/// Mode | Description
116
/// ----------- | -----------
117
/// skip | after successful load from dump, do nothing
118
/// required | make a synchronous update of type `first-update-type`, stop the service on failure
119
/// best-effort | make a synchronous update of type `first-update-type`, keep working and use data from dump on failure
120
///
121
/// ### testsuite-force-periodic-update
122
/// use it to enable periodic cache update for a component in testsuite environment
123
/// where testsuite-periodic-update-enabled from TestsuiteSupport config is false
124
///
125
/// By default, update types are guessed based on update intervals presence.
126
/// If both `update-interval` and `full-update-interval` are present,
127
/// `full-and-incremental` types is assumed. Otherwise `only-full` is used.
128
///
129
/// @see `dump::Dumper` for more info on persistent cache dumps and
130
/// corresponding config options.
131
///
132
/// @see @ref scripts/docs/en/userver/caches.md. pytest_userver.client.Client.invalidate_caches()
133
/// for a function to force cache update from testsuite.
134
template
<
typename
T>
135
// NOLINTNEXTLINE(fuchsia-multiple-inheritance)
136
class
CachingComponentBase
:
public
ComponentBase
,
public
cache
::
DataProvider
<T>,
protected
cache
::
CacheUpdateTrait
{
137
public
:
138
CachingComponentBase(
const
ComponentConfig& config,
const
ComponentContext&);
139
~CachingComponentBase()
override
;
140
141
using
cache
::
CacheUpdateTrait
::Name;
142
143
using
cache
::
CacheUpdateTrait
::InvalidateAsync;
144
145
using
DataType = T;
146
147
/// @return cache contents. May be `nullptr` if and only if `MayReturnNull`
148
/// returns `true`.
149
/// @throws cache::EmptyCacheError if the contents are `nullptr`, and
150
/// `MayReturnNull` returns `false` (which is the default behavior).
151
utils
::SharedReadablePtr<T>
Get
()
const
final
;
152
153
/// @return cache contents. May be nullptr regardless of `MayReturnNull`.
154
utils
::SharedReadablePtr<T>
GetUnsafe
()
const
;
155
156
/// Subscribes to cache updates using a member function. Also immediately
157
/// invokes the function with the current cache contents.
158
///
159
/// Further updates are delivered after @ref utils::ResourceScopeStorage::AfterConstruction, including those updates
160
/// that arrived before it. Unsubscribe runs in @ref utils::ResourceScopeStorage::BeforeDestruction.
161
///
162
/// @param scopes storage that owns the subscription lifetime. In a component constructor pass `context.Scopes()`
163
/// or @ref components::GetResourceScopes.
164
template
<
class
Class>
165
void
UpdateAndListen
(
166
utils
::ResourceScopeStorage& scopes,
167
Class* obj,
168
std::string name,
169
void
(Class::*func)(
const
std::shared_ptr<
const
T>&)
170
);
171
172
/// @overload
173
/// @deprecated Use the overload that takes @ref utils::ResourceScopeStorage.
174
///
175
/// Store the returned scope as a member and call `Unsubscribe` explicitly.
176
template
<
class
Class>
177
concurrent
::AsyncEventSubscriberScope
UpdateAndListen
(
178
Class* obj,
179
std::string name,
180
void
(Class::*func)(
const
std::shared_ptr<
const
T>&)
181
);
182
183
concurrent
::
AsyncEventChannel
<
const
std::shared_ptr<
const
T>&>& GetEventChannel();
184
185
static
yaml_config
::Schema GetStaticConfigSchema();
186
187
protected
:
188
/// @brief Sets the new value of cache. As a result, @ref Get member function starts returning the new value.
189
///
190
/// Notifies subscribers after setting the new value, see @ref UpdateAndListen.
191
/// Should only be called from @ref Update normally.
192
///
193
/// @warning Do not forget to update @ref cache::UpdateStatisticsScope, otherwise the behavior is undefined.
194
void
Set
(std::unique_ptr<
const
T> value_ptr);
195
196
/// @overload
197
void
Set
(T&& value);
198
199
/// Attach the value of cache. As a result the `Get()` member function starts returning the value passed into
200
/// this function after the `Update()` finishes. Does not take over into sole ownership. Do not use unless
201
/// absolutely necessary. The object must be strictly thread-safe.
202
///
203
/// @warning Do not forget to update @ref cache::UpdateStatisticsScope, otherwise
204
/// the behavior is undefined.
205
void
Attach
(
const
std::shared_ptr<
const
T>& value_ptr);
206
207
/// @overload Set()
208
template
<
typename
... Args>
209
void
Emplace(Args&&... args);
210
211
/// Clears the content of the cache by string a default constructed T.
212
void
Clear
();
213
214
/// Whether @ref Get is expected to return `nullptr`.
215
virtual
bool
MayReturnNull
()
const
;
216
217
/// @{
218
/// Override to use custom serialization for cache dumps
219
virtual
void
WriteContents
(
dump
::
Writer
& writer,
const
T& contents)
const
;
220
221
virtual
std::unique_ptr<
const
T> ReadContents(
dump
::
Reader
& reader)
const
;
222
/// @}
223
224
/// @brief If the option has-pre-assign-check is set true in static config,
225
/// this function is called before assigning the new value to the cache
226
/// @note old_value_ptr and new_value_ptr can be nullptr.
227
virtual
void
PreAssignCheck
(
const
T* old_value_ptr,
const
T* new_value_ptr)
const
;
228
229
private
:
230
void
Cleanup()
final
;
231
232
void
MarkAsExpired()
final
;
233
234
void
GetAndWrite(
dump
::
Writer
& writer)
const
final
;
235
void
ReadAndSet(
dump
::
Reader
& reader)
final
;
236
237
std::shared_ptr<
const
T> TransformNewValue(std::unique_ptr<
const
T> new_value);
238
239
rcu
::
Variable
<std::shared_ptr<
const
T>> cache_;
240
concurrent
::
AsyncEventChannel
<
const
std::shared_ptr<
const
T>&> event_channel_;
241
utils
::impl::WaitTokenStorage wait_token_storage_;
242
};
243
244
template
<
typename
T>
245
CachingComponentBase
<T>::CachingComponentBase(
const
ComponentConfig& config,
const
ComponentContext& context)
246
:
ComponentBase
(config, context),
247
cache
::
CacheUpdateTrait
(config, context),
248
event_channel_(
249
components
::
GetCurrentComponentName
(
context
)
,
250
[
this
](
const
auto
& function) {
251
const
auto
ptr = cache_.ReadCopy();
252
if
(ptr) {
253
function(ptr);
254
}
255
}
256
)
257
{}
258
259
template
<
typename
T>
260
CachingComponentBase
<T>::~CachingComponentBase() {
261
// Avoid a deadlock in WaitForAllTokens
262
cache_.Assign(
nullptr
);
263
// We must wait for destruction of all instances of T to finish, otherwise
264
// it's UB if T's destructor accesses dependent components
265
wait_token_storage_.WaitForAllTokens();
266
}
267
268
template
<
typename
T>
269
utils
::SharedReadablePtr<T>
CachingComponentBase
<T>::
Get
()
const
{
270
auto
ptr =
GetUnsafe
(
)
;
271
if
(!ptr && !
MayReturnNull
(
)
) {
272
throw
cache
::EmptyCacheError(
Name
(
)
);
273
}
274
return
ptr;
275
}
276
277
template
<
typename
T
>
278
template
<
typename
Class>
279
void
CachingComponentBase
<T>::
UpdateAndListen
(
280
utils
::ResourceScopeStorage& scopes,
281
Class* obj,
282
std::string name,
283
void
(Class::*func)(
const
std::shared_ptr<
const
T>&)
284
) {
285
event_channel_.DoUpdateAndListenScoped(scopes, obj, std::move(name), func, [
this
, obj, func] {
286
auto
ptr =
Get
(
)
;
// TODO: extra ref
287
(obj->*func)(ptr);
288
});
289
}
290
291
template
<
typename
T
>
292
template
<
typename
Class>
293
concurrent
::AsyncEventSubscriberScope
CachingComponentBase
<
294
T>::
UpdateAndListen
(Class* obj, std::string name,
void
(Class::*func)(
const
std::shared_ptr<
const
T>&)) {
295
return
event_channel_.DoUpdateAndListen(obj, std::move(name), func, [&] {
296
auto
ptr =
Get
(
)
;
// TODO: extra ref
297
(obj->*func)(ptr);
298
});
299
}
300
301
template
<
typename
T>
302
concurrent
::
AsyncEventChannel
<
const
std::shared_ptr<
const
T>&>&
CachingComponentBase
<T>::GetEventChannel() {
303
return
event_channel_;
304
}
305
306
template
<
typename
T>
307
utils
::SharedReadablePtr<T>
CachingComponentBase
<T>::
GetUnsafe
()
const
{
308
return
utils
::SharedReadablePtr<T>(cache_.ReadCopy());
309
}
310
311
template
<
typename
T>
312
void
CachingComponentBase
<T>::
Set
(std::unique_ptr<
const
T> value_ptr) {
313
Attach
(
TransformNewValue(std::move(value_ptr))
)
;
314
}
315
316
template
<
typename
T>
317
void
CachingComponentBase
<T>::
Set
(T&& value) {
318
Emplace(std::move(value));
319
}
320
321
template
<
typename
T>
322
void
CachingComponentBase
<T>::
Attach
(
const
std::shared_ptr<
const
T>& value_ptr) {
323
if
(HasPreAssignCheck()) {
324
auto
old_value = cache_.Read();
325
PreAssignCheck
(
old_value->get()
,
value_ptr.get()
)
;
326
}
327
328
cache_.Assign(value_ptr);
329
event_channel_.SendEvent(value_ptr);
330
OnCacheModified
(
)
;
331
}
332
333
template
<
typename
T
>
334
template
<
typename
... Args>
335
void
CachingComponentBase
<T>::Emplace(Args&&... args) {
336
Set(std::make_unique<T>(std::forward<Args>(args)...));
337
}
338
339
template
<
typename
T>
340
void
CachingComponentBase
<T>::
Clear
() {
341
cache_.Assign(std::make_unique<
const
T>());
342
}
343
344
template
<
typename
T>
345
bool
CachingComponentBase
<T>::
MayReturnNull
()
const
{
346
return
false
;
347
}
348
349
template
<
typename
T>
350
void
CachingComponentBase
<T>::GetAndWrite(
dump
::
Writer
& writer)
const
{
351
const
auto
contents =
GetUnsafe
(
)
;
352
if
(!contents) {
353
throw
cache
::EmptyCacheError(
Name
(
)
);
354
}
355
WriteContents
(
writer
,
*contents
)
;
356
}
357
358
template
<
typename
T>
359
void
CachingComponentBase
<T>::ReadAndSet(
dump
::
Reader
& reader) {
360
auto
data = ReadContents(reader);
361
if
constexpr
(
meta
::
kIsSizable
<T>) {
362
if
(data) {
363
SetDataSizeStatistic(std::size(*data));
364
}
365
}
366
Set(std::move(data));
367
}
368
369
template
<
typename
T>
370
void
CachingComponentBase
<T>::
WriteContents
(
dump
::
Writer
& writer,
const
T& contents)
const
{
371
if
constexpr
(
dump
::
kIsDumpable
<T>) {
372
writer.Write(contents);
373
}
else
{
374
dump
::ThrowDumpUnimplemented(
Name
(
)
);
375
}
376
}
377
378
template
<
typename
T>
379
std::unique_ptr<
const
T>
CachingComponentBase
<T>::ReadContents(
dump
::
Reader
& reader)
const
{
380
if
constexpr
(
dump
::
kIsDumpable
<T>) {
381
// To avoid an extra move and avoid including common_containers.hpp
382
return
std::unique_ptr<
const
T>{
new
T(reader.Read<T>())};
383
}
else
{
384
dump
::ThrowDumpUnimplemented(
Name
(
)
);
385
}
386
}
387
388
template
<
typename
T>
389
void
CachingComponentBase
<T>::Cleanup() {
390
cache_.Cleanup();
391
}
392
393
template
<
typename
T>
394
void
CachingComponentBase
<T>::MarkAsExpired() {
395
Set(std::unique_ptr<
const
T>{});
396
}
397
398
namespace
impl {
399
400
yaml_config
::Schema GetCachingComponentBaseSchema();
401
402
template
<
typename
T,
typename
Deleter>
403
auto
MakeAsyncDeleter(engine::TaskProcessor& task_processor, Deleter deleter) {
404
return
[&task_processor, deleter = std::move(deleter)](
const
T* raw_ptr)
mutable
{
405
std::unique_ptr<
const
T, Deleter> ptr(raw_ptr, std::move(deleter));
406
407
engine
::
DetachUnscopedUnsafe
(
engine::CriticalAsyncNoTracing(task_processor, [ptr = std::move(ptr)]()
mutable
{})
408
)
;
409
};
410
}
411
412
}
// namespace impl
413
414
template
<
typename
T>
415
yaml_config
::Schema
CachingComponentBase
<T>::GetStaticConfigSchema() {
416
return
impl::GetCachingComponentBaseSchema();
417
}
418
419
template
<
typename
T>
420
void
CachingComponentBase
<T>::
PreAssignCheck
(
const
T*, [[
maybe_unused
]]
const
T* new_value_ptr)
const
{
421
UINVARIANT
(
422
meta
::
kIsSizable
<T>,
423
fmt
::format(
424
"{} type does not support std::size(), add implementation of "
425
"the method size() for this type or "
426
"override cache::CachingComponentBase::PreAssignCheck."
,
427
compiler
::GetTypeName<T>()
428
)
429
);
430
431
if
constexpr
(
meta
::
kIsSizable
<T>) {
432
if
(!new_value_ptr || std::size(*new_value_ptr) == 0) {
433
throw
cache
::EmptyDataError(
Name
(
)
);
434
}
435
}
436
}
437
438
template
<
typename
T>
439
std::shared_ptr<
const
T>
CachingComponentBase
<T>::TransformNewValue(std::unique_ptr<
const
T> new_value) {
440
// Kill garbage asynchronously as T::~T() might be very slow
441
if
(IsSafeDataLifetime()) {
442
// Use token only if `safe-data-lifetime` is true
443
auto
deleter_with_token = [token = wait_token_storage_.GetToken()](
const
T* raw_ptr) {
444
// Make sure *raw_ptr is deleted before token is destroyed
445
std::default_delete<
const
T>{}(raw_ptr);
446
};
447
return
std::shared_ptr<
const
T>(
448
new_value.release(),
449
impl::MakeAsyncDeleter<T>(GetCacheTaskProcessor(), std::move(deleter_with_token))
450
);
451
}
else
{
452
return
std::shared_ptr<
const
T>(
453
new_value.release(),
454
impl::MakeAsyncDeleter<T>(GetCacheTaskProcessor(), std::default_delete<
const
T>{})
455
);
456
}
457
}
458
459
}
// namespace components
460
461
USERVER_NAMESPACE_END
userver
cache
caching_component_base.hpp
Generated on
for userver by
Doxygen
1.17.0