// Copyright 2014 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 "content/common/host_discardable_shared_memory_manager.h" #include #include #include #include "base/threading/simple_thread.h" #include "content/public/common/child_process_host.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { class TestDiscardableSharedMemory : public base::DiscardableSharedMemory { public: TestDiscardableSharedMemory() {} explicit TestDiscardableSharedMemory(base::SharedMemoryHandle handle) : DiscardableSharedMemory(handle) {} void SetNow(base::Time now) { now_ = now; } private: // Overriden from base::DiscardableSharedMemory: base::Time Now() const override { return now_; } base::Time now_; }; class TestHostDiscardableSharedMemoryManager : public HostDiscardableSharedMemoryManager { public: TestHostDiscardableSharedMemoryManager() : enforce_memory_policy_pending_(false) {} void SetNow(base::Time now) { now_ = now; } void set_enforce_memory_policy_pending(bool enforce_memory_policy_pending) { enforce_memory_policy_pending_ = enforce_memory_policy_pending; } bool enforce_memory_policy_pending() const { return enforce_memory_policy_pending_; } private: // Overriden from HostDiscardableSharedMemoryManager: base::Time Now() const override { return now_; } void ScheduleEnforceMemoryPolicy() override { enforce_memory_policy_pending_ = true; } base::Time now_; bool enforce_memory_policy_pending_; }; class HostDiscardableSharedMemoryManagerTest : public testing::Test { protected: // Overridden from testing::Test: void SetUp() override { manager_.reset(new TestHostDiscardableSharedMemoryManager); } // HostDiscardableSharedMemoryManager requires a message loop. base::MessageLoop message_loop_; scoped_ptr manager_; }; TEST_F(HostDiscardableSharedMemoryManagerTest, AllocateForChild) { const int kDataSize = 1024; uint8_t data[kDataSize]; memset(data, 0x80, kDataSize); base::SharedMemoryHandle shared_handle; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 0, &shared_handle); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle)); TestDiscardableSharedMemory memory(shared_handle); bool rv = memory.Map(kDataSize); ASSERT_TRUE(rv); memcpy(memory.memory(), data, kDataSize); memory.SetNow(base::Time::FromDoubleT(1)); memory.Unlock(0, 0); ASSERT_EQ(base::DiscardableSharedMemory::SUCCESS, memory.Lock(0, 0)); EXPECT_EQ(memcmp(data, memory.memory(), kDataSize), 0); memory.Unlock(0, 0); } TEST_F(HostDiscardableSharedMemoryManagerTest, Purge) { const int kDataSize = 1024; base::SharedMemoryHandle shared_handle1; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 1, &shared_handle1); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle1)); TestDiscardableSharedMemory memory1(shared_handle1); bool rv = memory1.Map(kDataSize); ASSERT_TRUE(rv); base::SharedMemoryHandle shared_handle2; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 2, &shared_handle2); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle2)); TestDiscardableSharedMemory memory2(shared_handle2); rv = memory2.Map(kDataSize); ASSERT_TRUE(rv); // Enough memory for both allocations. manager_->SetNow(base::Time::FromDoubleT(1)); manager_->SetMemoryLimit(memory1.mapped_size() + memory2.mapped_size()); memory1.SetNow(base::Time::FromDoubleT(2)); memory1.Unlock(0, 0); memory2.SetNow(base::Time::FromDoubleT(2)); memory2.Unlock(0, 0); // Manager should not have to schedule another call to EnforceMemoryPolicy(). manager_->SetNow(base::Time::FromDoubleT(3)); manager_->EnforceMemoryPolicy(); EXPECT_FALSE(manager_->enforce_memory_policy_pending()); // Memory should still be resident. EXPECT_TRUE(memory1.IsMemoryResident()); EXPECT_TRUE(memory2.IsMemoryResident()); auto lock_rv = memory1.Lock(0, 0); EXPECT_EQ(base::DiscardableSharedMemory::SUCCESS, lock_rv); lock_rv = memory2.Lock(0, 0); EXPECT_EQ(base::DiscardableSharedMemory::SUCCESS, lock_rv); memory1.SetNow(base::Time::FromDoubleT(4)); memory1.Unlock(0, 0); memory2.SetNow(base::Time::FromDoubleT(5)); memory2.Unlock(0, 0); // Just enough memory for one allocation. manager_->SetNow(base::Time::FromDoubleT(6)); manager_->SetMemoryLimit(memory2.mapped_size()); EXPECT_FALSE(manager_->enforce_memory_policy_pending()); // LRU allocation should still be resident. EXPECT_FALSE(memory1.IsMemoryResident()); EXPECT_TRUE(memory2.IsMemoryResident()); lock_rv = memory1.Lock(0, 0); EXPECT_EQ(base::DiscardableSharedMemory::FAILED, lock_rv); lock_rv = memory2.Lock(0, 0); EXPECT_EQ(base::DiscardableSharedMemory::SUCCESS, lock_rv); } TEST_F(HostDiscardableSharedMemoryManagerTest, EnforceMemoryPolicy) { const int kDataSize = 1024; base::SharedMemoryHandle shared_handle; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 0, &shared_handle); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle)); TestDiscardableSharedMemory memory(shared_handle); bool rv = memory.Map(kDataSize); ASSERT_TRUE(rv); // Not enough memory for one allocation. manager_->SetNow(base::Time::FromDoubleT(1)); manager_->SetMemoryLimit(memory.mapped_size() - 1); // We need to enforce memory policy as our memory usage is currently above // the limit. EXPECT_TRUE(manager_->enforce_memory_policy_pending()); manager_->set_enforce_memory_policy_pending(false); manager_->SetNow(base::Time::FromDoubleT(2)); manager_->EnforceMemoryPolicy(); // Still need to enforce memory policy as nothing can be purged. EXPECT_TRUE(manager_->enforce_memory_policy_pending()); memory.SetNow(base::Time::FromDoubleT(3)); memory.Unlock(0, 0); manager_->set_enforce_memory_policy_pending(false); manager_->SetNow(base::Time::FromDoubleT(4)); manager_->EnforceMemoryPolicy(); // Memory policy should have successfully been enforced. EXPECT_FALSE(manager_->enforce_memory_policy_pending()); EXPECT_EQ(base::DiscardableSharedMemory::FAILED, memory.Lock(0, 0)); } TEST_F(HostDiscardableSharedMemoryManagerTest, ReduceMemoryAfterSegmentHasBeenDeleted) { const int kDataSize = 1024; base::SharedMemoryHandle shared_handle1; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 1, &shared_handle1); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle1)); TestDiscardableSharedMemory memory1(shared_handle1); bool rv = memory1.Map(kDataSize); ASSERT_TRUE(rv); base::SharedMemoryHandle shared_handle2; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 2, &shared_handle2); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle2)); TestDiscardableSharedMemory memory2(shared_handle2); rv = memory2.Map(kDataSize); ASSERT_TRUE(rv); // Unlock and delete segment 1. memory1.SetNow(base::Time::FromDoubleT(1)); memory1.Unlock(0, 0); memory1.Unmap(); memory1.Close(); manager_->ChildDeletedDiscardableSharedMemory( 1, ChildProcessHost::kInvalidUniqueID); // Make sure the manager is able to reduce memory after the segment 1 was // deleted. manager_->SetNow(base::Time::FromDoubleT(2)); manager_->SetMemoryLimit(0); // Unlock segment 2. memory2.SetNow(base::Time::FromDoubleT(3)); memory2.Unlock(0, 0); } class HostDiscardableSharedMemoryManagerScheduleEnforceMemoryPolicyTest : public testing::Test { protected: // Overridden from testing::Test: void SetUp() override { manager_.reset(new HostDiscardableSharedMemoryManager); } // HostDiscardableSharedMemoryManager requires a message loop. base::MessageLoop message_loop_; scoped_ptr manager_; }; class SetMemoryLimitRunner : public base::DelegateSimpleThread::Delegate { public: SetMemoryLimitRunner(HostDiscardableSharedMemoryManager* manager, size_t limit) : manager_(manager), limit_(limit) {} ~SetMemoryLimitRunner() override {} void Run() override { manager_->SetMemoryLimit(limit_); } private: HostDiscardableSharedMemoryManager* const manager_; const size_t limit_; }; TEST_F(HostDiscardableSharedMemoryManagerScheduleEnforceMemoryPolicyTest, SetMemoryLimitOnSimpleThread) { const int kDataSize = 1024; base::SharedMemoryHandle shared_handle; manager_->AllocateLockedDiscardableSharedMemoryForChild( base::GetCurrentProcessHandle(), ChildProcessHost::kInvalidUniqueID, kDataSize, 0, &shared_handle); ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle)); // Set the memory limit to a value that will require EnforceMemoryPolicy() // to be schedule on a thread without a message loop. SetMemoryLimitRunner runner(manager_.get(), kDataSize - 1); base::DelegateSimpleThread thread(&runner, "memory_limit_setter"); thread.Start(); thread.Join(); } } // namespace } // namespace content