mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-10 05:38:10 +00:00
Five sites copied the top entry out and then popped it. `SHAMapTreeNodePtr` is refcounted, so each copy bumped the pointee's atomic strong count and the original's destructor then released it. `releaseNode()` moves the pointer out instead, a plain swap with no atomic at all. `dirtyUp` and `delItem` walk up to 64 levels per insert or delete on the ledger write path, so this removes up to 64 increments and 64 release sequences per call. The sites that also want the ID read `top().second` first, which costs the same either way, and the two that read without popping now bind a reference. `staticPointerCast` and `dynamicPointerCast` had only a `TT const&` overload, so no caller could move into them. Each gains an rvalue overload, tied to `SharedIntrusive<TT>&&` rather than a bare `TT&&` so it cannot bind to an lvalue in preference to the const-ref one, and the sites that own a discarded pointer now pass `std::move`. `SharedIntrusive`'s move constructors also become `noexcept`, so a `std::vector` of them relocates by moving; without that, `move_if_noexcept` copies every element, since the type is copy constructible. Three of the casts become static, and a fourth that already was gains the same live type test, so no traversal path is left paying for a `dynamic_cast`. `dirtyUp` and `delItem`'s loop rest on every remaining entry being inner, which holds but was only an `XRPL_ASSERT`, a no-op under `NDEBUG`, so both report `UNREACHABLE` and throw rather than writing through a misread node. `updateGiveItem` and `delItem`'s leaf cast need the test for a different reason: an absent tag leaves an inner node on top, which the public API permits, so they return false rather than aborting an instrumented build. A test pins that.
581 lines
16 KiB
C++
581 lines
16 KiB
C++
#pragma once
|
|
|
|
#include <concepts>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Tag to create an intrusive pointer from another intrusive pointer by using a
|
|
* static cast. This is useful to create an intrusive pointer to a derived
|
|
* class from an intrusive pointer to a base class.
|
|
*/
|
|
struct StaticCastTagSharedIntrusive
|
|
{
|
|
};
|
|
|
|
/**
|
|
* Tag to create an intrusive pointer from another intrusive pointer by using a
|
|
* dynamic cast. This is useful to create an intrusive pointer to a derived
|
|
* class from an intrusive pointer to a base class. If the cast fails an empty
|
|
* (null) intrusive pointer is created.
|
|
*/
|
|
struct DynamicCastTagSharedIntrusive
|
|
{
|
|
};
|
|
|
|
/**
|
|
* When creating or adopting a raw pointer, controls whether the strong count
|
|
* is incremented or not. Use this tag to increment the strong count.
|
|
*/
|
|
struct SharedIntrusiveAdoptIncrementStrongTag
|
|
{
|
|
};
|
|
|
|
/**
|
|
* When creating or adopting a raw pointer, controls whether the strong count
|
|
* is incremented or not. Use this tag to leave the strong count unchanged.
|
|
*/
|
|
struct SharedIntrusiveAdoptNoIncrementTag
|
|
{
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
|
|
template <class T>
|
|
concept CAdoptTag = std::is_same_v<T, SharedIntrusiveAdoptIncrementStrongTag> ||
|
|
std::is_same_v<T, SharedIntrusiveAdoptNoIncrementTag>;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* A shared intrusive pointer class that supports weak pointers.
|
|
*
|
|
* This is meant to be used for SHAMapInnerNodes, but may be useful for other
|
|
* cases. Since the reference counts are stored on the pointee, the pointee is
|
|
* not destroyed until both the strong _and_ weak pointer counts go to zero.
|
|
* When the strong pointer count goes to zero, the "partialDestructor" is
|
|
* called. This can be used to destroy as much of the object as possible while
|
|
* still retaining the reference counts. For example, for SHAMapInnerNodes the
|
|
* children may be reset in that function. Note that std::shared_pointer WILL
|
|
* run the destructor when the strong count reaches zero, but may not free the
|
|
* memory used by the object until the weak count reaches zero. In xrpld, we
|
|
* typically allocate shared pointers with the `make_shared` function. When
|
|
* that is used, the memory is not reclaimed until the weak count reaches zero.
|
|
*/
|
|
template <class T>
|
|
class SharedIntrusive
|
|
{
|
|
public:
|
|
SharedIntrusive() = default;
|
|
|
|
template <CAdoptTag TAdoptTag>
|
|
SharedIntrusive(T* p, TAdoptTag) noexcept;
|
|
|
|
SharedIntrusive(SharedIntrusive const& rhs);
|
|
|
|
template <class TT>
|
|
// TODO: convertible_to isn't quite right. That include a static castable.
|
|
// Find the right concept.
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedIntrusive(SharedIntrusive<TT> const& rhs);
|
|
|
|
// noexcept so that a std::vector of these relocates by moving. Without it, move_if_noexcept
|
|
// copies each element instead, since this type is also copy constructible, and every copy is an
|
|
// atomic increment on the pointee's refcount followed by a release on the original. The body is
|
|
// a std::exchange on a raw pointer, so it provably cannot throw.
|
|
SharedIntrusive(SharedIntrusive&& rhs) noexcept;
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
|
|
SharedIntrusive(SharedIntrusive<TT>&& rhs) noexcept;
|
|
|
|
SharedIntrusive&
|
|
operator=(SharedIntrusive const& rhs);
|
|
|
|
bool
|
|
operator==(std::nullptr_t) const;
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedIntrusive&
|
|
operator=(SharedIntrusive<TT> const& rhs);
|
|
|
|
SharedIntrusive&
|
|
operator=(SharedIntrusive&& rhs);
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedIntrusive&
|
|
operator=(
|
|
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
|
|
|
|
/**
|
|
* Adopt the raw pointer. The strong reference may or may not be
|
|
* incremented, depending on the TAdoptTag
|
|
*/
|
|
template <CAdoptTag TAdoptTag = SharedIntrusiveAdoptIncrementStrongTag>
|
|
void
|
|
adopt(T* p);
|
|
|
|
~SharedIntrusive();
|
|
|
|
/**
|
|
* Create a new SharedIntrusive by statically casting the pointer
|
|
* controlled by the rhs param.
|
|
*/
|
|
template <class TT>
|
|
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
|
|
|
|
/**
|
|
* Create a new SharedIntrusive by statically casting the pointer
|
|
* controlled by the rhs param.
|
|
*/
|
|
template <class TT>
|
|
SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
|
|
|
|
/**
|
|
* Create a new SharedIntrusive by dynamically casting the pointer
|
|
* controlled by the rhs param.
|
|
*/
|
|
template <class TT>
|
|
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT> const& rhs);
|
|
|
|
/**
|
|
* Create a new SharedIntrusive by dynamically casting the pointer
|
|
* controlled by the rhs param.
|
|
*/
|
|
template <class TT>
|
|
SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive<TT>&& rhs);
|
|
|
|
T&
|
|
operator*() const noexcept;
|
|
|
|
T*
|
|
operator->() const noexcept;
|
|
|
|
explicit
|
|
operator bool() const noexcept;
|
|
|
|
/**
|
|
* Set the pointer to null, decrement the strong count, and run the
|
|
* appropriate release action.
|
|
*/
|
|
void
|
|
reset();
|
|
|
|
/**
|
|
* Get the raw pointer
|
|
*/
|
|
[[nodiscard]] T*
|
|
get() const;
|
|
|
|
/**
|
|
* Return the strong count
|
|
*/
|
|
[[nodiscard]] std::size_t
|
|
useCount() const;
|
|
|
|
template <class TT, class... Args>
|
|
friend SharedIntrusive<TT>
|
|
makeSharedIntrusive(Args&&... args);
|
|
|
|
template <class TT>
|
|
friend class SharedIntrusive;
|
|
|
|
template <class TT>
|
|
friend class SharedWeakUnion;
|
|
|
|
template <class TT>
|
|
friend class WeakIntrusive;
|
|
|
|
private:
|
|
/**
|
|
* Return the raw pointer held by this object.
|
|
*/
|
|
[[nodiscard]] T*
|
|
unsafeGetRawPtr() const;
|
|
|
|
/**
|
|
* Exchange the current raw pointer held by this object with the given
|
|
* pointer. Decrement the strong count of the raw pointer previously held
|
|
* by this object and run the appropriate release action.
|
|
*/
|
|
void
|
|
unsafeReleaseAndStore(T* next);
|
|
|
|
/**
|
|
* Set the raw pointer directly. This is wrapped in a function so the class
|
|
* can support both atomic and non-atomic pointers in a future patch.
|
|
*/
|
|
void
|
|
unsafeSetRawPtr(T* p);
|
|
|
|
/**
|
|
* Exchange the raw pointer directly.
|
|
* This sets the raw pointer to the given value and returns the previous
|
|
* value. This is wrapped in a function so the class can support both
|
|
* atomic and non-atomic pointers in a future patch.
|
|
*/
|
|
T*
|
|
unsafeExchange(T* p);
|
|
|
|
/**
|
|
* pointer to the type with an intrusive count
|
|
*/
|
|
T* ptr_{nullptr};
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* A weak intrusive pointer class for the SharedIntrusive pointer class.
|
|
*
|
|
* Note that this weak pointer class asks differently from normal weak pointer
|
|
* classes. When the strong pointer count goes to zero, the "partialDestructor"
|
|
* is called. See the comment on SharedIntrusive for a fuller explanation.
|
|
*/
|
|
template <class T>
|
|
class WeakIntrusive
|
|
{
|
|
public:
|
|
WeakIntrusive() = default;
|
|
|
|
WeakIntrusive(WeakIntrusive const& rhs);
|
|
|
|
WeakIntrusive(WeakIntrusive&& rhs);
|
|
|
|
WeakIntrusive(SharedIntrusive<T> const& rhs);
|
|
|
|
// There is no move constructor from a strong intrusive ptr because
|
|
// moving would be move expensive than copying in this case (the strong
|
|
// ref would need to be decremented)
|
|
WeakIntrusive(SharedIntrusive<T> const&& rhs) = delete;
|
|
|
|
// Since there are no current use cases for copy assignment in
|
|
// WeakIntrusive, we delete this operator to simplify the implementation. If
|
|
// a need arises in the future, we can reintroduce it with proper
|
|
// consideration."
|
|
WeakIntrusive&
|
|
operator=(WeakIntrusive const&) = delete;
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
WeakIntrusive&
|
|
operator=(SharedIntrusive<TT> const& rhs);
|
|
|
|
/**
|
|
* Adopt the raw pointer and increment the weak count.
|
|
*/
|
|
void
|
|
adopt(T* ptr);
|
|
|
|
~WeakIntrusive();
|
|
|
|
/**
|
|
* Get a strong pointer from the weak pointer, if possible. This will
|
|
* only return a seated pointer if the strong count on the raw pointer
|
|
* is non-zero before locking.
|
|
*/
|
|
SharedIntrusive<T>
|
|
lock() const;
|
|
|
|
/**
|
|
* Return true if the strong count is zero.
|
|
*/
|
|
[[nodiscard]] bool
|
|
expired() const;
|
|
|
|
/**
|
|
* Set the pointer to null and decrement the weak count.
|
|
*
|
|
* Note: This may run the destructor if the strong count is zero.
|
|
*/
|
|
void
|
|
reset();
|
|
|
|
private:
|
|
T* ptr_ = nullptr;
|
|
|
|
/**
|
|
* Decrement the weak count. This does _not_ set the raw pointer to
|
|
* null.
|
|
*
|
|
* Note: This may run the destructor if the strong count is zero.
|
|
*/
|
|
void
|
|
unsafeReleaseNoStore();
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* A combination of a strong and a weak intrusive pointer stored in the
|
|
* space of a single pointer.
|
|
*
|
|
* This class is similar to a `std::variant<SharedIntrusive,WeakIntrusive>`
|
|
* with some optimizations. In particular, it uses a low-order bit to
|
|
* determine if the raw pointer represents a strong pointer or a weak
|
|
* pointer. It can also be quickly switched between its strong pointer and
|
|
* weak pointer representations. This class is useful for storing intrusive
|
|
* pointers in tagged caches.
|
|
*/
|
|
|
|
template <class T>
|
|
class SharedWeakUnion
|
|
{
|
|
// Tagged pointer. Low bit determines if this is a strong or a weak
|
|
// pointer. The low bit must be masked to zero when converting back to a
|
|
// pointer. If the low bit is '1', this is a weak pointer.
|
|
static_assert(alignof(T) >= 2, "Bad alignment: Combo pointer requires low bit to be zero");
|
|
|
|
public:
|
|
SharedWeakUnion() = default;
|
|
|
|
SharedWeakUnion(SharedWeakUnion const& rhs);
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedWeakUnion(SharedIntrusive<TT> const& rhs);
|
|
|
|
SharedWeakUnion(SharedWeakUnion&& rhs);
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedWeakUnion(
|
|
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
|
|
|
|
SharedWeakUnion&
|
|
operator=(SharedWeakUnion const& rhs);
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedWeakUnion&
|
|
operator=(SharedIntrusive<TT> const& rhs);
|
|
|
|
template <class TT>
|
|
requires std::convertible_to<TT*, T*>
|
|
SharedWeakUnion&
|
|
operator=(
|
|
SharedIntrusive<TT>&& rhs); // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
|
|
|
|
~SharedWeakUnion();
|
|
|
|
/**
|
|
* Return a strong pointer if this is already a strong pointer (i.e.
|
|
* don't lock the weak pointer. Use the `lock` method if that's what's
|
|
* needed)
|
|
*/
|
|
[[nodiscard]] SharedIntrusive<T>
|
|
getStrong() const;
|
|
|
|
/**
|
|
* Return true if this is a strong pointer and the strong pointer is
|
|
* seated.
|
|
*/
|
|
explicit
|
|
operator bool() const noexcept;
|
|
|
|
/**
|
|
* Set the pointer to null, decrement the appropriate ref count, and
|
|
* run the appropriate release action.
|
|
*/
|
|
void
|
|
reset();
|
|
|
|
/**
|
|
* If this is a strong pointer, return the raw pointer. Otherwise
|
|
* return null.
|
|
*/
|
|
[[nodiscard]] T*
|
|
get() const;
|
|
|
|
/**
|
|
* If this is a strong pointer, return the strong count. Otherwise
|
|
* return 0
|
|
*/
|
|
[[nodiscard]] std::size_t
|
|
useCount() const;
|
|
|
|
/**
|
|
* Return true if there is a non-zero strong count.
|
|
*/
|
|
[[nodiscard]] bool
|
|
expired() const;
|
|
|
|
/**
|
|
* If this is a strong pointer, return the strong pointer. Otherwise
|
|
* attempt to lock the weak pointer.
|
|
*/
|
|
[[nodiscard]] SharedIntrusive<T>
|
|
lock() const;
|
|
|
|
/**
|
|
* Return true is this represents a strong pointer.
|
|
*/
|
|
[[nodiscard]] bool
|
|
isStrong() const;
|
|
|
|
/**
|
|
* Return true is this represents a weak pointer.
|
|
*/
|
|
[[nodiscard]] bool
|
|
isWeak() const;
|
|
|
|
/**
|
|
* If this is a weak pointer, attempt to convert it to a strong
|
|
* pointer.
|
|
*
|
|
* @return true if successfully converted to a strong pointer (or was
|
|
* already a strong pointer). Otherwise false.
|
|
*/
|
|
bool
|
|
convertToStrong();
|
|
|
|
/**
|
|
* If this is a strong pointer, attempt to convert it to a weak
|
|
* pointer.
|
|
*
|
|
* @return false if the pointer is null. Otherwise return true.
|
|
*/
|
|
bool
|
|
convertToWeak();
|
|
|
|
private:
|
|
// Tagged pointer. Low bit determines if this is a strong or a weak
|
|
// pointer. The low bit must be masked to zero when converting back to a
|
|
// pointer. If the low bit is '1', this is a weak pointer.
|
|
std::uintptr_t tp_{0};
|
|
static constexpr std::uintptr_t kTagMask = 1;
|
|
static constexpr std::uintptr_t kPtrMask = ~kTagMask;
|
|
|
|
private:
|
|
/**
|
|
* Return the raw pointer held by this object.
|
|
*/
|
|
[[nodiscard]] T*
|
|
unsafeGetRawPtr() const;
|
|
|
|
enum class RefStrength { Strong, Weak };
|
|
/**
|
|
* Set the raw pointer and tag bit directly.
|
|
*/
|
|
void
|
|
unsafeSetRawPtr(T* p, RefStrength rs);
|
|
|
|
/**
|
|
* Set the raw pointer and tag bit to all zeros (strong null pointer).
|
|
*/
|
|
void unsafeSetRawPtr(std::nullptr_t);
|
|
|
|
/**
|
|
* Decrement the appropriate ref count, and run the appropriate release
|
|
* action. Note: this does _not_ set the raw pointer to null.
|
|
*/
|
|
void
|
|
unsafeReleaseNoStore();
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Create a shared intrusive pointer.
|
|
*
|
|
* Note: unlike std::shared_ptr, where there is an advantage of allocating
|
|
* the pointer and control block together, there is no benefit for intrusive
|
|
* pointers.
|
|
*/
|
|
template <class TT, class... Args>
|
|
SharedIntrusive<TT>
|
|
makeSharedIntrusive(Args&&... args)
|
|
{
|
|
auto p = new TT(std::forward<Args>(args)...);
|
|
|
|
static_assert(
|
|
noexcept(SharedIntrusive<TT>(
|
|
std::declval<TT*>(), std::declval<SharedIntrusiveAdoptNoIncrementTag>())),
|
|
"SharedIntrusive constructor should not throw or this can leak "
|
|
"memory");
|
|
|
|
return SharedIntrusive<TT>(p, SharedIntrusiveAdoptNoIncrementTag{});
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace intr_ptr {
|
|
template <class T>
|
|
using SharedPtr = SharedIntrusive<T>;
|
|
|
|
template <class T>
|
|
using WeakPtr = WeakIntrusive<T>;
|
|
|
|
template <class T>
|
|
using SharedWeakUnionPtr = SharedWeakUnion<T>;
|
|
|
|
template <class T, class... A>
|
|
SharedPtr<T>
|
|
makeShared(A&&... args)
|
|
{
|
|
return makeSharedIntrusive<T>(std::forward<A>(args)...);
|
|
}
|
|
|
|
template <class T, class TT>
|
|
SharedPtr<T>
|
|
staticPointerCast(TT const& v)
|
|
{
|
|
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, v);
|
|
}
|
|
|
|
/**
|
|
* Statically cast an intrusive pointer the caller is giving up, moving out of
|
|
* it.
|
|
*
|
|
* The parameter names the wrapped type rather than taking a bare `TT&&`. A
|
|
* bare one would be a forwarding reference, so it would also bind to lvalues
|
|
* in preference to the `const&` overload above and move out of a caller's live
|
|
* variable on what looks like a copy call.
|
|
*
|
|
* @param v the pointer to cast, left empty afterwards.
|
|
* @return a pointer of the requested type to the same object.
|
|
*/
|
|
template <class T, class TT>
|
|
SharedPtr<T>
|
|
staticPointerCast(SharedIntrusive<TT>&& v)
|
|
{
|
|
return SharedPtr<T>(StaticCastTagSharedIntrusive{}, std::move(v));
|
|
}
|
|
|
|
template <class T, class TT>
|
|
SharedPtr<T>
|
|
dynamicPointerCast(TT const& v)
|
|
{
|
|
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, v);
|
|
}
|
|
|
|
/**
|
|
* Dynamically cast an intrusive pointer the caller is giving up, moving out of
|
|
* it.
|
|
*
|
|
* Tied to `SharedIntrusive<TT>&&` for the reason given above.
|
|
*
|
|
* @param v the pointer to cast, left empty afterwards if the cast succeeds and
|
|
* left owning the object if it does not.
|
|
* @return a pointer of the requested type, or an empty one if the object is
|
|
* not of that type.
|
|
*/
|
|
template <class T, class TT>
|
|
SharedPtr<T>
|
|
dynamicPointerCast(SharedIntrusive<TT>&& v)
|
|
{
|
|
return SharedPtr<T>(DynamicCastTagSharedIntrusive{}, std::move(v));
|
|
}
|
|
} // namespace intr_ptr
|
|
} // namespace xrpl
|