Compare commits

...

12 Commits

Author SHA1 Message Date
Ed Hennis
5d2bc88a2e Document the reasons for the post-allocation assert
- Hopefully the AI bots will stop telling me to change it now.
2026-07-01 19:05:01 -04:00
Ed Hennis
9c03931190 Merge branch 'develop' into ximinez/fix-getkeys 2026-07-01 16:03:04 -04:00
Ed Hennis
4f0738fff3 Merge branch 'develop' into ximinez/fix-getkeys 2026-06-30 16:27:25 -04:00
Ed Hennis
1a3d460046 Add missed header 2026-06-30 16:26:43 -04:00
Ed Hennis
c2e54d12e9 Use the right type in include/xrpl/basics/TaggedCache.ipp
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-06-30 16:25:18 -04:00
Ed Hennis
0ded97ba5b Make the getKeys() allocation more robust
- Make it very unlikely that the allocation loop will ever need to run
  more than once. Recover gracefully if it does.
- Prevent livelock by limiting the total possible number of iterations.
  - If this ever happens, throw our metaphorical hands in the air, and
    allocate under lock.
- Pad the size before allocating to give the cache a little room to grow
  while not holding the lock.
  - Pad by less on each iteration.
- Assert that no more than two iterations occur. Even two is probably
  overkill, but this allows for rare edge case scenarios. e.g. The
  cache is very small, the allocation takes a long time (which is
  contradictory) and a handful of items get added, overcoming the
  padding.
2026-06-30 16:13:11 -04:00
Ed Hennis
0e8714af73 Merge branch 'develop' into ximinez/fix-getkeys 2026-06-25 11:43:38 -04:00
Ed Hennis
d62ad9a8e7 Merge branch 'develop' into ximinez/fix-getkeys 2026-06-23 11:10:09 -04:00
Ed Hennis
d7e7baa675 Merge branch 'develop' into ximinez/fix-getkeys 2026-06-22 16:42:07 -04:00
Ed Hennis
054284701e Apply suggestion from @xrplf-ai-reviewer[bot]
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-06-17 15:20:48 -04:00
Ed Hennis
eb4681da51 Merge branch 'develop' into ximinez/fix-getkeys 2026-06-17 15:19:55 -04:00
Ed Hennis
9b3dd7002d fix: Allocate TaggedCache::getKeys() memory outside of lock
- Uses a loop in case the size grows while the lock is free. Guarantees
  the result vector will not need to allocate under lock.
2026-06-17 13:06:40 -04:00

View File

@@ -2,6 +2,9 @@
#include <xrpl/basics/IntrusivePointer.ipp>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/scope.h>
#include <algorithm>
namespace xrpl {
@@ -595,8 +598,39 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
std::vector<key_type> v;
{
std::scoped_lock const lock(mutex_);
v.reserve(cache_.size());
// Keep track of how many iterations are needed. Exit the loop if the number of retries gets
// absurd. (Note that if this somehow ever happens, one more allocation will be done under
// lock, which is undesirable, but really should be almost impossible.)
std::size_t allocationIterations = 0;
std::unique_lock lock(mutex_);
for (auto size = cache_.size(); v.capacity() < size && allocationIterations < 20;
size = cache_.size())
{
ScopeUnlock const unlock(lock);
// Allocate the current size plus a little extra, in case the cache grows while
// allocating. Each time another allocation is needed, the extra also gets bigger until
// it ultimately doubles the size + 1.
size += (size >> (4 - std::min(allocationIterations, std::size_t{4}))) + 1;
v.reserve(size);
++allocationIterations;
}
// In a normal operating environment, because of the padding added to size before
// allocating, even 2 iterations is going to be very rare. If 3 or more are ever needed,
// that's unusual enough that I want to know about it. Don't ask me to change it without
// empirical data. - Ed H.
XRPL_ASSERT(
allocationIterations < 3,
"xrpl::TaggedCache::getKeys(): limited allocation iterations");
if (v.capacity() < cache_.size())
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::TaggedCache::getKeys(): failed to allocate sufficient capacity");
v.reserve(cache_.size());
// LCOV_EXCL_STOP
}
XRPL_ASSERT(lock.owns_lock(), "xrpl::TaggedCache::getKeys(): owns lock");
XRPL_ASSERT(
v.capacity() >= cache_.size(), "xrpl::TaggedCache::getKeys(): sufficient capacity");
for (auto const& _ : cache_)
v.push_back(_.first);
}