Compare commits

...

20 Commits

Author SHA1 Message Date
JCW
b7a066caa1 Updated the approach after we introduced the use of canonlalize in DatabaseNodeImp.cpp 2026-06-06 18:13:47 +01:00
JCW
79d19f392c Merge remote-tracking branch 'origin/develop' into a1q123456/remove-const-cast-from-tagged-cache 2026-06-06 12:11:43 +01:00
JCW
b3e1937244 Address PR comments 2026-04-01 21:07:28 +01:00
Jingchen
e7f3241af3 Update src/test/basics/TaggedCache_test.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-01 21:05:57 +01:00
JCW
9c23371c46 Address PR comments 2026-04-01 21:04:20 +01:00
Jingchen
f2edebd919 Merge branch 'develop' into a1q123456/remove-const-cast-from-tagged-cache 2026-04-01 16:20:45 +01:00
JCW
a7ee324153 Split the function into 2 2026-04-01 16:19:53 +01:00
JCW
39c66b4da0 Simplify the code and address PR comments
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-03-18 22:23:53 +00:00
JCW
0bcf43a26e Simplify the code and address PR comments
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-03-18 22:06:17 +00:00
JCW
9b42bd288a Merge remote-tracking branch 'origin/develop' into a1q123456/remove-const-cast-from-tagged-cache
Signed-off-by: JCW <a1q123456@users.noreply.github.com>

# Conflicts:
#	include/xrpl/basics/TaggedCache.h
#	include/xrpl/basics/TaggedCache.ipp
#	src/test/basics/TaggedCache_test.cpp
2026-03-18 21:47:22 +00:00
JCW
95969cb95e Remove unrelated optimisation to make the PR easier to review
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2026-03-18 21:45:22 +00:00
JCW
bf4dc342c6 Fix formatting
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-10-29 14:36:18 +00:00
JCW
a71cd5d271 Address PR comments
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-10-28 11:31:45 +00:00
JCW
45baf7339c Merge remote-tracking branch 'origin/develop' into a1q123456/remove-const-cast-from-tagged-cache 2025-10-28 11:26:58 +00:00
JCW
2bc2930a28 Fix errors
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-09-18 20:16:36 +01:00
JCW
e837171f7c Fix formatting
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-09-18 20:16:36 +01:00
JCW
6f05bd035c Add test case and improve test coverage
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-09-18 20:16:36 +01:00
JCW
b98b42bbec Fix comment and fix formatting
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-09-18 20:16:36 +01:00
JCW
6e6ea4311b Add unittest
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-09-18 20:16:36 +01:00
JCW
f2271305e5 Fix attempt 2025-09-18 20:16:34 +01:00
3 changed files with 192 additions and 16 deletions

View File

@@ -17,6 +17,23 @@
namespace xrpl {
namespace detail {
// Replace-policy tags used by TaggedCache::canonicalizeReplaceCache /
// canonicalizeReplaceClient when calling canonicalize. With ReplaceCached the
// cached value is always replaced with `data`, so `data` is never written back
// and may be const. With ReplaceClient the cached value is kept and written
// back into `data` (the client's pointer), which must therefore be writable.
struct ReplaceCached
{
};
struct ReplaceClient
{
};
} // namespace detail
/** Map/cache combination.
This class implements a cache and a map. The cache keeps objects alive
in the map. The map allows multiple code paths that reference objects
@@ -96,6 +113,16 @@ public:
bool
del(key_type const& key, bool valid);
private:
// Selects the `data` parameter type of canonicalize from the replace
// policy: const for detail::ReplaceCached (never written back), otherwise
// writable.
template <typename R>
using CanonicalizeClientPointerType = std::conditional_t<
std::is_same_v<detail::ReplaceCached, R>,
SharedPointerType const&,
SharedPointerType&>;
public:
/** Replace aliased objects with originals.
@@ -104,15 +131,23 @@ public:
This routine eliminates the duplicate and performs a replacement
on the callers shared pointer if needed.
`replacePolicy` is a callable taking the existing strong pointer and
returning whether to replace the cached value with `data` (true) or to
keep the cached value and write it back into `data` (false). Because the
write-back case mutates `data`, `data` must be writable.
(canonicalizeReplaceCache / canonicalizeReplaceClient call this with
internal replace-policy tags in place of a callable.)
@param key The key corresponding to the object
@param data A shared pointer to the data corresponding to the object.
@param replace Function that decides if cache should be replaced
@param replacePolicy A callable (existing strong pointer -> bool).
@return `true` If the key already existed.
*/
template <class R>
bool
canonicalize(key_type const& key, SharedPointerType& data, R&& replaceCallback);
canonicalize(key_type const& key, CanonicalizeClientPointerType<R> data, R&& replacePolicy);
bool
canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data);

View File

@@ -303,10 +303,26 @@ template <
template <class R>
inline bool
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
canonicalize(key_type const& key, SharedPointerType& data, R&& replaceCallback)
canonicalize(
key_type const& key,
CanonicalizeClientPointerType<R> data,
[[maybe_unused]] R&& replacePolicy)
{
// Return canonical value, store if needed, refresh in cache
// Return values: true=we had the data already
// `replacePolicy` is one of:
// - detail::ReplaceCached: always replace the cached value with `data`;
// `data` is never written back and may be const.
// - detail::ReplaceClient: keep the cached value and write it back into
// `data` (the client's pointer), which must therefore be writable.
// - a callable(strong pointer) -> bool: decide at run time; `data` must
// be writable.
// For the latter two the write-back below requires a mutable `data`, so
// passing a const argument is a compile error.
using C = std::remove_cvref_t<R>;
constexpr bool replaceCached = std::is_same_v<C, detail::ReplaceCached>;
std::scoped_lock const lock(mutex_);
auto cit = cache_.find(key);
@@ -324,27 +340,28 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
Entry& entry = cit->second;
entry.touch(clock_.now());
auto shouldReplace = [&] {
if constexpr (std::is_invocable_r_v<bool, R>)
auto shouldReplaceCached = [&] {
if constexpr (replaceCached)
{
// The reason for this extra complexity is for intrusive
// strong/weak combo getting a strong is relatively expensive
// and not needed for many cases.
return replaceCallback();
return true;
}
else if constexpr (std::is_same_v<C, detail::ReplaceClient>)
{
return false;
}
else
{
return replaceCallback(entry.ptr.getStrong());
return replacePolicy(entry.ptr.getStrong());
}
};
if (entry.isCached())
{
if (shouldReplace())
if (shouldReplaceCached())
{
entry.ptr = data;
}
else
else if constexpr (!replaceCached)
{
data = entry.ptr.getStrong();
}
@@ -356,11 +373,11 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
if (cachedData)
{
if (shouldReplace())
if (shouldReplaceCached())
{
entry.ptr = data;
}
else
else if constexpr (!replaceCached)
{
entry.ptr.convertToStrong();
data = cachedData;
@@ -389,7 +406,7 @@ inline bool
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data)
{
return canonicalize(key, const_cast<SharedPointerType&>(data), []() { return true; });
return canonicalize(key, data, detail::ReplaceCached{});
}
template <
@@ -405,7 +422,7 @@ inline bool
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
canonicalizeReplaceClient(key_type const& key, SharedPointerType& data)
{
return canonicalize(key, data, []() { return false; });
return canonicalize(key, data, detail::ReplaceClient{});
}
template <

View File

@@ -8,6 +8,7 @@
#include <xrpl/protocol/Protocol.h>
#include <memory>
#include <utility>
namespace xrpl {
@@ -133,6 +134,129 @@ public:
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.getTrackSize() == 0);
}
{
BEAST_EXPECT(!c.insert(5, "five"));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.size() == 1);
{
auto const p1 = c.fetch(5);
BEAST_EXPECT(p1 != nullptr);
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.size() == 1);
// Advance the clock a lot
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.size() == 1);
auto p2 = std::make_shared<std::string>("five_2");
BEAST_EXPECT(c.canonicalizeReplaceCache(5, p2));
BEAST_EXPECT(c.getCacheSize() == 1);
BEAST_EXPECT(c.size() == 1);
// Make sure the caller's original pointer is unchanged
BEAST_EXPECT(p1.get() != p2.get());
BEAST_EXPECT(*p2 == "five_2");
auto const p3 = c.fetch(5);
BEAST_EXPECT(p3 != nullptr);
BEAST_EXPECT(p3.get() == p2.get());
BEAST_EXPECT(p3.get() != p1.get());
}
++clock;
c.sweep();
BEAST_EXPECT(c.getCacheSize() == 0);
BEAST_EXPECT(c.size() == 0);
}
{
testcase("intrptr");
struct MyRefCountObject : IntrusiveRefCounts
{
std::string data;
// Needed to support weak intrusive pointers
virtual void
partialDestructor() {};
MyRefCountObject() = default;
explicit MyRefCountObject(std::string data) : data(std::move(data))
{
}
explicit MyRefCountObject(char const* data) : data(data)
{
}
MyRefCountObject&
operator=(MyRefCountObject const& other)
{
data = other.data;
return *this;
}
bool
operator==(MyRefCountObject const& other) const
{
return data == other.data;
}
bool
operator==(std::string const& other) const
{
return data == data;
}
};
using IntrPtrCache = TaggedCache<
Key,
MyRefCountObject,
/*IsKeyCache*/ false,
intr_ptr::SharedWeakUnionPtr<MyRefCountObject>,
intr_ptr::SharedPtr<MyRefCountObject>>;
IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal);
intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared<MyRefCountObject>("one"));
BEAST_EXPECT(intrPtrCache.getCacheSize() == 1);
BEAST_EXPECT(intrPtrCache.size() == 1);
{
{
intrPtrCache.canonicalizeReplaceCache(
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced"));
auto p = intrPtrCache.fetch(1);
BEAST_EXPECT(*p == "one_replaced");
// Advance the clock a lot
++clock;
intrPtrCache.sweep();
BEAST_EXPECT(intrPtrCache.getCacheSize() == 0);
BEAST_EXPECT(intrPtrCache.size() == 1);
intrPtrCache.canonicalizeReplaceCache(
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_2"));
auto p2 = intrPtrCache.fetch(1);
BEAST_EXPECT(*p2 == "one_replaced_2");
intrPtrCache.del(1, true);
}
intrPtrCache.canonicalizeReplaceCache(
1, intr_ptr::makeShared<MyRefCountObject>("one_replaced_3"));
auto p3 = intrPtrCache.fetch(1);
BEAST_EXPECT(*p3 == "one_replaced_3");
}
++clock;
intrPtrCache.sweep();
BEAST_EXPECT(intrPtrCache.getCacheSize() == 0);
BEAST_EXPECT(intrPtrCache.size() == 0);
}
}
};