Reland "ipcz: Ref counted fragments"
This is a reland of commit 6385cfb931f738334dc59e9d7d39eb7daa70f7ba
Original change's description:
> ipcz: Ref counted fragments
>
> Introduces RefCountedFragment and FragmentRef<T> as helpers to support
> ref-counted objects living in shared memory fragments, as allocated
> via NodeLinkMemory.
>
> Also introduces some builtin BlockAllocators to each NodeLinkMemory's
> primary buffer.
>
> This change lays the ground work for dynamic allocation of managed state
> objects between each connected pair of Routers.
>
> Bug: 1299283
> Change-Id: Ibc2859a8cdcca00fd0d9602664eceaaccb5bd9ae
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3750056
> Reviewed-by: Alex Gough <ajgo@chromium.org>
> Commit-Queue: Ken Rockot <rockot@google.com>
> Cr-Commit-Position: refs/heads/main@{#1021940}
Bug: 1299283
Change-Id: I5d43afc6547a6abc333884e0ab352221b944e5b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3752528
Reviewed-by: Alex Gough <ajgo@chromium.org>
Commit-Queue: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/main@{#1022194}
NOKEYCHECK=True
GitOrigin-RevId: e1512be64c53904a3b5f56ebf9d8f79a8054d9f1
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 75aef4a..85a4b98 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -218,6 +218,7 @@
"ipcz/driver_transport.h",
"ipcz/fragment.h",
"ipcz/fragment_descriptor.h",
+ "ipcz/fragment_ref.h",
"ipcz/link_side.h",
"ipcz/link_type.h",
"ipcz/message.h",
@@ -229,6 +230,7 @@
"ipcz/parcel.h",
"ipcz/parcel_queue.h",
"ipcz/portal.h",
+ "ipcz/ref_counted_fragment.h",
"ipcz/remote_router_link.h",
"ipcz/router.h",
"ipcz/sequence_number.h",
@@ -250,6 +252,7 @@
"ipcz/driver_transport.cc",
"ipcz/fragment.cc",
"ipcz/fragment_descriptor.cc",
+ "ipcz/fragment_ref.cc",
"ipcz/handle_type.h",
"ipcz/link_side.cc",
"ipcz/link_type.cc",
@@ -275,6 +278,7 @@
"ipcz/parcel.cc",
"ipcz/parcel_queue.cc",
"ipcz/portal.cc",
+ "ipcz/ref_counted_fragment.cc",
"ipcz/remote_router_link.cc",
"ipcz/router.cc",
"ipcz/router_descriptor.cc",
@@ -330,6 +334,7 @@
"ipcz/node_connector_test.cc",
"ipcz/node_link_test.cc",
"ipcz/parcel_queue_test.cc",
+ "ipcz/ref_counted_fragment_test.cc",
"ipcz/sequenced_queue_test.cc",
"reference_drivers/sync_reference_driver_test.cc",
"remote_portal_test.cc",
diff --git a/src/ipcz/fragment_ref.cc b/src/ipcz/fragment_ref.cc
new file mode 100644
index 0000000..e586924
--- /dev/null
+++ b/src/ipcz/fragment_ref.cc
@@ -0,0 +1,54 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ipcz/fragment_ref.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "ipcz/fragment.h"
+#include "ipcz/node_link_memory.h"
+#include "ipcz/ref_counted_fragment.h"
+#include "util/ref_counted.h"
+
+namespace ipcz::internal {
+
+GenericFragmentRef::GenericFragmentRef() = default;
+
+GenericFragmentRef::GenericFragmentRef(Ref<NodeLinkMemory> memory,
+ const Fragment& fragment)
+ : memory_(std::move(memory)), fragment_(fragment) {}
+
+GenericFragmentRef::~GenericFragmentRef() {
+ reset();
+}
+
+void GenericFragmentRef::reset() {
+ Ref<NodeLinkMemory> memory = std::move(memory_);
+ if (fragment_.is_null()) {
+ return;
+ }
+
+ Fragment fragment;
+ std::swap(fragment, fragment_);
+ if (!fragment.is_addressable()) {
+ return;
+ }
+
+ auto* ref_counted = static_cast<RefCountedFragment*>(fragment.address());
+ if (ref_counted->ReleaseRef() > 1 || !memory) {
+ return;
+ }
+
+ memory->buffer_pool().FreeFragment(fragment);
+}
+
+Fragment GenericFragmentRef::release() {
+ Fragment fragment;
+ std::swap(fragment_, fragment);
+ memory_.reset();
+ return fragment;
+}
+
+} // namespace ipcz::internal
diff --git a/src/ipcz/fragment_ref.h b/src/ipcz/fragment_ref.h
new file mode 100644
index 0000000..e54135b
--- /dev/null
+++ b/src/ipcz/fragment_ref.h
@@ -0,0 +1,144 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPCZ_SRC_IPCZ_FRAGMENT_REF_H_
+#define IPCZ_SRC_IPCZ_FRAGMENT_REF_H_
+
+#include <algorithm>
+#include <type_traits>
+#include <utility>
+
+#include "ipcz/fragment.h"
+#include "ipcz/fragment_descriptor.h"
+#include "ipcz/ref_counted_fragment.h"
+#include "third_party/abseil-cpp/absl/base/macros.h"
+#include "util/ref_counted.h"
+
+namespace ipcz {
+
+class NodeLinkMemory;
+
+namespace internal {
+
+// Base class for any FragmentRef<T>, implementing common behavior for managing
+// the underlying RefCountedFragment.
+class GenericFragmentRef {
+ public:
+ GenericFragmentRef();
+
+ // Does not increase the ref count of the underlying RefCountedFragment,
+ // effectively assuming ownership of a previously acquired ref.
+ GenericFragmentRef(Ref<NodeLinkMemory> memory, const Fragment& fragment);
+
+ ~GenericFragmentRef();
+
+ const Ref<NodeLinkMemory>& memory() const { return memory_; }
+ const Fragment& fragment() const { return fragment_; }
+
+ bool is_null() const { return fragment_.is_null(); }
+ bool is_addressable() const { return fragment_.is_addressable(); }
+ bool is_pending() const { return fragment_.is_pending(); }
+
+ void reset();
+ Fragment release();
+
+ int32_t ref_count_for_testing() const {
+ return AsRefCountedFragment()->ref_count_for_testing();
+ }
+
+ protected:
+ RefCountedFragment* AsRefCountedFragment() const {
+ return static_cast<RefCountedFragment*>(fragment_.address());
+ }
+
+ // The NodeLinkMemory who ultimately owns this fragment's memory. May be null
+ // if the FragmentRef is unmanaged.
+ Ref<NodeLinkMemory> memory_;
+
+ Fragment fragment_;
+};
+
+} // namespace internal
+
+// Holds a reference to a RefCountedFragment. When this object is destroyed, the
+// underlying ref count is decreased. If the ref count is decreased to zero, the
+// underlying Fragment is returned to its NodeLinkMemory.
+//
+// Some FragmentRefs may be designated as "unmanaged", meaning that they will
+// never attempt to free the underlying Fragment. These refs are used to
+// preserve type compatibility with other similar (but managed) FragmentRefs
+// when the underlying Fragment isn't dynamically allocated and can't be freed.
+//
+// For example most RouterLinkState fragments are dynamically allocated and
+// managed by FragmentRefs, but some instances are allocated at fixed locations
+// within the NodeLinkMemory and cannot be freed or reused. In both cases, ipcz
+// can refer to these objects using a FragmentRef<RouterLinkState>.
+template <typename T>
+class FragmentRef : public internal::GenericFragmentRef {
+ public:
+ static_assert(std::is_base_of<RefCountedFragment, T>::value,
+ "T must inherit RefCountedFragment for FragmentRef<T>");
+
+ constexpr FragmentRef() = default;
+ constexpr FragmentRef(std::nullptr_t) : FragmentRef() {}
+
+ // Adopts an existing ref to the RefCountedFragment located at the beginning
+ // of `fragment`, which is a Fragment owned by `memory.
+ FragmentRef(decltype(RefCountedFragment::kAdoptExistingRef),
+ Ref<NodeLinkMemory> memory,
+ const Fragment& fragment)
+ : GenericFragmentRef(std::move(memory), fragment) {
+ ABSL_ASSERT(memory_);
+ ABSL_ASSERT(fragment_.is_null() || fragment_.size() >= sizeof(T));
+ }
+
+ // Constructs an unmanaged FragmentRef, which references `fragment` and
+ // updates its refcount, but which never attempts to release `fragment` back
+ // to its NodeLinkMemory. This is only safe to use with Fragments which cannot
+ // be freed.
+ FragmentRef(decltype(RefCountedFragment::kUnmanagedRef),
+ const Fragment& fragment)
+ : GenericFragmentRef(nullptr, fragment) {
+ ABSL_ASSERT(fragment_.is_null() || fragment_.size() >= sizeof(T));
+ }
+
+ FragmentRef(const FragmentRef<T>& other)
+ : GenericFragmentRef(other.memory(), other.fragment()) {
+ if (!fragment_.is_null()) {
+ ABSL_ASSERT(fragment_.is_addressable());
+ AsRefCountedFragment()->AddRef();
+ }
+ }
+
+ FragmentRef(FragmentRef<T>&& other) noexcept
+ : GenericFragmentRef(std::move(other.memory_), other.fragment_) {
+ other.release();
+ }
+
+ FragmentRef<T>& operator=(const FragmentRef<T>& other) {
+ reset();
+ memory_ = other.memory();
+ fragment_ = other.fragment();
+ if (!fragment_.is_null()) {
+ ABSL_ASSERT(fragment_.is_addressable());
+ AsRefCountedFragment()->AddRef();
+ }
+ return *this;
+ }
+
+ FragmentRef<T>& operator=(FragmentRef<T>&& other) {
+ reset();
+ memory_ = std::move(other.memory_);
+ fragment_ = other.release();
+ return *this;
+ }
+
+ T* get() const { return static_cast<T*>(fragment_.address()); }
+ T* operator->() const { return get(); }
+ T& operator*() const { return *get(); }
+};
+
+} // namespace ipcz
+
+#endif // IPCZ_SRC_IPCZ_FRAGMENT_REF_H_
diff --git a/src/ipcz/node_link_memory.cc b/src/ipcz/node_link_memory.cc
index 5a8aba0..012f26c 100644
--- a/src/ipcz/node_link_memory.cc
+++ b/src/ipcz/node_link_memory.cc
@@ -25,12 +25,11 @@
// Fixed allocation size for each NodeLink's primary shared buffer.
constexpr size_t kPrimaryBufferSize = 65536;
-} // namespace
+// The front of the primary buffer is reserved for special current and future
+// uses which require synchronous availability throughout a link's lifetime.
+constexpr size_t kPrimaryBufferReservedHeaderSize = 256;
-// This structure always sits at offset 0 in the primary buffer and has a fixed
-// layout according to the NodeLink's agreed upon protocol version. This is the
-// layout for version 0 (currently the only version.)
-struct IPCZ_ALIGN(8) NodeLinkMemory::PrimaryBuffer {
+struct IPCZ_ALIGN(8) PrimaryBufferHeader {
// Atomic generator for new unique BufferIds to use across the associated
// NodeLink. This allows each side of a NodeLink to generate new BufferIds
// spontaneously without synchronization or risk of collisions.
@@ -42,6 +41,51 @@
std::atomic<uint64_t> next_sublink_id;
};
+static_assert(sizeof(PrimaryBufferHeader) < kPrimaryBufferReservedHeaderSize);
+
+constexpr size_t kPrimaryBufferHeaderPaddingSize =
+ kPrimaryBufferReservedHeaderSize - sizeof(PrimaryBufferHeader);
+
+} // namespace
+
+// This structure always sits at offset 0 in the primary buffer and has a fixed
+// layout according to the NodeLink's agreed upon protocol version. This is the
+// layout for version 0 (currently the only version.)
+struct IPCZ_ALIGN(8) NodeLinkMemory::PrimaryBuffer {
+ // Header + padding occupies the first 256 bytes.
+ PrimaryBufferHeader header;
+ uint8_t reserved_header_padding[kPrimaryBufferHeaderPaddingSize];
+
+ // Reserved memory for a series of fixed block allocators. Additional
+ // allocators may be adopted by a NodeLinkMemory over its lifetime, but these
+ // ones remain fixed within the primary buffer.
+ std::array<uint8_t, 4096> mem_for_64_byte_blocks;
+ std::array<uint8_t, 12288> mem_for_256_byte_blocks;
+ std::array<uint8_t, 15360> mem_for_512_byte_blocks;
+ std::array<uint8_t, 11264> mem_for_1024_byte_blocks;
+ std::array<uint8_t, 16384> mem_for_2048_byte_blocks;
+
+ BlockAllocator block_allocator_64() {
+ return BlockAllocator(absl::MakeSpan(mem_for_64_byte_blocks), 64);
+ }
+
+ BlockAllocator block_allocator_256() {
+ return BlockAllocator(absl::MakeSpan(mem_for_256_byte_blocks), 256);
+ }
+
+ BlockAllocator block_allocator_512() {
+ return BlockAllocator(absl::MakeSpan(mem_for_512_byte_blocks), 512);
+ }
+
+ BlockAllocator block_allocator_1024() {
+ return BlockAllocator(absl::MakeSpan(mem_for_1024_byte_blocks), 1024);
+ }
+
+ BlockAllocator block_allocator_2048() {
+ return BlockAllocator(absl::MakeSpan(mem_for_2048_byte_blocks), 2048);
+ }
+};
+
NodeLinkMemory::NodeLinkMemory(Ref<Node> node,
DriverMemoryMapping primary_buffer_memory)
: node_(std::move(node)),
@@ -52,8 +96,17 @@
static_assert(sizeof(PrimaryBuffer) <= kPrimaryBufferSize,
"PrimaryBuffer structure is too large.");
- buffer_pool_.AddBuffer(BufferId{kPrimaryBufferId},
- std::move(primary_buffer_memory));
+ buffer_pool_.AddBuffer(kPrimaryBufferId, std::move(primary_buffer_memory));
+ buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
+ primary_buffer_.block_allocator_64());
+ buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
+ primary_buffer_.block_allocator_256());
+ buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
+ primary_buffer_.block_allocator_512());
+ buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
+ primary_buffer_.block_allocator_1024());
+ buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
+ primary_buffer_.block_allocator_2048());
}
NodeLinkMemory::~NodeLinkMemory() = default;
@@ -71,15 +124,21 @@
PrimaryBuffer& primary_buffer = memory->primary_buffer_;
// The first allocable BufferId is 1, because the primary buffer uses 0.
- primary_buffer.next_buffer_id.store(1, std::memory_order_relaxed);
+ primary_buffer.header.next_buffer_id.store(1, std::memory_order_relaxed);
// The first allocable SublinkId is kMaxInitialPortals. This way it doesn't
// matter whether the two ends of a NodeLink initiate their connection with a
// different initial portal count: neither can request more than
// kMaxInitialPortals, so neither will be assuming initial ownership of any
// SublinkIds at or above this value.
- primary_buffer.next_sublink_id.store(kMaxInitialPortals,
- std::memory_order_release);
+ primary_buffer.header.next_sublink_id.store(kMaxInitialPortals,
+ std::memory_order_release);
+
+ primary_buffer.block_allocator_64().InitializeRegion();
+ primary_buffer.block_allocator_256().InitializeRegion();
+ primary_buffer.block_allocator_512().InitializeRegion();
+ primary_buffer.block_allocator_1024().InitializeRegion();
+ primary_buffer.block_allocator_2048().InitializeRegion();
return {
.node_link_memory = std::move(memory),
@@ -95,12 +154,12 @@
}
BufferId NodeLinkMemory::AllocateNewBufferId() {
- return BufferId{
- primary_buffer_.next_buffer_id.fetch_add(1, std::memory_order_relaxed)};
+ return BufferId{primary_buffer_.header.next_buffer_id.fetch_add(
+ 1, std::memory_order_relaxed)};
}
SublinkId NodeLinkMemory::AllocateSublinkIds(size_t count) {
- return SublinkId{primary_buffer_.next_sublink_id.fetch_add(
+ return SublinkId{primary_buffer_.header.next_sublink_id.fetch_add(
count, std::memory_order_relaxed)};
}
diff --git a/src/ipcz/node_link_memory.h b/src/ipcz/node_link_memory.h
index b4d65a6..014a78e 100644
--- a/src/ipcz/node_link_memory.h
+++ b/src/ipcz/node_link_memory.h
@@ -63,7 +63,7 @@
// Exposes the underlying BufferPool which owns all shared buffers for this
// NodeLinkMemory and which facilitates dynamic allocation of the fragments
// within.
- BufferPool& buffer_pool();
+ BufferPool& buffer_pool() { return buffer_pool_; }
// Returns a new BufferId which should still be unused by any buffer in this
// NodeLinkMemory's BufferPool, or that of its peer NodeLinkMemory. When
diff --git a/src/ipcz/ref_counted_fragment.cc b/src/ipcz/ref_counted_fragment.cc
new file mode 100644
index 0000000..14b21cf
--- /dev/null
+++ b/src/ipcz/ref_counted_fragment.cc
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ipcz/ref_counted_fragment.h"
+
+#include "third_party/abseil-cpp/absl/base/macros.h"
+
+namespace ipcz {
+
+RefCountedFragment::RefCountedFragment() = default;
+
+RefCountedFragment::~RefCountedFragment() = default;
+
+void RefCountedFragment::AddRef() {
+ ref_count_.fetch_add(1, std::memory_order_relaxed);
+}
+
+int32_t RefCountedFragment::ReleaseRef() {
+ return ref_count_.fetch_sub(1, std::memory_order_acq_rel);
+}
+
+} // namespace ipcz
diff --git a/src/ipcz/ref_counted_fragment.h b/src/ipcz/ref_counted_fragment.h
new file mode 100644
index 0000000..ff69c45
--- /dev/null
+++ b/src/ipcz/ref_counted_fragment.h
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPCZ_SRC_IPCZ_REF_COUNTED_FRAGMENT_H_
+#define IPCZ_SRC_IPCZ_REF_COUNTED_FRAGMENT_H_
+
+#include <atomic>
+
+#include "ipcz/ipcz.h"
+#include "util/ref_counted.h"
+
+namespace ipcz {
+
+// A RefCountedFragment is an object allocated within a shared Fragment from
+// NodeLinkMemory, and which is automatially freed when its last reference is
+// released. Consumers can hold onto references to RefCountedFragment objects
+// by holding a FragmentRef.
+struct IPCZ_ALIGN(4) RefCountedFragment {
+ enum { kAdoptExistingRef };
+ enum { kUnmanagedRef };
+
+ RefCountedFragment();
+ ~RefCountedFragment();
+
+ int32_t ref_count_for_testing() const { return ref_count_; }
+
+ // Increments the reference count for this object.
+ void AddRef();
+
+ // Releases a reference and returns the previous reference count. If this
+ // returns 1, the underlying Fragment can be safely freed.
+ int32_t ReleaseRef();
+
+ private:
+ std::atomic<int32_t> ref_count_{1};
+};
+
+} // namespace ipcz
+
+#endif // IPCZ_SRC_IPCZ_REF_COUNTED_FRAGMENT_H_
diff --git a/src/ipcz/ref_counted_fragment_test.cc b/src/ipcz/ref_counted_fragment_test.cc
new file mode 100644
index 0000000..5425304
--- /dev/null
+++ b/src/ipcz/ref_counted_fragment_test.cc
@@ -0,0 +1,179 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ipcz/ref_counted_fragment.h"
+
+#include <atomic>
+#include <tuple>
+
+#include "ipcz/fragment.h"
+#include "ipcz/fragment_ref.h"
+#include "ipcz/node.h"
+#include "ipcz/node_link_memory.h"
+#include "reference_drivers/sync_reference_driver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "util/ref_counted.h"
+
+namespace ipcz {
+namespace {
+
+const IpczDriver& kTestDriver = reference_drivers::kSyncReferenceDriver;
+
+using RefCountedFragmentTest = testing::Test;
+
+using TestObject = RefCountedFragment;
+
+TEST_F(RefCountedFragmentTest, NullRef) {
+ FragmentRef<TestObject> ref;
+ EXPECT_TRUE(ref.is_null());
+ EXPECT_FALSE(ref.is_addressable());
+
+ ref.reset();
+ EXPECT_TRUE(ref.is_null());
+ EXPECT_FALSE(ref.is_addressable());
+
+ FragmentRef<TestObject> other1 = ref;
+ EXPECT_TRUE(ref.is_null());
+ EXPECT_FALSE(ref.is_addressable());
+ EXPECT_TRUE(other1.is_null());
+ EXPECT_FALSE(other1.is_addressable());
+
+ FragmentRef<TestObject> other2 = std::move(ref);
+ EXPECT_TRUE(ref.is_null());
+ EXPECT_FALSE(ref.is_addressable());
+ EXPECT_TRUE(other2.is_null());
+ EXPECT_FALSE(other2.is_addressable());
+
+ ref = other1;
+ EXPECT_TRUE(ref.is_null());
+ EXPECT_FALSE(ref.is_addressable());
+ EXPECT_TRUE(other1.is_null());
+ EXPECT_FALSE(other1.is_addressable());
+
+ ref = std::move(other2);
+ EXPECT_TRUE(ref.is_null());
+ EXPECT_FALSE(ref.is_addressable());
+ EXPECT_TRUE(other1.is_null());
+ EXPECT_FALSE(other1.is_addressable());
+}
+
+TEST_F(RefCountedFragmentTest, SimpleRef) {
+ TestObject object;
+
+ FragmentRef<TestObject> ref(
+ RefCountedFragment::kUnmanagedRef,
+ Fragment(FragmentDescriptor(BufferId(0), 0, sizeof(object)), &object));
+ EXPECT_EQ(1, object.ref_count_for_testing());
+ ref.reset();
+ EXPECT_EQ(0, object.ref_count_for_testing());
+}
+
+TEST_F(RefCountedFragmentTest, Copy) {
+ TestObject object1;
+
+ FragmentRef<TestObject> ref1(
+ RefCountedFragment::kUnmanagedRef,
+ Fragment(FragmentDescriptor(BufferId(0), 0, sizeof(object1)), &object1));
+ EXPECT_EQ(1, object1.ref_count_for_testing());
+
+ FragmentRef<TestObject> other1 = ref1;
+ EXPECT_EQ(2, object1.ref_count_for_testing());
+ other1.reset();
+ EXPECT_EQ(1, object1.ref_count_for_testing());
+ EXPECT_TRUE(other1.is_null());
+ EXPECT_FALSE(other1.is_addressable());
+
+ TestObject object2;
+ auto ref2 = FragmentRef<TestObject>(
+ RefCountedFragment::kUnmanagedRef,
+ Fragment(FragmentDescriptor(BufferId(0), 0, sizeof(object2)), &object2));
+ EXPECT_EQ(1, object1.ref_count_for_testing());
+ EXPECT_EQ(1, object2.ref_count_for_testing());
+ ref2 = ref1;
+ EXPECT_EQ(2, object1.ref_count_for_testing());
+ EXPECT_EQ(0, object2.ref_count_for_testing());
+ EXPECT_FALSE(ref1.is_null());
+ EXPECT_TRUE(ref1.is_addressable());
+ EXPECT_FALSE(ref2.is_null());
+ EXPECT_TRUE(ref2.is_addressable());
+ ref1.reset();
+ EXPECT_EQ(1, object1.ref_count_for_testing());
+ EXPECT_EQ(0, object2.ref_count_for_testing());
+ EXPECT_TRUE(ref1.is_null());
+ EXPECT_FALSE(ref1.is_addressable());
+ ref2.reset();
+ EXPECT_EQ(0, object1.ref_count_for_testing());
+ EXPECT_EQ(0, object2.ref_count_for_testing());
+ EXPECT_TRUE(ref2.is_null());
+ EXPECT_FALSE(ref2.is_addressable());
+}
+
+TEST_F(RefCountedFragmentTest, Move) {
+ TestObject object1;
+
+ FragmentRef<TestObject> ref1(
+ RefCountedFragment::kUnmanagedRef,
+ Fragment(FragmentDescriptor(BufferId(0), 0, sizeof(object1)), &object1));
+ EXPECT_EQ(1, ref1.ref_count_for_testing());
+
+ FragmentRef<TestObject> other1 = std::move(ref1);
+ EXPECT_EQ(1, object1.ref_count_for_testing());
+ EXPECT_FALSE(other1.is_null());
+ EXPECT_TRUE(other1.is_addressable());
+ EXPECT_TRUE(ref1.is_null());
+ EXPECT_FALSE(ref1.is_addressable());
+ other1.reset();
+ EXPECT_TRUE(other1.is_null());
+ EXPECT_FALSE(other1.is_addressable());
+ EXPECT_EQ(0, object1.ref_count_for_testing());
+
+ TestObject object2;
+ TestObject object3;
+ FragmentRef<TestObject> ref2(
+ RefCountedFragment::kUnmanagedRef,
+ Fragment(FragmentDescriptor(BufferId(0), 0, sizeof(object2)), &object2));
+ FragmentRef<TestObject> ref3(
+ RefCountedFragment::kUnmanagedRef,
+ Fragment(FragmentDescriptor(BufferId(0), 0, sizeof(object3)), &object3));
+
+ EXPECT_FALSE(ref2.is_null());
+ EXPECT_TRUE(ref2.is_addressable());
+ EXPECT_FALSE(ref3.is_null());
+ EXPECT_TRUE(ref3.is_addressable());
+ EXPECT_EQ(1, object2.ref_count_for_testing());
+ EXPECT_EQ(1, object3.ref_count_for_testing());
+ ref3 = std::move(ref2);
+ EXPECT_EQ(1, object2.ref_count_for_testing());
+ EXPECT_EQ(0, object3.ref_count_for_testing());
+ EXPECT_TRUE(ref2.is_null());
+ EXPECT_FALSE(ref2.is_addressable());
+ EXPECT_FALSE(ref3.is_null());
+ EXPECT_TRUE(ref3.is_addressable());
+ ref3.reset();
+ EXPECT_TRUE(ref3.is_null());
+ EXPECT_FALSE(ref3.is_addressable());
+ EXPECT_EQ(0, object2.ref_count_for_testing());
+ EXPECT_EQ(0, object3.ref_count_for_testing());
+}
+
+TEST_F(RefCountedFragmentTest, Free) {
+ auto node = MakeRefCounted<Node>(Node::Type::kNormal, kTestDriver,
+ IPCZ_INVALID_DRIVER_HANDLE);
+ auto memory = NodeLinkMemory::Allocate(std::move(node)).node_link_memory;
+
+ // Allocate a ton of fragments and let them be released by FragmentRef on
+ // destruction. If the fragments aren't freed properly, allocations will fail
+ // and so will the test.
+ constexpr size_t kNumAllocations = 100000;
+ for (size_t i = 0; i < kNumAllocations; ++i) {
+ Fragment fragment =
+ memory->buffer_pool().AllocateFragment(sizeof(TestObject));
+ EXPECT_TRUE(fragment.is_addressable());
+ FragmentRef<TestObject> ref(RefCountedFragment::kAdoptExistingRef, memory,
+ fragment);
+ }
+}
+
+} // namespace
+} // namespace ipcz