Files
rippled/include/xrpl/shamap/FullBelowCache.h
Pratik Mankawde 70766746f3 feat(basics): expose the peak TaggedCache lock hold and warn on a one-second hold
Preparing to point the finger at TaggedCache's mutex when the whole process
freezes at rotation time. Every `sweep()` and `getKeys()` calls `noteLockHold`
after releasing the mutex; `takeLockHoldPeak()` returns the longest hold since
the last call and resets. A one-second hold logs a warn line naming the op
and the entry count — the same one-second bar `LoadMonitor::addLoadSample`
uses to flag a job.

`lockHoldPeakNs_` is a mutable atomic so `getKeys() const` can note its own
hold; `FullBelowCache` forwards `takeLockHoldPeak()` so the metrics layer can
read either cache the same way. Nothing here depends on telemetry — this file
lives in `xrpl/basics` and must not.

Test pins the behaviour end to end: neither getKeys() nor sweep() records
anything on an empty cache; both push the peak above zero once the cache has
200,000 entries; and takeLockHoldPeak() is destructive.
2026-09-14 20:37:09 +01:00

155 lines
3.3 KiB
C++

#pragma once
#include <xrpl/basics/KeyCache.h>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/insight/Collector.h>
#include <xrpl/beast/insight/NullCollector.h>
#include <xrpl/beast/utility/Journal.h>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <string>
namespace xrpl {
namespace detail {
/**
* Remembers which tree keys have all descendants resident.
* This optimizes the process of acquiring a complete tree.
*/
class BasicFullBelowCache
{
private:
using CacheType = KeyCache;
public:
static constexpr auto kDefaultCacheTargetSize = 0;
using key_type = uint256;
using clock_type = CacheType::clock_type;
/**
* Construct the cache.
*
* @param name A label for diagnostics and stats reporting.
* @param collector The collector to use for reporting stats.
* @param targetSize The cache target size.
* @param targetExpirationSeconds The expiration time for items.
*/
BasicFullBelowCache(
std::string const& name,
clock_type& clock,
beast::Journal j,
beast::insight::Collector::ptr const& collector = beast::insight::NullCollector::make(),
std::size_t targetSize = kDefaultCacheTargetSize,
std::chrono::seconds expiration = std::chrono::minutes{2})
: cache_(name, targetSize, expiration, clock, j, collector), gen_(1)
{
}
/**
* Return the clock associated with the cache.
*/
clock_type&
clock()
{
return cache_.clock();
}
/**
* Return the number of elements in the cache.
* Thread safety:
* Safe to call from any thread.
*/
std::size_t
size() const
{
return cache_.size();
}
/**
* Remove expired cache items.
* Thread safety:
* Safe to call from any thread.
*/
void
sweep()
{
cache_.sweep();
}
/**
* See TaggedCache::takeLockHoldPeak(). Longest mutex hold since the last
* call, then reset. Read once per metrics collection tick.
*/
[[nodiscard]] std::chrono::nanoseconds
takeLockHoldPeak() noexcept
{
return cache_.takeLockHoldPeak();
}
/**
* Refresh the last access time of an item, if it exists.
* Thread safety:
* Safe to call from any thread.
* @param key The key to refresh.
* @return `true` If the key exists.
*/
bool
touchIfExists(key_type const& key)
{
return cache_.touchIfExists(key);
}
/**
* Insert a key into the cache.
* If the key already exists, the last access time will still
* be refreshed.
* Thread safety:
* Safe to call from any thread.
* @param key The key to insert.
*/
void
insert(key_type const& key)
{
cache_.insert(key);
}
/**
* generation determines whether cached entry is valid
*/
std::uint32_t
getGeneration() const
{
return gen_;
}
void
clear()
{
cache_.clear();
++gen_;
}
void
reset()
{
cache_.clear();
gen_ = 1;
}
private:
CacheType cache_;
std::atomic<std::uint32_t> gen_;
};
} // namespace detail
using FullBelowCache = detail::BasicFullBelowCache;
} // namespace xrpl