blob: d3707f3e7f16a66514d7981d649938a3ba4e7d02 [file] [log] [blame]
// 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/node_link_memory.h"
#include <utility>
#include <vector>
#include "ipcz/driver_memory.h"
#include "ipcz/driver_transport.h"
#include "ipcz/ipcz.h"
#include "ipcz/link_side.h"
#include "ipcz/node.h"
#include "ipcz/node_link.h"
#include "ipcz/node_link_memory.h"
#include "ipcz/node_name.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;
constexpr NodeName kTestBrokerName(1, 2);
constexpr NodeName kTestNonBrokerName(2, 3);
class NodeLinkMemoryTest : public testing::Test {
public:
NodeLinkMemory& memory_a() { return link_a_->memory(); }
NodeLinkMemory& memory_b() { return link_b_->memory(); }
void SetUp() override {
auto transports = DriverTransport::CreatePair(kTestDriver);
DriverMemoryWithMapping buffer =
NodeLinkMemory::AllocateMemory(kTestDriver);
link_a_ = NodeLink::CreateInactive(
node_a_, LinkSide::kA, kTestBrokerName, kTestNonBrokerName,
Node::Type::kNormal, 0, transports.first,
NodeLinkMemory::Create(node_a_, std::move(buffer.mapping)));
link_b_ = NodeLink::CreateInactive(
node_b_, LinkSide::kB, kTestNonBrokerName, kTestBrokerName,
Node::Type::kBroker, 0, transports.second,
NodeLinkMemory::Create(node_b_, buffer.memory.Map()));
node_a_->AddLink(kTestNonBrokerName, link_a_);
node_b_->AddLink(kTestBrokerName, link_b_);
link_a_->Activate();
link_b_->Activate();
}
void TearDown() override {
node_b_->Close();
node_a_->Close();
}
private:
const Ref<Node> node_a_{MakeRefCounted<Node>(Node::Type::kBroker,
kTestDriver,
IPCZ_INVALID_DRIVER_HANDLE)};
const Ref<Node> node_b_{MakeRefCounted<Node>(Node::Type::kNormal,
kTestDriver,
IPCZ_INVALID_DRIVER_HANDLE)};
Ref<NodeLink> link_a_;
Ref<NodeLink> link_b_;
};
TEST_F(NodeLinkMemoryTest, BasicAllocAndFree) {
Fragment fragment = memory_a().AllocateFragment(64);
EXPECT_TRUE(fragment.is_addressable());
EXPECT_TRUE(fragment.address());
EXPECT_EQ(fragment.size(), 64u);
EXPECT_TRUE(memory_a().FreeFragment(fragment));
}
TEST_F(NodeLinkMemoryTest, Zero) {
// Zero-sized fragments cannot be allocated.
EXPECT_TRUE(memory_a().AllocateFragment(0).is_null());
}
TEST_F(NodeLinkMemoryTest, MinimumSize) {
// Very small fragment sizes a minimum of 64 bytes.
Fragment fragments[] = {
memory_a().AllocateFragment(1), memory_a().AllocateFragment(2),
memory_a().AllocateFragment(3), memory_a().AllocateFragment(4),
memory_a().AllocateFragment(17), memory_a().AllocateFragment(63),
};
for (const auto& fragment : fragments) {
EXPECT_TRUE(fragment.is_addressable());
EXPECT_EQ(64u, fragment.size());
}
}
TEST_F(NodeLinkMemoryTest, RoundUpSize) {
// Fragment sizes are rounded up to the nearest power of 2.
Fragment fragment = memory_a().AllocateFragment(250);
EXPECT_TRUE(fragment.is_addressable());
EXPECT_EQ(256u, fragment.size());
}
TEST_F(NodeLinkMemoryTest, SharedPrimaryBuffer) {
// Test basic allocation from the primary buffer which both NodeLinkMemory
// instances share from the moment they're constructed. Each NodeLinkMemory
// should be able to resolve and free fragments allocated by the other.
Fragment fragment_from_a = memory_a().AllocateFragment(8);
EXPECT_TRUE(fragment_from_a.is_addressable());
EXPECT_EQ(BufferId(0), fragment_from_a.buffer_id());
EXPECT_GE(fragment_from_a.size(), 8u);
Fragment same_fragment = memory_b().GetFragment(fragment_from_a.descriptor());
EXPECT_TRUE(same_fragment.is_addressable());
EXPECT_EQ(fragment_from_a.buffer_id(), same_fragment.buffer_id());
EXPECT_EQ(fragment_from_a.offset(), same_fragment.offset());
EXPECT_EQ(fragment_from_a.size(), same_fragment.size());
Fragment fragment_from_b = memory_b().AllocateFragment(16);
EXPECT_TRUE(fragment_from_b.is_addressable());
EXPECT_EQ(BufferId(0), fragment_from_b.buffer_id());
EXPECT_GE(fragment_from_b.size(), 16u);
same_fragment = memory_a().GetFragment(fragment_from_b.descriptor());
EXPECT_TRUE(same_fragment.is_addressable());
EXPECT_EQ(fragment_from_b.buffer_id(), same_fragment.buffer_id());
EXPECT_EQ(fragment_from_b.offset(), same_fragment.offset());
EXPECT_EQ(fragment_from_b.size(), same_fragment.size());
EXPECT_TRUE(memory_a().FreeFragment(fragment_from_b));
EXPECT_TRUE(memory_b().FreeFragment(fragment_from_a));
}
TEST_F(NodeLinkMemoryTest, ExpandCapacity) {
// If we depelete a NodeLinkMemory's capacity to allocate fragments of a given
// size, it should automatically acquire new capacity for future allocations.
constexpr size_t kSize = 64;
bool has_new_capacity = false;
memory_a().WaitForBufferAsync(
BufferId(1), [&has_new_capacity] { has_new_capacity = true; });
while (!memory_a().AllocateFragment(kSize).is_null())
;
// Since we're using a synchronous driver, this should have already been true
// by the time the most recent failed allocation returned.
EXPECT_TRUE(has_new_capacity);
// And a subsequent allocation request should now succeed with a fragment from
// the new buffer.
Fragment fragment = memory_a().AllocateFragment(kSize);
EXPECT_FALSE(fragment.is_null());
EXPECT_TRUE(fragment.is_addressable());
EXPECT_EQ(BufferId(1), fragment.buffer_id());
// The new buffer should have also been shared with the other NodeLinkMemory
// already.
EXPECT_TRUE(memory_b().FreeFragment(fragment));
}
TEST_F(NodeLinkMemoryTest, LimitedCapacityExpansion) {
// A NodeLinkMemory will eventually stop expanding its capacity for new
// fragments of a given size.
static constexpr size_t kSize = 64;
std::vector<Fragment> fragments;
auto try_alloc = [&fragments, this] {
Fragment fragment = memory_a().AllocateFragment(kSize);
if (!fragment.is_null()) {
fragments.push_back(fragment);
}
return !fragment.is_null();
};
do {
// Deplete the current capacity.
while (try_alloc()) {
}
// Because we're using a synchronous driver, if the NodeLinkMemory will
// expand its capacity at all, it will have already done so by the time the
// the failed allocation returns above. So if allocation fails again here,
// then we've reached the capacity limit for this fragment size and we can
// end the test.
} while (try_alloc());
// Any additionally allocated buffers should already have been shared with the
// other NodeLinkMemory. Let it free all of the fragments and verify success
// in every case.
for (const auto& fragment : fragments) {
EXPECT_TRUE(memory_b().FreeFragment(fragment));
}
}
TEST_F(NodeLinkMemoryTest, OversizedAllocation) {
// Allocations which are too large for block-based allocation will fail for
// now. This may change as new allocation schemes are supported.
constexpr size_t kWayTooBig = 64 * 1024 * 1024;
Fragment fragment = memory_a().AllocateFragment(kWayTooBig);
EXPECT_TRUE(fragment.is_null());
}
TEST_F(NodeLinkMemoryTest, NewBlockSizes) {
// NodeLinkMemory begins life with a fixed set of block allocators available
// for certain common block sizes. These are capped out at 2 kB blocks, but
// NodeLinkMemory still supports block allocation of larger blocks as well --
// at least up to 16 kB in size. Verify that we can trigger new capacity for
// such sizes by attempting to allocate them.
constexpr size_t kPrettyBig = 16 * 1024;
Fragment fragment = memory_a().AllocateFragment(kPrettyBig);
// No initial capacity for 16 kB fragments.
EXPECT_TRUE(fragment.is_null());
// But the failure above should have triggered expansion of capacity for that
// size. This request should succeed.
fragment = memory_a().AllocateFragment(kPrettyBig);
EXPECT_FALSE(fragment.is_null());
EXPECT_TRUE(fragment.is_addressable());
EXPECT_GE(fragment.size(), kPrettyBig);
// And as with other cases, the new capacity should have already been shared
// with the other NodeLinkMemory.
EXPECT_TRUE(memory_b().FreeFragment(fragment));
}
} // namespace
} // namespace ipcz