rippled
TaggedCache.h
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #ifndef RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED
21 #define RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED
22 
23 #include <ripple/basics/Log.h>
24 #include <ripple/basics/UnorderedContainers.h>
25 #include <ripple/basics/hardened_hash.h>
26 #include <ripple/beast/clock/abstract_clock.h>
27 #include <ripple/beast/insight/Insight.h>
28 #include <functional>
29 #include <mutex>
30 #include <vector>
31 
32 namespace ripple {
33 
46 template <
47  class Key,
48  class T,
49  class Hash = hardened_hash<>,
50  class KeyEqual = std::equal_to<Key>,
51  class Mutex = std::recursive_mutex>
53 {
54 public:
55  using mutex_type = Mutex;
56  using key_type = Key;
57  using mapped_type = T;
59 
60 public:
62  std::string const& name,
63  int size,
64  clock_type::duration expiration,
66  beast::Journal journal,
67  beast::insight::Collector::ptr const& collector =
69  : m_journal(journal)
70  , m_clock(clock)
71  , m_stats(
72  name,
73  std::bind(&TaggedCache::collect_metrics, this),
74  collector)
75  , m_name(name)
76  , m_target_size(size)
77  , m_target_age(expiration)
78  , m_cache_count(0)
79  , m_hits(0)
80  , m_misses(0)
81  {
82  }
83 
84 public:
86  clock_type&
88  {
89  return m_clock;
90  }
91 
92  int
93  getTargetSize() const
94  {
96  return m_target_size;
97  }
98 
99  void
101  {
102  std::lock_guard lock(m_mutex);
103  m_target_size = s;
104 
105  if (s > 0)
106  m_cache.rehash(static_cast<std::size_t>(
107  (s + (s >> 2)) / m_cache.max_load_factor() + 1));
108 
109  JLOG(m_journal.debug()) << m_name << " target size set to " << s;
110  }
111 
113  getTargetAge() const
114  {
115  std::lock_guard lock(m_mutex);
116  return m_target_age;
117  }
118 
119  void
121  {
122  std::lock_guard lock(m_mutex);
123  m_target_age = s;
124  JLOG(m_journal.debug())
125  << m_name << " target age set to " << m_target_age.count();
126  }
127 
128  int
129  getCacheSize() const
130  {
131  std::lock_guard lock(m_mutex);
132  return m_cache_count;
133  }
134 
135  int
136  getTrackSize() const
137  {
138  std::lock_guard lock(m_mutex);
139  return m_cache.size();
140  }
141 
142  float
144  {
145  std::lock_guard lock(m_mutex);
146  auto const total = static_cast<float>(m_hits + m_misses);
147  return m_hits * (100.0f / std::max(1.0f, total));
148  }
149 
150  void
152  {
153  std::lock_guard lock(m_mutex);
154  m_cache.clear();
155  m_cache_count = 0;
156  }
157 
158  void
160  {
161  std::lock_guard lock(m_mutex);
162  m_cache.clear();
163  m_cache_count = 0;
164  m_hits = 0;
165  m_misses = 0;
166  }
167 
168  void
170  {
171  int cacheRemovals = 0;
172  int mapRemovals = 0;
173  int cc = 0;
174 
175  // Keep references to all the stuff we sweep
176  // so that we can destroy them outside the lock.
177  //
179 
180  {
181  clock_type::time_point const now(m_clock.now());
182  clock_type::time_point when_expire;
183 
184  std::lock_guard lock(m_mutex);
185 
186  if (m_target_size == 0 ||
187  (static_cast<int>(m_cache.size()) <= m_target_size))
188  {
189  when_expire = now - m_target_age;
190  }
191  else
192  {
193  when_expire =
195 
196  clock_type::duration const minimumAge(std::chrono::seconds(1));
197  if (when_expire > (now - minimumAge))
198  when_expire = now - minimumAge;
199 
200  JLOG(m_journal.trace())
201  << m_name << " is growing fast " << m_cache.size() << " of "
202  << m_target_size << " aging at "
203  << (now - when_expire).count() << " of "
204  << m_target_age.count();
205  }
206 
207  stuffToSweep.reserve(m_cache.size());
208 
209  auto cit = m_cache.begin();
210 
211  while (cit != m_cache.end())
212  {
213  if (cit->second.isWeak())
214  {
215  // weak
216  if (cit->second.isExpired())
217  {
218  ++mapRemovals;
219  cit = m_cache.erase(cit);
220  }
221  else
222  {
223  ++cit;
224  }
225  }
226  else if (cit->second.last_access <= when_expire)
227  {
228  // strong, expired
229  --m_cache_count;
230  ++cacheRemovals;
231  if (cit->second.ptr.unique())
232  {
233  stuffToSweep.push_back(cit->second.ptr);
234  ++mapRemovals;
235  cit = m_cache.erase(cit);
236  }
237  else
238  {
239  // remains weakly cached
240  cit->second.ptr.reset();
241  ++cit;
242  }
243  }
244  else
245  {
246  // strong, not expired
247  ++cc;
248  ++cit;
249  }
250  }
251  }
252 
253  if (mapRemovals || cacheRemovals)
254  {
255  JLOG(m_journal.trace())
256  << m_name << ": cache = " << m_cache.size() << "-"
257  << cacheRemovals << ", map-=" << mapRemovals;
258  }
259 
260  // At this point stuffToSweep will go out of scope outside the lock
261  // and decrement the reference count on each strong pointer.
262  }
263 
264  bool
265  del(const key_type& key, bool valid)
266  {
267  // Remove from cache, if !valid, remove from map too. Returns true if
268  // removed from cache
269  std::lock_guard lock(m_mutex);
270 
271  auto cit = m_cache.find(key);
272 
273  if (cit == m_cache.end())
274  return false;
275 
276  Entry& entry = cit->second;
277 
278  bool ret = false;
279 
280  if (entry.isCached())
281  {
282  --m_cache_count;
283  entry.ptr.reset();
284  ret = true;
285  }
286 
287  if (!valid || entry.isExpired())
288  m_cache.erase(cit);
289 
290  return ret;
291  }
292 
306 private:
307  template <bool replace>
308  bool
310  const key_type& key,
312  replace,
313  std::shared_ptr<T> const,
314  std::shared_ptr<T>>& data)
315  {
316  // Return canonical value, store if needed, refresh in cache
317  // Return values: true=we had the data already
318  std::lock_guard lock(m_mutex);
319 
320  auto cit = m_cache.find(key);
321 
322  if (cit == m_cache.end())
323  {
325  std::piecewise_construct,
328  ++m_cache_count;
329  return false;
330  }
331 
332  Entry& entry = cit->second;
333  entry.touch(m_clock.now());
334 
335  if (entry.isCached())
336  {
337  if constexpr (replace)
338  {
339  entry.ptr = data;
340  entry.weak_ptr = data;
341  }
342  else
343  {
344  data = entry.ptr;
345  }
346 
347  return true;
348  }
349 
350  auto cachedData = entry.lock();
351 
352  if (cachedData)
353  {
354  if constexpr (replace)
355  {
356  entry.ptr = data;
357  entry.weak_ptr = data;
358  }
359  else
360  {
361  entry.ptr = cachedData;
362  data = cachedData;
363  }
364 
365  ++m_cache_count;
366  return true;
367  }
368 
369  entry.ptr = data;
370  entry.weak_ptr = data;
371  ++m_cache_count;
372 
373  return false;
374  }
375 
376 public:
377  bool
379  const key_type& key,
380  std::shared_ptr<T> const& data)
381  {
382  return canonicalize<true>(key, data);
383  }
384 
385  bool
387  {
388  return canonicalize<false>(key, data);
389  }
390 
392  fetch(const key_type& key)
393  {
394  // fetch us a shared pointer to the stored data object
395  std::lock_guard lock(m_mutex);
396 
397  auto cit = m_cache.find(key);
398 
399  if (cit == m_cache.end())
400  {
401  ++m_misses;
402  return {};
403  }
404 
405  Entry& entry = cit->second;
406  entry.touch(m_clock.now());
407 
408  if (entry.isCached())
409  {
410  ++m_hits;
411  return entry.ptr;
412  }
413 
414  entry.ptr = entry.lock();
415 
416  if (entry.isCached())
417  {
418  // independent of cache size, so not counted as a hit
419  ++m_cache_count;
420  return entry.ptr;
421  }
422 
423  m_cache.erase(cit);
424  ++m_misses;
425  return {};
426  }
427 
432  bool
433  insert(key_type const& key, T const& value)
434  {
435  auto p = std::make_shared<T>(std::cref(value));
436  return canonicalize_replace_client(key, p);
437  }
438 
439  // VFALCO NOTE It looks like this returns a copy of the data in
440  // the output parameter 'data'. This could be expensive.
441  // Perhaps it should work like standard containers, which
442  // simply return an iterator.
443  //
444  bool
445  retrieve(const key_type& key, T& data)
446  {
447  // retrieve the value of the stored data
448  auto entry = fetch(key);
449 
450  if (!entry)
451  return false;
452 
453  data = *entry;
454  return true;
455  }
456 
462  bool
464  {
465  bool found = false;
466 
467  // If present, make current in cache
468  std::lock_guard lock(m_mutex);
469 
470  if (auto cit = m_cache.find(key); cit != m_cache.end())
471  {
472  Entry& entry = cit->second;
473 
474  if (!entry.isCached())
475  {
476  // Convert weak to strong.
477  entry.ptr = entry.lock();
478 
479  if (entry.isCached())
480  {
481  // We just put the object back in cache
482  ++m_cache_count;
483  entry.touch(m_clock.now());
484  found = true;
485  }
486  else
487  {
488  // Couldn't get strong pointer,
489  // object fell out of the cache so remove the entry.
490  m_cache.erase(cit);
491  }
492  }
493  else
494  {
495  // It's cached so update the timer
496  entry.touch(m_clock.now());
497  found = true;
498  }
499  }
500 
501  return found;
502  }
503 
504  mutex_type&
506  {
507  return m_mutex;
508  }
509 
511  getKeys() const
512  {
514 
515  {
516  std::lock_guard lock(m_mutex);
517  v.reserve(m_cache.size());
518  for (auto const& _ : m_cache)
519  v.push_back(_.first);
520  }
521 
522  return v;
523  }
524 
525 private:
526  void
528  {
530 
531  {
533  {
534  std::lock_guard lock(m_mutex);
535  auto const total(m_hits + m_misses);
536  if (total != 0)
537  hit_rate = (m_hits * 100) / total;
538  }
539  m_stats.hit_rate.set(hit_rate);
540  }
541  }
542 
543 private:
544  struct Stats
545  {
546  template <class Handler>
548  std::string const& prefix,
549  Handler const& handler,
550  beast::insight::Collector::ptr const& collector)
551  : hook(collector->make_hook(handler))
552  , size(collector->make_gauge(prefix, "size"))
553  , hit_rate(collector->make_gauge(prefix, "hit_rate"))
554  {
555  }
556 
560  };
561 
562  class Entry
563  {
564  public:
568 
570  clock_type::time_point const& last_access_,
571  std::shared_ptr<mapped_type> const& ptr_)
572  : ptr(ptr_), weak_ptr(ptr_), last_access(last_access_)
573  {
574  }
575 
576  bool
577  isWeak() const
578  {
579  return ptr == nullptr;
580  }
581  bool
582  isCached() const
583  {
584  return ptr != nullptr;
585  }
586  bool
587  isExpired() const
588  {
589  return weak_ptr.expired();
590  }
593  {
594  return weak_ptr.lock();
595  }
596  void
598  {
599  last_access = now;
600  }
601  };
602 
604 
607  Stats m_stats;
608 
610 
611  // Used for logging
613 
614  // Desired number of cache entries (0 = ignore)
616 
617  // Desired maximum cache age
619 
620  // Number of items cached
622  cache_type m_cache; // Hold strong reference to recent objects
625 };
626 
627 } // namespace ripple
628 
629 #endif
ripple::TaggedCache::Stats::hook
beast::insight::Hook hook
Definition: TaggedCache.h:557
ripple::TaggedCache::m_hits
std::uint64_t m_hits
Definition: TaggedCache.h:623
ripple::TaggedCache::del
bool del(const key_type &key, bool valid)
Definition: TaggedCache.h:265
std::weak_ptr::lock
T lock(T... args)
ripple::AcceptedLedger
A ledger that has become irrevocable.
Definition: AcceptedLedger.h:44
ripple::TaggedCache::m_journal
beast::Journal m_journal
Definition: TaggedCache.h:605
std::string
STL class.
std::shared_ptr< Collector >
ripple::TaggedCache
Map/cache combination.
Definition: TaggedCache.h:52
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::TaggedCache::clock_type
beast::abstract_clock< std::chrono::steady_clock > clock_type
Definition: TaggedCache.h:58
ripple::TaggedCache::sweep
void sweep()
Definition: TaggedCache.h:169
functional
std::vector::reserve
T reserve(T... args)
vector
std::unordered_map::find
T find(T... args)
std::unordered_map::size
T size(T... args)
ripple::TaggedCache::Entry::lock
std::shared_ptr< mapped_type > lock()
Definition: TaggedCache.h:592
std::chrono::seconds
ripple::TaggedCache::fetch
std::shared_ptr< T > fetch(const key_type &key)
Definition: TaggedCache.h:392
std::unordered_map::emplace
T emplace(T... args)
std::recursive_mutex
STL class.
std::lock_guard
STL class.
ripple::TaggedCache::Entry::ptr
std::shared_ptr< mapped_type > ptr
Definition: TaggedCache.h:565
ripple::TaggedCache::canonicalize_replace_cache
bool canonicalize_replace_cache(const key_type &key, std::shared_ptr< T > const &data)
Definition: TaggedCache.h:378
ripple::TaggedCache::m_clock
clock_type & m_clock
Definition: TaggedCache.h:606
std::unordered_map::max_load_factor
T max_load_factor(T... args)
ripple::TaggedCache::clear
void clear()
Definition: TaggedCache.h:151
std::unordered_map::clear
T clear(T... args)
ripple::TaggedCache::getHitRate
float getHitRate()
Definition: TaggedCache.h:143
std::weak_ptr::expired
T expired(T... args)
beast::abstract_clock::now
virtual time_point now() const =0
Returns the current time.
std::vector::push_back
T push_back(T... args)
ripple::base_uint
Definition: base_uint.h:63
ripple::TaggedCache::m_cache
cache_type m_cache
Definition: TaggedCache.h:622
ripple::TaggedCache::getCacheSize
int getCacheSize() const
Definition: TaggedCache.h:129
ripple::TaggedCache::getTargetSize
int getTargetSize() const
Definition: TaggedCache.h:93
ripple::TaggedCache::getKeys
std::vector< key_type > getKeys() const
Definition: TaggedCache.h:511
ripple::TaggedCache::peekMutex
mutex_type & peekMutex()
Definition: TaggedCache.h:505
ripple::TaggedCache::reset
void reset()
Definition: TaggedCache.h:159
ripple::TaggedCache::m_mutex
mutex_type m_mutex
Definition: TaggedCache.h:609
ripple::TaggedCache::Entry::Entry
Entry(clock_type::time_point const &last_access_, std::shared_ptr< mapped_type > const &ptr_)
Definition: TaggedCache.h:569
std::unordered_map::erase
T erase(T... args)
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint64_t
ripple::TaggedCache::Stats
Definition: TaggedCache.h:544
std::forward_as_tuple
T forward_as_tuple(T... args)
ripple::TaggedCache::getTrackSize
int getTrackSize() const
Definition: TaggedCache.h:136
ripple::TaggedCache::m_cache_count
int m_cache_count
Definition: TaggedCache.h:621
ripple::TaggedCache::getTargetAge
clock_type::duration getTargetAge() const
Definition: TaggedCache.h:113
beast::abstract_clock< std::chrono::steady_clock >
ripple::TaggedCache::m_target_age
clock_type::duration m_target_age
Definition: TaggedCache.h:618
beast::insight::Gauge
A metric for measuring an integral value.
Definition: Gauge.h:39
std::weak_ptr< mapped_type >
ripple::TaggedCache::Stats::size
beast::insight::Gauge size
Definition: TaggedCache.h:558
std::equal_to
ripple::TaggedCache::refreshIfPresent
bool refreshIfPresent(const key_type &key)
Refresh the expiration time on a key.
Definition: TaggedCache.h:463
ripple::TaggedCache::setTargetAge
void setTargetAge(clock_type::duration s)
Definition: TaggedCache.h:120
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::TaggedCache::Entry::touch
void touch(clock_type::time_point const &now)
Definition: TaggedCache.h:597
ripple::TaggedCache::Stats::Stats
Stats(std::string const &prefix, Handler const &handler, beast::insight::Collector::ptr const &collector)
Definition: TaggedCache.h:547
ripple::TaggedCache::setTargetSize
void setTargetSize(int s)
Definition: TaggedCache.h:100
ripple::TaggedCache::m_target_size
int m_target_size
Definition: TaggedCache.h:615
std::unordered_map::begin
T begin(T... args)
std
STL namespace.
ripple::TaggedCache::insert
bool insert(key_type const &key, T const &value)
Insert the element into the container.
Definition: TaggedCache.h:433
ripple::TaggedCache::m_stats
Stats m_stats
Definition: TaggedCache.h:607
ripple::TaggedCache::Entry::weak_ptr
std::weak_ptr< mapped_type > weak_ptr
Definition: TaggedCache.h:566
mutex
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::TaggedCache::Entry::isWeak
bool isWeak() const
Definition: TaggedCache.h:577
std::size_t
std::unordered_map::end
T end(T... args)
std::unordered_map::rehash
T rehash(T... args)
ripple::TaggedCache::canonicalize
bool canonicalize(const key_type &key, std::conditional_t< replace, std::shared_ptr< T > const, std::shared_ptr< T >> &data)
Replace aliased objects with originals.
Definition: TaggedCache.h:309
ripple::TaggedCache::m_misses
std::uint64_t m_misses
Definition: TaggedCache.h:624
std::conditional_t
ripple::TaggedCache::collect_metrics
void collect_metrics()
Definition: TaggedCache.h:527
std::max
T max(T... args)
ripple::TaggedCache::canonicalize_replace_client
bool canonicalize_replace_client(const key_type &key, std::shared_ptr< T > &data)
Definition: TaggedCache.h:386
ripple::TaggedCache::TaggedCache
TaggedCache(std::string const &name, int size, clock_type::duration expiration, clock_type &clock, beast::Journal journal, beast::insight::Collector::ptr const &collector=beast::insight::NullCollector::New())
Definition: TaggedCache.h:61
ripple::TaggedCache::clock
clock_type & clock()
Return the clock associated with the cache.
Definition: TaggedCache.h:87
ripple::TaggedCache::m_name
std::string m_name
Definition: TaggedCache.h:612
std::unordered_map
STL class.
beast::abstract_clock< std::chrono::steady_clock >::time_point
typename std::chrono::steady_clock ::time_point time_point
Definition: abstract_clock.h:63
ripple::TaggedCache::mutex_type
Mutex mutex_type
Definition: TaggedCache.h:55
beast::insight::Hook
A reference to a handler for performing polled collection.
Definition: Hook.h:31
ripple::TaggedCache::Entry::last_access
clock_type::time_point last_access
Definition: TaggedCache.h:567
beast::insight::NullCollector::New
static std::shared_ptr< Collector > New()
Definition: NullCollector.cpp:152
beast::abstract_clock< std::chrono::steady_clock >::duration
typename std::chrono::steady_clock ::duration duration
Definition: abstract_clock.h:62
ripple::TaggedCache::Stats::hit_rate
beast::insight::Gauge hit_rate
Definition: TaggedCache.h:559
std::cref
T cref(T... args)
beast::insight::Gauge::set
void set(value_type value) const
Set the value on the gauge.
Definition: Gauge.h:68
ripple::TaggedCache::Entry::isExpired
bool isExpired() const
Definition: TaggedCache.h:587
ripple::TaggedCache::Entry
Definition: TaggedCache.h:562
ripple::TaggedCache::retrieve
bool retrieve(const key_type &key, T &data)
Definition: TaggedCache.h:445
ripple::TaggedCache::Entry::isCached
bool isCached() const
Definition: TaggedCache.h:582