Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
TimedCache.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Cache_TimedCache_h_
5#define _Stroika_Foundation_Cache_TimedCache_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <map>
10#include <mutex>
11#include <optional>
12
13#include "Stroika/Foundation/Cache/Common.h"
15#include "Stroika/Foundation/Common/Common.h"
23
24/**
25 * \file
26 *
27 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
28 *
29 * TODO:
30 * @todo Perhaps use Stroika Mapping<> instead of std::map<> - and in that way - we can use aribtrary externally
31 * specified map impl - so can use HASHING or BTREE, based on passed in arg. So we don't ahve problem with
32 * creating the default, specify default type to create in the TRAITS object (so for example, if using Hash,
33 * we don't force having operator< for BTREE map).
34 *
35 * AND RELATED cleanup to spec using InOrderComparerType - as opposed to EQUALS COMPARER or other stuff like HashFunctions!
36 * MAYBE LOSE inorder comparer arg - and INSTEAD have template / template for creating container (for now its map<>??)
37 *
38 * Implementation Note:
39 *
40 * This module uses stl:map<> instead of a Stroika Mapping since we are comfortable with
41 * the current implementation using btree's, and to avoid any dependencies between
42 * Caching and Containers. We may want to re-think that, and just use Mapping here.
43 */
44
46
47 /**
48 * TimedCacheSupport mostly for defining TRAITS object that configures the cache behavior.
49 */
50 namespace TimedCacheSupport {
51
53
54 /**
55 * @brief see TimedCache<>::TraitsType::kAutomaticPurgeFrequency - disable automatic purging
56 *
57 * \note would be nice to declare as of type Time::DurationSeconds, but then won't work as template parameter
58 *
59 * \note TimeStampDifferenceType{TimedCacheSupport::kNoAutomaticPurgeSentinal} all over place due to qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy.
60 * if we can stop supporting compilers with this issue, we can probably redeclare this as TimeStampDifferenceType --LGP 2026-03-23
61 */
62 constexpr float kNoAutomaticPurgeSentinal = -1.0f;
63
64 /**
65 * @brief The default 'TTL'/Max-Age of items added to a TimedCache, if not specified/overriden anyplace else (1 minute)
66 *
67 * \note float due to qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy.
68 * if we can stop supporting compilers with this issue, we can probably redeclare this as TimeStampDifferenceType --LGP 2026-03-23
69 */
70 constexpr float kDefaultMaxAge = 60.0;
71
72 /**
73 * @brief see TimedCache<>::TraitsType::kAutomaticPurgeFrequency - default to purging every 5 minutes
74 *
75 * \note float due to qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy.
76 * if we can stop supporting compilers with this issue, we can probably redeclare this as TimeStampDifferenceType --LGP 2026-03-23
77 */
78#if qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy
79 constexpr int kDefaultAutomaticPurgeFrequency = 5 * 60;
80#else
81 constexpr float kDefaultAutomaticPurgeFrequency = 5 * 60.0f;
82#endif
83
84 /**
85 * @brief Check if argument TRAITS is a valid TRAITS object for TimedCache<>
86 *
87 * \note ONE of (but not both) - kTrackFreshness or kTrackExpiration
88 * \note kTrackExpiration not compatible with kAutomaticallyMarkDataAsRefreshedEachTimeAccessed
89 * \note valid meaningful TRAITS::kDefaultMaxAge or TRAITS::kPerCacheMaxAge
90 */
91 template <typename TRAITS, typename KEY, typename VALUE>
93 requires (TRAITS) {
94 typename TRAITS::KeyType;
95 typename TRAITS::ResultType;
96 typename TRAITS::StatsType;
97 typename TRAITS::TimeStampType;
98 typename TRAITS::TimeStampDifferenceType;
99 { TRAITS::kInternallySynchronized } -> convertible_to<InternallySynchronized>;
100 { TRAITS::kAutomaticPurgeFrequency } -> convertible_to<typename TRAITS::TimeStampDifferenceType>;
101 { TRAITS::kAutomaticallyMarkDataAsRefreshedEachTimeAccessed } -> convertible_to<bool>;
102 { TRAITS::kTrackFreshness } -> convertible_to<bool>;
103 { TRAITS::kTrackExpiration } -> convertible_to<bool>;
104 { TRAITS::GetCurrentTimestamp () } -> convertible_to<typename TRAITS::TimeStampType>;
105 {
106 declval<typename TRAITS::TimeStampType> () - declval<typename TRAITS::TimeStampType> ()
107 } -> convertible_to<typename TRAITS::TimeStampDifferenceType>;
108 {
109 declval<typename TRAITS::TimeStampType> () + declval<typename TRAITS::TimeStampDifferenceType> ()
110 } -> convertible_to<typename TRAITS::TimeStampType>;
111 { TRAITS::kPerCacheMaxAge } -> convertible_to<bool>;
112 } and same_as<typename TRAITS::KeyType, KEY> and same_as<typename TRAITS::ResultType, VALUE> and
113 TRAITS::kTrackFreshness != TRAITS::kTrackExpiration and totally_ordered<typename TRAITS::TimeStampType> and
114 (not TRAITS::kAutomaticallyMarkDataAsRefreshedEachTimeAccessed or not TRAITS::kTrackExpiration) and
116 (not std::is_empty_v<decltype (TRAITS::kDefaultMaxAge)> or TRAITS::kPerCacheMaxAge) and
118
119 /**
120 * The DefaultTraits<> is a simple default traits implementation for building an TimedCache<>.
121 *
122 * \note This class was incompatibly changed in Stroika 3.0d1. It used to have a TRACK_READ_ACCESS parameter.
123 * Since Stroika 3.0d1, instead, if you wish to set that true, call Lookup (..., eTreatFoundThroughLookupAsRefreshed) instead
124 * of Lookup ()
125 *
126 * \note this class was incompatibly changed in Stroika 3.0d23. It used to have the STRICT_INORDER_COPARER as third arugment
127 * but InternallySynchronized added as new third pushing comarer to fourth.
128 *
129 * \note Use of this directly IS allowed, but its fragile, as there isn't a good way to overload or evolve definition over time
130 * so code using this directly will be more likely to not be backward compatible in the future. Better to use adapters like InternallySynchronizedTraits
131 *
132 * \see ITraits<> above
133 */
134 template <IKey KEY, IValue VALUE, InternallySynchronized INTERNALLY_SYNCHRONIZED, typename STRICT_INORDER_COMPARER, bool TRACK_FRESHNESS, bool TRACK_EXPIRATION,
135 Cache::Statistics::IStatsType STATS_TYPE, typename TIMESTAMP_TYPE, typename TIMESTAMP_DIFFERENCE_TYPE, TIMESTAMP_TYPE (*GET_CURRENT_TIMESTAMP) (),
136// IDEALLY if I can - TIMESTAMP_DIFFERENCE_TYPE DEFAULT_MAX_AGE;
137#if qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy
138 int DEFAULT_MAX_AGE,
139#else
140 float DEFAULT_MAX_AGE,
141#endif
142 bool PER_CACHE_MAX_AGE,
143#if qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy
144 int AUTOMATIC_PURGE_FREQUENCY_SECONDS,
145#else
146 float AUTOMATIC_PURGE_FREQUENCY_SECONDS,
147#endif
148 bool AUTO_MARK_DATA_AS_REFRESHED_ON_EACH_WRITABLE_ACCESS>
150 using KeyType = KEY;
151 using ResultType = VALUE;
152
153 /**
154 * @brief Typically Time::TimePointSeconds
155 */
156 using TimeStampType = TIMESTAMP_TYPE;
157
158 /**
159 * @brief Typically Time::DurationSeconds
160 */
161 using TimeStampDifferenceType = TIMESTAMP_DIFFERENCE_TYPE;
162
163 /**
164 * @brief Get the Current Timestamp object - defaults to Time::GetTickCount ()
165 */
166 static constexpr auto GetCurrentTimestamp{GET_CURRENT_TIMESTAMP};
167
168 /**
169 * @brief specifies a default MAX_AGE (can be UNDEFINED or Empty). If defined, its the default value returned by/used by
170 * the TimedCache as the max age (TTL) of an item added to the cache. It can generally be overridden (by Lookup or Add or LookupValue)
171 * functions (which depends on kTrackFreshness or TrackExpiration).
172 *
173 * \note TimedCache MAY have kDefaultMaxAge, but MUST have EITHER (inclusive or) kDefaultMaxAge or kPerCacheMaxAge.
174 */
175 static constexpr conditional_t<(DEFAULT_MAX_AGE < 0), Common::Empty, TimeStampDifferenceType> kDefaultMaxAge{DEFAULT_MAX_AGE};
176
177 /**
178 * @brief allow a per-cache MAX_AGE to be defined. If allowed, and the default (traits) is specified, that is the default for the cache instance.
179 *
180 * \note TimedCache MAY have kDefaultMaxAge, but MUST have EITHER (inclusive or) kDefaultMaxAge or kPerCacheMaxAge.
181 */
182 static constexpr bool kPerCacheMaxAge{PER_CACHE_MAX_AGE};
183
184 /**
185 * @brief This 'automatic synchronization' feature is off (eNotKnownInternallySynchronized) by default, but can easily
186 * be turned on with InternallySynchronizedTraits
187 */
188 static constexpr inline InternallySynchronized kInternallySynchronized{INTERNALLY_SYNCHRONIZED};
189
190 /**
191 * @brief This just applies to LookupValue() calls; can increase latency setting to true
192 *
193 * Note: We choose to not hold any lock while filling the cache (fHoldWriteLockDuringCacheFill false by default).
194 * This is because typically, filling the cache
195 * will be slow (otherwise you would be us using the SynchronizedTimedCache).
196 *
197 * But this has the downside, that you could try filling the cache multiple times with the same value.
198 *
199 * Thats perfectly safe, but not speedy.
200 *
201 * Which is better depends on the likihood the caller will make multiple requests for the same non-existent value at
202 * the same time. If yes, you should set fHoldWriteLockDuringCacheFill. If no (or if you care more about being able to
203 * read the rest of the data and not having threads block needlessly for other values) set fHoldWriteLockDuringCacheFill false (default).
204 */
205 static constexpr bool kHoldWriteLockDuringCacheFill = false;
206
207 /**
208 * @brief freshness means when last added/updated (or if kAutomaticallyMarkDataAsRefreshedEachTimeAccessed) then last accessed too)
209 * This is true by default
210 *
211 * \note kTrackFreshness and kTrackExpiration are mutually exclusive
212 */
213 static constexpr inline bool kTrackFreshness{TRACK_FRESHNESS};
214
215 /**
216 * @brief Track on a per-item when it expires. If not tracked, we use expiresAt as whenAdded + maxAge
217 * This is false by default
218 *
219 * \note kTrackFreshness and kTrackExpiration are mutually exclusive
220 */
221 static constexpr inline bool kTrackExpiration{TRACK_EXPIRATION};
222
223 /**
224 * @brief if IKeyedCache, this is a how the KEY type elements are compared (for Lookup purposes).
225 *
226 * @todo - historical that we use INORDER comparer - cuz impl uses std::map<> - but could loosen this to be ANY EQUALITY_COMPARER or other - if we use
227 * different kinds of MAP (Mapping...) --LGP 2026-03-23
228 */
229 using InOrderComparerType = STRICT_INORDER_COMPARER;
230
231 /**
232 * @brief Internally synchronized 'Stats' collector type (Cache::Statistics::IStatsType). Often null stats collector.
233 */
234 using StatsType = STATS_TYPE;
235
236 /**
237 * How often TimedCache-modifying operations will automatically trigger a call to ClearExpiredData ()
238 *
239 * This defaults to kDefaultAutomaticPurgeFrequency (but can be set to NEVER (kNoAutomaticPurgeSentinal)).
240 *
241 * \note - NOT triggered asynchronously, but from modifying APIs, like Add, or non-const Lookup()
242 */
243 static constexpr TimeStampDifferenceType kAutomaticPurgeFrequency{AUTOMATIC_PURGE_FREQUENCY_SECONDS};
244
245 /*
246 * This is useful for behavior like an LRU cache, where you express INTEREST in an item by using it.
247 * Use this if the data doesn't truely expire, but you want to keep intresting / recently used data around (though you maybe should
248 * just use LRUCache in that case).
249 *
250 * This is off by default.
251 *
252 * \note this only applies to NON-CONST methods, like the non-const Lookup() overload, and LookupValue() methods.
253 *
254 * \note Before Stroika v3.0d23, this was expressed via the optional argument to Lookup/LookupValue of type
255 * LookupMarksDataAsRefreshed (value eTreatFoundThroughLookupAsRefreshed).
256 * \note Before Stroika 3.0d1, this used to support TraitsType::kTrackReadAccess, and if it was true did the same
257 * as the newer Lookup (..., eTreatFoundThroughLookupAsRefreshed)
258 */
259 static constexpr inline bool kAutomaticallyMarkDataAsRefreshedEachTimeAccessed = AUTO_MARK_DATA_AS_REFRESHED_ON_EACH_WRITABLE_ACCESS;
260 };
261
262 /**
263 * @brief Default choices for TimedCache, if you don't specify anything else.
264 *
265 * @tparam KEY
266 * @tparam VALUE
267 */
268 template <IKey KEY, IValue VALUE>
271#if qCompilerAndStdLib_FloatNonTypeTemplateArgument_Buggy
272 (int)
273#endif
276
277 /**
278 * @brief InternallySynchronizedTraits same as argument traits, but resetting the kInternallySynchronized to eInternallySynchronized
279 *
280 * @tparam TRAITS
281 */
282 template <typename TRAITS>
285 static constexpr inline Execution::InternallySynchronized kInternallySynchronized{Execution::InternallySynchronized::eInternallySynchronized};
286 };
287
288 /**
289 * @brief add the argument bool PER_CACHE_MAX_AGE as the kPerCacheMaxAge property.
290 *
291 * @tparam TRAITS
292 */
293 template <typename TRAITS, bool PER_CACHE_MAX_AGE = true>
295 struct PerInstanceMaxAgeTraits : TRAITS {
296 static constexpr bool kPerCacheMaxAge{PER_CACHE_MAX_AGE};
297 };
298
299 /**
300 * @brief add the argument bool WRITE_LOCK_DURING_CACHE_FILL as the kHoldWriteLockDuringCacheFill property.
301 *
302 * @tparam TRAITS
303 */
304 template <typename TRAITS, bool WRITE_LOCK_DURING_CACHE_FILL = true>
306 static constexpr bool kHoldWriteLockDuringCacheFill{WRITE_LOCK_DURING_CACHE_FILL};
307 };
308
309 /**
310 * @brief take argument TRAITS, and set to track-expires-at mode.
311 *
312 * @tparam TRAITS
313 */
314 template <typename TRAITS>
316 struct TrackExpirationTraits : TRAITS {
317 static constexpr inline bool kTrackFreshness{false};
318 static constexpr inline bool kTrackExpiration{true};
319 static constexpr inline bool kAutomaticallyMarkDataAsRefreshedEachTimeAccessed{false}; // doesn't work with expiration based cache
320 };
321
322 enum class [[deprecated ("Since Stroika 3.0d23 use TRAITS kAutomaticPurgeFrequency")]] PurgeSpoiledDataFlagType {
323 eAutomaticallyPurgeSpoiledData,
324 eDontAutomaticallyPurgeSpoiledData
325 };
326 enum class [[deprecated (
327 "Since Stroika 3.0d23 use TRAITS kAutomaticallyMarkDataAsRefreshedEachTimeAccessed")]] LookupMarksDataAsRefreshed {
328 eTreatFoundThroughLookupAsRefreshed,
329 eDontTreatFoundThroughLookupAsRefreshed
330 };
331
332 }
333
334 /**
335 * \brief Keep track of a bunch of objects, each with an associated time used to allow data to 'expire'.
336 *
337 * This expiration time is handled PRINCIPALLY, in one of two ways:
338 * kTrackExpiration:
339 * In this case, at the time the data is ADDED, a time of expiration is captured and associated with the datum.
340 *
341 * kTrackFreshness:
342 * In this case, the time the data is Added (last-refreshed) is associated with the datum, and expiration computed later.
343 * **this is the default**
344 *
345 * More about kTrackFreshness:
346 * We define 'fresheness' somewhat arbitrarily, but by default, this means since the item was added. However, the TimedCache
347 * also provides other apis to update the 'freshness' of a stored object, depending on application needs.
348 *
349 * Keeps track of all items - indexed by Key - but throws away items which are any more
350 * stale than given by the staleness limit.
351 *
352 * When/Why to use?:
353 * (@todo revise these docs - next 15 lines)
354 * The idea behind this cache is to track when something is added, and that the lookup function can avoid
355 * a costly call to compute something if its been recently enough added.
356 *
357 * For example, consider a system where memory is stored across a slow bus, and several components need to read data from
358 * across that bus. But the different components have different tolerance for staleness (e.g. PID loop needs fresh temperature sensor
359 * data but GUI can use more stale data).
360 *
361 * This CallerStalenessCache will store when the value is updated, and let the caller either return the
362 * value from cache, or fetch it and update the cache if needed.
363 *
364 * This differs from other forms of caches in that:
365 * o It records the timestamp when a value is last-updated
366 * o It doesn't EXPIRE the data ever (except by explicit Clear or ClearOlderThan call)
367 * o The lookup caller specifies its tolerance for data staleness, and refreshes the data as needed.
368 *
369 * \note Comparison with LRUCache
370 * The main difference beweeen an LRUCache and TimedCache has to do with when an element is evicted from the Cache.
371 * With a TimedCache, its evicted only when its overly aged. With an LRUCache, its more random, and depends a
372 * bit on luck (when using hashing) and how recently an item was last accessed.
373 *
374 * \note TimedCache (since Stroika v3.0d23) fully supports the caching model from the Stroika v2.1
375 * CallerStalenessCache (which is now obsolete). If the cache uses 'Freshness' instead of 'Expired' (the default)
376 * then the various Lookup APIs fully support specifying a (non-defaulted, caller specified) 'staleness'.
377 *
378 * \par Example Usage
379 * Use TimedCache to avoid needlessly redundant lookups
380 * \code
381 * optional<String> ReverseDNSLookup_ (const InternetAddress& inetAddr)
382 * {
383 * const Time::Duration kCacheTTL_{5min};
384 * static Cache::TimedCache<InternetAddress, optional<String>> sCache_{kCacheTTL_}; // not threadsafe (not internally synchronized) by default - but checked with Debug::AssertExternallySyncrhonizedMutex
385 * return sCache_.LookupValue (inetAddr, [] (const InternetAddress& inetAddr) {
386 * return DNS::kThe.ReverseLookup (inetAddr);
387 * });
388 * }
389 * \endcode
390 *
391 * \par Example Usage
392 * Assume 'LookupDiskStats_' returns DiskSpaceUsageType, but its expensive, and the results change only slowly...
393 *
394 * \code
395 * struct DiskSpaceUsageType {
396 * int size;
397 * };
398 * // do the actual lookup part which maybe slow
399 * auto LookupDiskStats_ ([[maybe_unused]] const String& filename) -> DiskSpaceUsageType { return DiskSpaceUsageType{33}; };
400 *
401 * Cache::TimedCache<String, DiskSpaceUsageType> sDiskUsageCache_{5.0_duration};
402 * OR
403 * struct CACHE_TRAITS_ : Cache::TimedCacheSupport::DefaultTraits<String, DiskSpaceUsageType> {
404 * static constexpr inline bool kAutomaticallyMarkDataAsRefreshedEachTimeAccessed = true; // to treat lookups as 'refreshing' the cache like LRU
405 * };
406 * Cache::TimedCache<String, DiskSpaceUsageType, CACHE_TRAITS_> sDiskUsageCache_{5.0_duration};
407 *
408 * // explicitly caller maintaining the cache
409 * optional<DiskSpaceUsageType> LookupDiskStats_Try1 (String diskName)
410 * {
411 * optional<DiskSpaceUsageType> o = sDiskUsageCache_.Lookup (diskName);
412 * if (not o.has_value ()) {
413 * o = LookupDiskStats_ (diskName);
414 * if (o) {
415 * sDiskUsageCache_.Add (diskName, *o);
416 * }
417 * }
418 * return o;
419 * }
420 *
421 * // more automatic maintainance of that update pattern
422 * DiskSpaceUsageType LookupDiskStats_Try2 (String diskName)
423 * {
424 * return sDiskUsageCache_.LookupValue (diskName,
425 * [](String diskName) -> DiskSpaceUsageType {
426 * return LookupDiskStats_ (diskName);
427 * });
428 * }
429 *
430 * // or still simpler
431 * DiskSpaceUsageType LookupDiskStats_Try3 (String diskName)
432 * {
433 * // maybe use eTreatFoundThroughLookupAsRefreshed depending on your application
434 * return sDiskUsageCache_.LookupValue (diskName, LookupDiskStats_);
435 * }
436 * void DoIt ()
437 * {
438 * // example usage
439 * EXPECT_TRUE (Memory::NullCoalesce (LookupDiskStats_Try1 ("xx")).size == 33);
440 * EXPECT_TRUE (LookupDiskStats_Try2 ("xx").size == 33);
441 * EXPECT_TRUE (LookupDiskStats_Try3 ("xx").size == 33);
442 * }
443 * \endcode
444 *
445 * \par Example Usage
446 * Same as above, but adding internal syncrhonization (automatic thread safety)
447 * \code
448 * optional<String> ReverseDNSLookup_ThreadSafe_ (const InternetAddress& inetAddr)
449 * {
450 * const Time::Duration kCacheTTL_{5min};
451 * struct CACHE_TRAITS_ : Cache::TimedCacheSupport::DefaultTraits<InternetAddress, optional<String>> {
452 * static constexpr inline InternallySynchronized kInternallySynchronized{InternallySynchronized::eInternallySynchronized};
453 * };
454 * static Cache::TimedCache<InternetAddress, optional<String>, CACHE_TRAITS_> sCache_{kCacheTTL_}; // NOW the cache is threadsafe
455 * return sCache_.LookupValue (inetAddr, [] (const InternetAddress& inetAddr) {
456 * return DNS::kThe.ReverseLookup (inetAddr);
457 * });
458 * }
459 * \endcode
460 *
461 * \par Example Usage (caller specifies staleness in each context where lookup happens)
462 * \code
463 * // keyed cache
464 * optional<int> MapValue_ (int value, optional<Time::DurationSeconds> allowedStaleness = {})
465 * {
466 * static CallerStalenessCache<int, optional<int>> sCache_;
467 * try {
468 * return sCache_.LookupValue (value, allowedStaleness.value_or (30), [=](int v) -> optional<int> {
469 * return v; // typically more expensive computation
470 * });
471 * }
472 * catch (...) {
473 * // NOTE - to NEGATIVELY CACHE failure, you could call sCache_.Add (value, nullopt);
474 * // return null here, or Execution::ReThrow ()
475 * }
476 * }
477 * EXPECT_EQ (MapValue_ (1), 1); // skips 'more expensive computation' if in cache
478 * EXPECT_EQ (MapValue_ (2), 2); // ''
479 * \endcode
480 *
481 * \par Example Usage (no key, and 'caller staleness' style)
482 * \code
483 * optional<InternetAddress> LookupExternalInternetAddress_ (optional<Time::DurationSeconds> allowedStaleness = {})
484 * {
485 * static TimedCache<NonKeyedKeySentinalType, optional<InternetAddress>> sCache_;
486 * return sCache_.Lookup (allowedStaleness.value_or (30), []() -> optional<InternetAddress> {
487 * ...
488 * return IO::Network::InternetAddress{connection.GET ().GetDataTextInputStream ().ReadAll ().Trim ()};
489 * });
490 * }
491 * optional<InternetAddress> iaddr = LookupExternalInternetAddress_ (); // only invoke connection logic if timed out
492 * \endcode
493 *
494 * \par Example Usage
495 * @todo REVIEW DOCS FROM HERE DOWN
496 *
497 * To use TimedCache<> to 'own' a set of objects (say a set caches where we are the only
498 * possible updater) - you can make the 'VALUE' type a shared_ptr<X>, and use Lookup (...,eTreatFoundThroughLookupAsRefreshed) instead
499 * of Lookup ().
500 *
501 * In this example, there is a set of files on disk in a folder, which is complex to analyze
502 * but once analyzed, lots of calls come in at once to read (and maybe update) the set of files
503 * and once nobody has asked for a while, we throw that cache away, and rebuild it as needed.
504 *
505 * This example ALSO shows how to wrap a cache object in 'Synchronized' for thread safety.
506 *
507 * \code
508 * using ScanFolderKey_ = String;
509 * static constexpr Time::DurationSeconds kAgeForScanPersistenceCache_{5 * 60.0s};
510 * struct FolderDetails_ {
511 * int size; // ...info to cache about a folder
512 * };
513 * Synchronized<Cache::TimedCache<
514 * ScanFolderKey_,
515 * shared_ptr<FolderDetails_>,
516 * shared_ptr<FolderDetails_>>>
517 * sCachedScanFoldersDetails_{kAgeForScanPersistenceCache_};
518 *
519 * shared_ptr<FolderDetails_> AccessFolder_ (const ScanFolderKey_& folder)
520 * {
521 * auto lockedCache = sCachedScanFoldersDetails_.rwget ();
522 * if (optional<shared_ptr<FolderDetails_>> o = lockedCache->Lookup (folder, eTreatFoundThroughLookupAsRefreshed)) {
523 * return *o;
524 * }
525 * else {
526 * shared_ptr<FolderDetails_> fd = MakeSharedPtr<FolderDetails_> (); // and fill in default values looking at disk
527 * lockedCache->Add (folder, fd);
528 * return fd;
529 * }
530 * }
531 *
532 * void DoIt ()
533 * {
534 * auto f1 = AccessFolder_ ("folder1"_k);
535 * auto f2 = AccessFolder_ ("folder2"_k);
536 * auto f1again = AccessFolder_ ("folder1"); // if you trace through the debug code you'll see this is a cache hit
537 * }
538 * \endcode
539 *
540 * \par Example Usage
541 * Use 'void' as second type argument - just storing the first KEY value (like keyedCollection or just presence/absence test - like accessKeys)
542 *
543 * \code
544 * Cache::TimedCache<Characters::String, void> validAccessKeyCache{30s};
545 * EXPECT_TRUE (validAccessKeyCache.GetExpiration ("fred") == nullopt);
546 * validAccessKeyCache.Add ("fred");
547 * EXPECT_TRUE (validAccessKeyCache.GetExpiration ("fred"));
548 * \endcode
549 *
550 * \note This cache will keep using more and more memory until the cached items become
551 * out of date. For a cache that limits the max number of entries, use the @see LRUCache.
552 *
553 * \note This cache assumes one timeout for all items. To have timeouts vary by item,
554 * @see CallerStalenessCache.
555 *
556 * \note Satisfies Concepts:
557 * o ICache<TimedCache<KEY,VALUE>,KEY,VALUE>
558 * o moveable<TimedCache<KEY,VALUE>>
559 * o copyable<TimedCache<KEY,VALUE>>
560 *
561 * \note \em Thread-Safety if (TRAITS::kInternallySynchronized == eInternallySynchronized) <a href='#Internally-Synchronized-Thread-Safety'>Internally-Synchronized-Thread-Safety</a>
562 * \note \em Thread-Safety if (TRAITS::kInternallySynchronized == eNotKnownInternallySynchronized) <a href="Thread-Safety.md#C++-Standard-Thread-Safety">C++-Standard-Thread-Safety</a>
563 *
564 * \note we REQUIRE (without a way to enforce) - that the STATS object be internally synchronized, so that we can
565 * maintain statistics, without requiring the lookup method be non-const; this is only for tuning/debugging, anyhow...
566 *
567 * \note Implementation Note: '_' private routines assume called with locks in place, and just do the minimal function (except when otherwise noted).
568 *
569 * @see LRUCache
570 */
571 template <IKey KEY, IValue VALUE, TimedCacheSupport::ITraits<KEY, VALUE> TRAITS = TimedCacheSupport::DefaultTraits<KEY, VALUE>>
573 public:
574 using TraitsType = TRAITS;
575
576 public:
577 /**
578 */
579 using TimeStampType = typename TRAITS::TimeStampType;
580
581 public:
582 /**
583 */
584 using TimeStampDifferenceType = typename TRAITS::TimeStampDifferenceType;
585
586 public:
587 /**
588 * @brief Track on a per-item when it expires. If not tracked, we use expiresAt as whenAdded + maxAge
589 * This is false by default
590 *
591 * \note kTrackFreshness and kTrackExpiration are mutually exclusive
592 */
593 static constexpr bool kTrackExpiration = TRAITS::kTrackExpiration;
594
595 public:
596 /**
597 * @brief freshness means when last added/updated (or if kAutomaticallyMarkDataAsRefreshedEachTimeAccessed) then last accessed too)
598 * This is true by default
599 *
600 * \note kTrackFreshness and kTrackExpiration are mutually exclusive
601 */
602 static constexpr bool kTrackFreshness = TRAITS::kTrackFreshness;
603
604 public:
605 /**
606 * Note that TimedCache is copyable and moveable by value.
607 *
608 * It MAYBE default constructible (if there is a kDefaultMaxAge - defaults to true).
609 */
610 TimedCache ()
611 requires (not is_empty_v<decltype (TRAITS::kDefaultMaxAge)>);
612 explicit TimedCache (TimeStampDifferenceType maxAge)
613 requires (TRAITS::kPerCacheMaxAge);
614 TimedCache (TimedCache&& src) noexcept;
615 TimedCache (const TimedCache& src);
616
617 public:
618 nonvirtual TimedCache& operator= (TimedCache&& rhs) noexcept;
619 nonvirtual TimedCache& operator= (const TimedCache& rhs);
620
621 private:
622 // same as GetMaxAge, but doesnt do locking
623 constexpr TimeStampDifferenceType GetMaxAge_ () const;
624
625 public:
626 /**
627 * When items are added to the timed cache, there is a universal (for the entire cache) minimum allowed freshness (how old item
628 * allowed to be before thrown away).
629 *
630 * \alias Note - 'allowed freshness' == 'time to live' == 'TTL'.
631 * GetMinimumFreshness, GetTimeout, TTL, GetDefaultMaxAge ().
632 *
633 * So an item added 30 seconds ago (freshness = 30s), would be thrown away/not returned as part of the cache
634 * if the minimum allowed freshness was 5 seconds.
635 *
636 * \note if kTrackFreshness, this is used in CHECKS of freshness (provides a default 'maxage' at the point of Lookup call).
637 * if kTrackExpiration, this is applied to the added item, as its default expiration. In both cases, these values
638 * can be overridden (either in Add or Lookup depending on which you are tracking).
639 */
640 nonvirtual TimeStampDifferenceType GetMaxAge () const;
641
642 public:
643 /**
644 * @see GetMaxAge ()
645 */
646 nonvirtual void SetMaxAge (TimeStampDifferenceType maxAge)
647 requires (TRAITS::kPerCacheMaxAge);
648
649 public:
650 /**
651 * @brief everything here is optional ;-) But typically, its fKey, fValue, fLastRefreshedAt
652 */
654 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<same_as<KEY, NonKeyedKeySentinalType>, Common::Empty, KEY> fKey;
655 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<IValuelessCache<VALUE>, Common::Empty, VALUE> fValue;
656 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<kTrackFreshness, TimeStampType, Common::Empty> fLastRefreshedAt;
657 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<kTrackExpiration, TimeStampType, Common::Empty> fExpiresAt;
658 };
659
660 public:
661 /**
662 * \note This returns the non-expired elements of the current cache object.
663 *
664 * @todo could use overload taking TTL argumnet (if track freshness)
665 */
666 nonvirtual Traversal::Iterable<CacheElement> Elements () const;
667
668 public:
669 /**
670 * \note This returns the non-expired keys of the current cache object.
671 *
672 * @todo could use overload taking TTL argumnet (if track freshness)
673 */
674 template <typename K = KEY>
675 nonvirtual Traversal::Iterable<K> Keys () const
676 requires (IKeyedCache<K>);
677
678 public:
679 /**
680 * \brief Returns the value associated with argument 'key', or nullopt, if its missing (missing same as expired). Can be used to retrieve lastRefreshedAt
681 *
682 * \note that the non-const overload of Lookup respects TRAITS::kAutomaticallyMarkDataAsRefreshedEachTimeAccessed, and will
683 * auto-refresh the item (similar to LRUCache) if found.
684 *
685 * Occasionally, a caller might want to ASSURE it gets data, and just use the cached value if fresh enuf, and specify
686 * a lookup lambda to fetch the actual data if its not fresh, in which case call LookupValue ().
687 *
688 * \note difference between const and non-const overloads is just that some extra bookkeeping can be done and kAutomaticallyMarkDataAsRefreshedEachTimeAccessed respected in non-const overload.
689 *
690 * \note for Lookup (key)
691 * whether we track freshness or expiration, the API looks the same (though the impl differs).
692 * \note for Lookup (key,maxAge)
693 * only possible if you track freshness
694 * no non-const version of this, cuz no bookkeeping. If you are using this, the caller decides what gets cleaned up, explicitly.
695 *
696 * \note @todo - Lookup () - for VALUELESS collection - argument K should be not just K, but anything COMPARABLE with K!!! - that way
697 * strictly more flexible - but can fix this later, since compatible API
698 */
699 template <typename K = KEY>
700 requires (IKeyedCache<K>)
701 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key) const;
702 template <typename K = KEY>
703 requires (IKeyedCache<K>)
704 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key);
705 template <typename K = KEY>
706 requires (IKeyedCache<K> and TRAITS::kTrackFreshness)
707 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, TimeStampDifferenceType maxAge) const;
708 template <typename K = KEY>
709 requires (not IKeyedCache<K>)
710 nonvirtual optional<VALUE> Lookup () const;
711 template <typename K = KEY>
712 requires (not IKeyedCache<K>)
713 nonvirtual optional<VALUE> Lookup ();
714 template <typename K = KEY>
715 requires (not IKeyedCache<K> and TRAITS::kTrackFreshness)
716 nonvirtual optional<VALUE> Lookup (TimeStampDifferenceType maxAge) const;
717 template <typename K = KEY, typename V = VALUE>
718 requires (IKeyedCache<K> and IValuelessCache<V>)
719 nonvirtual optional<KEY> Lookup (typename Common::ArgByValueType<K> key) const;
720
721 public:
722 /**
723 * \brief Returns the value associated with argument 'key' (if IKeyedCache), or nullopt, if its missing (missing same as expired). Like Lookup but also returns expiration information.
724 *
725 * \note that the non-const overload of Lookup respects TRAITS::kAutomaticallyMarkDataAsRefreshedEachTimeAccessed, and will
726 * auto-refresh the item (similar to LRUCache) if found.
727 *
728 * Occasionally, a caller might want to ASSURE it gets data, and just use the cached value if fresh enuf, and specify
729 * a lookup lambda to fetch the actual data if its not fresh, in which case call LookupValue ().
730 *
731 * \note difference between const and non-const overloads is just that some extra bookkeeping can be done and kAutomaticallyMarkDataAsRefreshedEachTimeAccessed respected in non-const overload.
732 */
733 template <typename K = KEY>
734 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key) const
735 requires (kTrackExpiration and IKeyedCache<K>);
736 template <typename K = KEY>
737 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key)
738 requires (kTrackExpiration and IKeyedCache<K>);
739 template <typename K = KEY>
740 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key) const
741 requires (kTrackFreshness and IKeyedCache<K>);
742 template <typename K = KEY>
743 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key)
744 requires (kTrackFreshness and IKeyedCache<K>);
745 template <typename K = KEY>
746 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key, TimeStampDifferenceType maxAge) const
747 requires (kTrackFreshness and IKeyedCache<K>);
748 template <typename K = KEY>
749 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (TimeStampDifferenceType maxAge) const
750 requires (not IKeyedCache<K>);
751 template <typename K = KEY, typename V = VALUE>
752 requires (IKeyedCache<K> and IValuelessCache<V>)
753 nonvirtual optional<tuple<KEY, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key) const;
754
755 public:
756 /**
757 * @brief Get the Expiration of object or nullopt of item expired/not in cache
758 *
759 * in the case of IKeyedCache, it returns the expiration of the given key (or nullopt if not present or expired).
760 * in the case of NOT IKeyedCache, it refers to the single cached item, but otherwise acts the same.
761 */
762 template <typename K = KEY>
763 nonvirtual optional<TimeStampType> GetExpiration () const
764 requires (not IKeyedCache<K>);
765 template <typename K = KEY>
766 nonvirtual optional<TimeStampType> GetExpiration (typename Common::ArgByValueType<K> key) const
767 requires (IKeyedCache<K>);
768
769 public:
770 /**
771 * @brief Lookup value, and if missing, fetch it with argument cacheFiller (and add/return its value).
772 *
773 * This operates as if:
774 * if (auto ov = Lookup (key)) {
775 * return *ov;
776 * }
777 * else {
778 * VALUE r = cacheFiller();
779 * Add (r);
780 * return r;
781 * }
782 *
783 * \note **this is the PREFERRED API for adding/looking up in TimedCache**
784 *
785 * Usually one will use this as:
786 * \code
787 * VALUE v = cache.LookupValue (key, ts, [this] () -> VALUE {return this->realLookup(key); });
788 * \endcode
789 *
790 * \note This function may update the TimedCache (which is why it is non-const).
791 *
792 * \note Any time arguments given constrain the lookup. They are not used for the Add ().
793 */
794 template <typename K = KEY, Common::invocable_r<VALUE, KEY> CACHE_FILLTER_T>
795 requires (IKeyedCache<K>)
796 nonvirtual VALUE LookupValue (typename Common::ArgByValueType<K> key, CACHE_FILLTER_T&& cacheFiller);
797 template <typename K = KEY, Common::invocable_r<VALUE, KEY> CACHE_FILLTER_T>
798 requires (IKeyedCache<K> and TRAITS::kTrackFreshness)
799 nonvirtual VALUE LookupValue (typename Common::ArgByValueType<K> key, TimeStampDifferenceType maxAge, CACHE_FILLTER_T&& cacheFiller);
800 template <typename K = KEY, Common::invocable_r<VALUE> CACHE_FILLTER_T>
801 requires (not IKeyedCache<K>)
802 nonvirtual VALUE LookupValue (CACHE_FILLTER_T&& cacheFiller);
803 template <typename K = KEY, Common::invocable_r<VALUE> CACHE_FILLTER_T>
804 requires (not IKeyedCache<K> and TRAITS::kTrackFreshness)
805 nonvirtual VALUE LookupValue (TimeStampDifferenceType maxAge, CACHE_FILLTER_T&& cacheFiller);
806 template <typename K = KEY, typename V = VALUE, Common::invocable_r<K, K> CACHE_FILLTER_T>
807 requires (IKeyedCache<K> and IValuelessCache<V>)
808 nonvirtual KEY LookupValue (typename Common::ArgByValueType<K> key, CACHE_FILLTER_T&& cacheFiller);
809
810 public:
811 /**
812 * Adds/Updates the given value associated with key.
813 * if kTrackFreshness (the default)
814 * o The new items freshness is TRAITS::GetCurrentTimestamp (), or the value given as argument
815 * if kTrackExpiration
816 * o The new item's expiration is either given by expiresAt or now+ttl, or defaults to
817 * GetMaxAge()
818 *
819 * \note this API supports overloads of Add () where either the KEY or the VALUE is missing, depending on how
820 * the TimedCache TRAITS were declared.
821 */
822 template <typename K = KEY>
823 requires (IKeyedCache<K> and IValuelessCache<VALUE>)
824 nonvirtual void Add (typename Common::ArgByValueType<K> key);
825 template <typename K = KEY, typename V = VALUE>
826 requires (IKeyedCache<K> and not IValuelessCache<V>)
827 nonvirtual void Add (typename Common::ArgByValueType<K> key, typename Common::ArgByValueType<V> result);
828 template <typename K = KEY, typename V = VALUE>
829 requires (IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackExpiration)
830 nonvirtual void Add (typename Common::ArgByValueType<K> key, typename Common::ArgByValueType<V> result, TimeStampType expiresAt);
831 template <typename K = KEY, typename V = VALUE>
832 requires (IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackFreshness)
833 nonvirtual void Add (typename Common::ArgByValueType<K> key, typename Common::ArgByValueType<V> result, TimeStampType freshAsOf);
834 template <typename K = KEY, typename V = VALUE>
835 requires (not IKeyedCache<K> and not IValuelessCache<V>)
836 nonvirtual void Add (typename Common::ArgByValueType<V> result);
837 template <typename K = KEY, typename V = VALUE>
838 requires (not IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackFreshness)
839 nonvirtual void Add (typename Common::ArgByValueType<V> result, TimeStampType freshAsOf);
840 template <typename K = KEY, typename V = VALUE>
841 requires (not IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackExpiration)
842 nonvirtual void Add (typename Common::ArgByValueType<V> result, TimeStampType expiresAt);
843
844 private:
845 // no locking or bookkeeping - just add the item
846 template <typename K = KEY>
847 requires (IKeyedCache<K> and IValuelessCache<VALUE> and TRAITS::kTrackExpiration)
848 nonvirtual void Add_ (typename Common::ArgByValueType<K> key, TimeStampType expiresAt);
849 template <typename K = KEY>
850 requires (IKeyedCache<K> and IValuelessCache<VALUE> and TRAITS::kTrackFreshness)
851 nonvirtual void Add_ (typename Common::ArgByValueType<K> key, TimeStampType freshAsOf);
852
853 template <typename K = KEY, typename V = VALUE>
854 requires (IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackFreshness)
855 nonvirtual void Add_ (typename Common::ArgByValueType<K> key, typename Common::ArgByValueType<V> result, TimeStampType freshAsOf);
856 template <typename K = KEY, typename V = VALUE>
857 requires (IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackExpiration)
858 nonvirtual void Add_ (typename Common::ArgByValueType<K> key, typename Common::ArgByValueType<V> result, TimeStampType expiresAt);
859 template <typename K = KEY, typename V = VALUE>
860 requires (not IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackFreshness)
861 nonvirtual void Add_ (typename Common::ArgByValueType<V> result, TimeStampType freshAsOf);
862 template <typename K = KEY, typename V = VALUE>
863 requires (not IKeyedCache<K> and not IValuelessCache<V> and TRAITS::kTrackExpiration)
864 nonvirtual void Add_ (typename Common::ArgByValueType<V> result, TimeStampType expiresAt);
865
866 public:
867 /**
868 */
869 template <typename K = KEY>
870 requires (IKeyedCache<K>)
871 nonvirtual void Remove (typename Common::ArgByValueType<K> key);
872
873 public:
874 /**
875 */
876 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (predicate<typename TimedCache<KEY, VALUE, TRAITS>::CacheElement>) PREDICATE>
877 nonvirtual void RemoveAll (PREDICATE&& p);
878
879 public:
880 /**
881 * @brief like Mapping<>::RetainAll () - throw away all elements not in items2Keep
882 */
883 template <Traversal::IIterableOfTo<KEY> ITERABLE_OF_KEY_TYPE>
884 nonvirtual void RetainAll (const ITERABLE_OF_KEY_TYPE& items2Keep);
885
886 public:
887 /**
888 * Remove everything from the cache
889 */
890 nonvirtual void clear ();
891
892 public:
893 /**
894 * May be called occasionally to free resources used by cached items that are out of date.
895 * Not necessary to call - but can save memory.
896 *
897 * Can be triggered automatically - see TRAITS::kAutomaticPurgeFrequency
898 */
899 nonvirtual void ClearExpiredData ();
900 nonvirtual void ClearExpiredData (TimeStampDifferenceType maxAge)
901 requires (TRAITS::kTrackFreshness);
902
903 public:
904 /**
905 */
906 nonvirtual typename TRAITS::StatsType GetStats () const;
907
908 private:
909 nonvirtual void AutomaticallyClearExpiredDataSometimes_ ()
910 requires (IKeyedCache<KEY>);
911
912 public:
913 DISABLE_COMPILER_MSC_WARNING_START (4996);
914 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
915 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
916 using LookupMarksDataAsRefreshed
917 [[deprecated ("Since Stroika 3.0d23 use TRAITS kAutomaticallyMarkDataAsRefreshedEachTimeAccessed")]] =
918 TimedCacheSupport::LookupMarksDataAsRefreshed;
919 using PurgeSpoiledDataFlagType
920 [[deprecated ("Since Stroika v3.0d23 - use TRAITS::kAutomaticallyMarkDataAsRefreshedEachTimeAccessed instead")]] =
921 TimedCacheSupport::PurgeSpoiledDataFlagType;
922 [[deprecated ("Since Stroika v3.0d1, use ClearExpiredData or count on Add's purgeSpoiledData parameter)")]] nonvirtual void DoBookkeeping ()
923 {
924 ClearExpiredData ();
925 }
926 [[deprecated ("Since Stroika 3.0d1 use GetMaxAge")]] Time::Duration GetTimeout () const
927 {
928 return GetMaxAge ();
929 }
930 [[deprecated ("Since Stroika 3.0d1 use GetMaxAge")]] void SetTimeout (Time::Duration timeout)
931 {
932 SetMaxAge (timeout);
933 }
934 [[deprecated ("Since Stroika 3.0d23 use GetMaxAge")]]
935 nonvirtual Time::Duration GetMinimumAllowedFreshness () const
936 {
937 return GetMaxAge ();
938 }
939 [[deprecated ("Since Stroika 3.0d23 use SetMaxAge")]]
940 nonvirtual void SetMinimumAllowedFreshness (Time::Duration minimumAllowedFreshness)
941 {
942 SetMaxAge (minimumAllowedFreshness);
943 }
944 template <typename K = KEY>
945 requires (IKeyedCache<K> and TRAITS::kTrackFreshness)
946 [[deprecated ("Since Stroika 3.0d23 use maxAge DURATION directly")]]
947 optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, TimeStampType maxAge) const
948 {
949 return Lookup (key, Ago (maxAge)); // IS THIS BACKWARDS
950 }
951 template <typename K = KEY>
952 [[deprecated ("Since Stroika 3.0d23 use maxAge DURATION directly")]]
953 nonvirtual optional<tuple<VALUE, TimeStampType>> LookupDetails (typename Common::ArgByValueType<K> key, TimeStampType maxAge) const
954 requires (TRAITS::kTrackFreshness and IKeyedCache<K>)
955 {
957 return nullopt;
958 }
959 template <typename K = KEY, Common::invocable_r<VALUE> CACHE_FILLTER_T>
960 requires (not IKeyedCache<K> and TRAITS::kTrackFreshness)
961 nonvirtual VALUE LookupValue (TimeStampType maxAge, CACHE_FILLTER_T&& cacheFiller)
962 {
963 return Lookup (Ago (maxAge), forward<CACHE_FILLTER_T> (cacheFiller));
964 }
965 template <typename K = KEY, Common::invocable_r<VALUE, KEY> CACHE_FILLTER_T>
966 requires (IKeyedCache<K> and TRAITS::kTrackFreshness)
967 [[deprecated ("Since Stroika 3.0d23 use maxAge DURATION directly")]]
968 nonvirtual VALUE LookupValue (typename Common::ArgByValueType<K> key, TimeStampType maxAge, CACHE_FILLTER_T&& cacheFiller)
969 {
970 return Lookup (key, Ago (maxAge), forward<CACHE_FILLTER_T> (cacheFiller));
971 }
972 template <typename K = KEY>
973 [[deprecated ("Since Stroika v3.0d23 - use TRAITS::kAutomaticallyMarkDataAsRefreshedEachTimeAccessed instead")]]
974 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, LookupMarksDataAsRefreshed successfulLookupRefreshesAcceesFlag)
975 {
976 scoped_lock critSec{fMaybeMutex_};
977 typename MyMapType_::iterator i = fData_.find (key);
978 TimeStampType now = TRAITS::GetCurrentTimestamp ();
979 if (i == fData_.end ()) {
980 fStats_.IncrementMisses ();
981 return nullopt;
982 }
983 else {
984 TimeStampType lastAccessThreshold = now - fMaxAge_;
985 if (i->second.fLastRefreshedAt < lastAccessThreshold) {
986 /**
987 * Before Stroika 3.0d1, we used to remove the entry from the list (an optimization). But
988 * that required Lookup to be non-const (with synchronization in mind probably a pessimization).
989 * So instead, count on PurgeUnusedData being called automatically on future adds,
990 * explicit user calls to purge unused data.
991 *
992 * i = fData_.erase (i);
993 */
994 fStats_.IncrementMisses ();
995 return nullopt;
996 }
997 if (successfulLookupRefreshesAcceesFlag == LookupMarksDataAsRefreshed::eTreatFoundThroughLookupAsRefreshed) {
998 i->second.fLastRefreshedAt = TRAITS::GetCurrentTimestamp ();
999 }
1000 fStats_.IncrementHits ();
1001 return i->second.fResult;
1002 }
1003 }
1004 template <typename K = KEY>
1005 requires (IKeyedCache<K>)
1006 [[deprecated ("Since Stroika v3.0d23 - use kAutomaticPurgeFrequency in TRAITS instead of PurgeSpoiledDataFlagType, and "
1007 "kAutomaticallyMarkDataAsRefreshedEachTimeAccessed in TRAITS instead of LookupMarksDataAsRefreshed")]]
1008 nonvirtual VALUE LookupValue (typename Common::ArgByValueType<K> key, const function<VALUE (typename Common::ArgByValueType<K>)>& cacheFiller,
1009 LookupMarksDataAsRefreshed successfulLookupRefreshesAcceesFlag,
1010 PurgeSpoiledDataFlagType purgeSpoiledData = PurgeSpoiledDataFlagType::eAutomaticallyPurgeSpoiledData)
1011 {
1012 auto&& readLock = shared_lock{fMaybeMutex_}; // try shared_lock for case where present, and then lose it if we need to update object
1013 if (optional<VALUE> o = Lookup (key, successfulLookupRefreshesAcceesFlag)) {
1014 return *o;
1015 }
1016 else {
1017 readLock.unlock ();
1018 VALUE v = cacheFiller (key);
1019 Add (key, v, purgeSpoiledData);
1020 return v;
1021 }
1022 }
1023 template <typename K = KEY, typename V = VALUE>
1024 requires (IKeyedCache<K> and not IValuelessCache<V>)
1025 [[deprecated ("Since Stroika v3.0d23 - use kAutomaticPurgeFrequency in TRAITS instead of PurgeSpoiledDataFlagType")]]
1026 nonvirtual void Add (typename Common::ArgByValueType<K> key, typename Common::ArgByValueType<V> result, PurgeSpoiledDataFlagType purgeSpoiledData)
1027 {
1028 scoped_lock critSec{fMaybeMutex_};
1029 if (purgeSpoiledData == PurgeSpoiledDataFlagType::eAutomaticallyPurgeSpoiledData) {
1030 AutomaticallyClearExpiredDataSometimes_ ();
1031 }
1032 typename MyMapType_::iterator i = fData_.find (key);
1033 if (i == fData_.end ()) {
1034 fData_.insert ({key, MyResult_{.fResult = result, .fLastRefreshedAt = TRAITS::GetCurrentTimestamp ()}});
1035 }
1036 else {
1037 i->second = MyResult_{.fResult = result, .fLastRefreshedAt = TRAITS::GetCurrentTimestamp ()}; // overwrite if its already there
1038 }
1039 }
1040 template <typename K = KEY>
1041 [[deprecated ("Since Stroika v3.0d23 - use LookupDetails")]]
1042 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, TimeStampType* lastRefreshedAt) const
1043 requires (TRAITS::kTrackFreshness)
1044 {
1045 if (auto r = LookupDetails (key)) {
1046 if (lastRefreshedAt != nullptr) {
1047 *lastRefreshedAt = get<2> (*r);
1048 }
1049 return get<1> (*r);
1050 }
1051 }
1052 template <typename K = KEY>
1053 [[deprecated ("Since Stroika v3.0d23 - use LookupDetails")]]
1054 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, TimeStampType* lastRefreshedAt)
1055 requires (TRAITS::kTrackFreshness)
1056 {
1057 if (auto r = LookupDetails (key)) {
1058 if (lastRefreshedAt != nullptr) {
1059 *lastRefreshedAt = get<2> (*r);
1060 }
1061 return get<1> (*r);
1062 }
1063 }
1064 template <typename K = KEY>
1065 [[deprecated ("Since Stroika v3.0d23 - use LookupDetails")]]
1066 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, TimeStampType* expiresAt) const
1067 requires (TRAITS::kTrackExpiration)
1068 {
1069 if (auto r = LookupDetails (key)) {
1070 if (expiresAt != nullptr) {
1071 *expiresAt = get<2> (*r);
1072 }
1073 return get<1> (*r);
1074 }
1075 }
1076 template <typename K = KEY>
1077 [[deprecated ("Since Stroika v3.0d23 - use LookupDetails")]]
1078 nonvirtual optional<VALUE> Lookup (typename Common::ArgByValueType<K> key, TimeStampType* expiresAt)
1079 requires (TRAITS::kTrackExpiration)
1080 {
1081 if (auto r = LookupDetails (key)) {
1082 if (expiresAt != nullptr) {
1083 *expiresAt = get<2> (*r);
1084 }
1085 return get<1> (*r);
1086 }
1087 }
1088 DISABLE_COMPILER_MSC_WARNING_END (4996);
1089 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"");
1090 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"");
1091
1092 private:
1093 // note if shared_mutex, it must be mutable, cuz shared locks still must be done
1094 using MaybeMutexType_ =
1095 conditional_t<TRAITS::kInternallySynchronized == Execution::InternallySynchronized::eInternallySynchronized, shared_timed_mutex, Debug::AssertExternallySynchronizedMutex>;
1096 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE mutable MaybeMutexType_ fMaybeMutex_;
1097
1098 private:
1099 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<TRAITS::kPerCacheMaxAge, TimeStampDifferenceType, Common::Empty> fMaxAge_;
1101 conditional_t<TRAITS::kAutomaticPurgeFrequency == TimeStampDifferenceType{TimedCacheSupport::kNoAutomaticPurgeSentinal}, Common::Empty, TimeStampType>
1102 fNextAutoClearAt_;
1103
1104 private:
1105 nonvirtual void ClearExpired_ ()
1106 requires (IKeyedCache<KEY>);
1107 nonvirtual void ClearExpired_ (TimeStampDifferenceType maxAge)
1108 requires (IKeyedCache<KEY> and TRAITS::kTrackFreshness);
1109
1110 private:
1111 // per-key 'value' data we track - includes both the 'VALUE' in expiration/time information
1112 struct MyResult_ {
1113 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<IValuelessCache<VALUE>, Common::Empty, VALUE> fResult;
1114 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<kTrackFreshness, TimeStampType, Common::Empty> fLastRefreshedAt;
1115 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE conditional_t<kTrackExpiration, TimeStampType, Common::Empty> fExpiresAt;
1116
1117 template <typename K = KEY>
1118 nonvirtual CacheElement MakeCacheElement (const K& key) const;
1119 };
1120
1121 private:
1122 nonvirtual bool Expired_ (const MyResult_& r, TimeStampType now = TRAITS::GetCurrentTimestamp ()) const;
1123
1124 private:
1125 // @todo could consider using Stroika Mapping<> - or TRAITS specified Mapper strategy
1126 using MyMapType_ =
1127 conditional_t<IKeyedCache<KEY>, Common::LazyType_t<map, KEY, MyResult_, typename TRAITS::InOrderComparerType>, optional<MyResult_>>;
1128 MyMapType_ fData_;
1129
1130 private:
1131 // most PRIVATE(_) routines assume locking done externally, but these 'Locking' ones do the locking themselves.
1132 template <typename K = KEY, Common::invocable_r<VALUE> CACHE_FILLTER_T>
1133 requires (not IKeyedCache<K>)
1134 nonvirtual VALUE LockingLookupValueAdder_ (CACHE_FILLTER_T&& cacheFiller);
1135 template <typename K = KEY, Common::invocable_r<VALUE, KEY> CACHE_FILLTER_T>
1136 requires (IKeyedCache<K>)
1137 nonvirtual VALUE LockingLookupValueAdder_ (typename Common::ArgByValueType<K> key, CACHE_FILLTER_T&& cacheFiller);
1138
1139 private:
1140 qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE mutable typename TRAITS::StatsType fStats_;
1141 };
1142 static_assert (ICache<TimedCache<int, int>, int, int>); // see Satisfies Concepts
1143 static_assert (movable<TimedCache<int, int>>);
1144 static_assert (copyable<TimedCache<int, int>>);
1145
1146 /**
1147 * @brief SynchronizedTimedCache just adds eInternallySynchronized to a regular 'TimedCache' (just short-hand).
1148 *
1149 * @tparam KEY
1150 * @tparam VALUE
1151 * @tparam TRAITS
1152 *
1153 * \par Example Usage
1154 * Use TimedCache to avoid needlessly redundant lookups
1155 * \code
1156 * optional<String> ReverseDNSLookup_ (const InternetAddress& inetAddr)
1157 * {
1158 * static SynchronizedTimedCache<InternetAddress, optional<String>> sCache_;
1159 * // Or could write like this
1160 * static TimedCache<InternetAddress, optional<String>, TimedCacheSupport::InternallySynchronizedTraits<TimedCacheSupport::DefaultTraits<InternetAddress, optional<String>>>> sCache2_;
1161 * return sCache_.LookupValue (inetAddr, [] (const InternetAddress& inetAddr) {
1162 * return DNS::kThe.ReverseLookup (inetAddr);
1163 * });
1164 * }
1165 * \endcode
1166 *
1167 * \note this CAN be done pretty easily without defining SynchronizedTimedCache (e.g. InternallySynchronizedTraits), but this is enough more terse
1168 * and common enough to be useful.
1169 */
1170 template <typename KEY, typename VALUE, TimedCacheSupport::ITraits<KEY, VALUE> TRAITS = TimedCacheSupport::DefaultTraits<KEY, VALUE>>
1172
1173}
1174
1175/*
1176 ********************************************************************************
1177 ***************************** Implementation Details ***************************
1178 ********************************************************************************
1179 */
1180#include "TimedCache.inl"
1181
1182#endif /*_Stroika_Foundation_Cache_TimedCache_h_*/
#define AssertNotImplemented()
Definition Assertions.h:402
conditional_t< qStroika_Foundation_Debug_AssertionsChecked, Stats_Basic, Stats_Null > StatsType_DEFAULT
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Definition StdCompat.h:445
Keep track of a bunch of objects, each with an associated time used to allow data to 'expire'.
Definition TimedCache.h:572
nonvirtual Traversal::Iterable< K > Keys() const
nonvirtual optional< VALUE > Lookup(typename Common::ArgByValueType< K > key, LookupMarksDataAsRefreshed successfulLookupRefreshesAcceesFlag)
Definition TimedCache.h:974
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
A KEY is any copyable value (or the sentinal type void - indicating a keyless - single valued - cache...
any copyable type can use used as the value, or the special sentinal type - ValuelessSentinalType,...
Check if argument TRAITS is a valid TRAITS object for TimedCache<>
Definition TimedCache.h:92
constexpr float kDefaultAutomaticPurgeFrequency
see TimedCache<>::TraitsType::kAutomaticPurgeFrequency - default to purging every 5 minutes
Definition TimedCache.h:81
constexpr float kDefaultMaxAge
The default 'TTL'/Max-Age of items added to a TimedCache, if not specified/overriden anyplace else (1...
Definition TimedCache.h:70
constexpr float kNoAutomaticPurgeSentinal
see TimedCache<>::TraitsType::kAutomaticPurgeFrequency - disable automatic purging
Definition TimedCache.h:62
conditional_t<(sizeof(CHECK_T)<=2 *sizeof(void *)) and is_trivially_copyable_v< CHECK_T >, CHECK_T, const CHECK_T & > ArgByValueType
This is an alias for 'T' - but how we want to pass it on stack as formal parameter.
Definition TypeHints.h:36
everything here is optional ;-) But typically, its fKey, fValue, fLastRefreshedAt
Definition TimedCache.h:653
STATS_TYPE StatsType
Internally synchronized 'Stats' collector type (Cache::Statistics::IStatsType). Often null stats coll...
Definition TimedCache.h:234
STRICT_INORDER_COMPARER InOrderComparerType
if IKeyedCache, this is a how the KEY type elements are compared (for Lookup purposes).
Definition TimedCache.h:229
TIMESTAMP_DIFFERENCE_TYPE TimeStampDifferenceType
Typically Time::DurationSeconds.
Definition TimedCache.h:161
TIMESTAMP_TYPE TimeStampType
Typically Time::TimePointSeconds.
Definition TimedCache.h:156
InternallySynchronizedTraits same as argument traits, but resetting the kInternallySynchronized to eI...
Definition TimedCache.h:284
add the argument bool PER_CACHE_MAX_AGE as the kPerCacheMaxAge property.
Definition TimedCache.h:295
take argument TRAITS, and set to track-expires-at mode.
Definition TimedCache.h:316
add the argument bool WRITE_LOCK_DURING_CACHE_FILL as the kHoldWriteLockDuringCacheFill property.
Definition TimedCache.h:305