blob: 6320be76d96d6a7d1ba71a5bf279b5cec787440f [file] [log] [blame]
// 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 <fcntl.h>
#include "base/files/scoped_file.h"
#include "base/memory/discardable_shared_memory.h"
#include "base/memory/shared_memory_tracker.h"
#include "base/process/process_metrics.h"
#include "base/trace_event/memory_allocator_dump.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "starboard/memory.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
class TestDiscardableSharedMemory : public DiscardableSharedMemory {
public:
TestDiscardableSharedMemory() = default;
explicit TestDiscardableSharedMemory(UnsafeSharedMemoryRegion region)
: DiscardableSharedMemory(std::move(region)) {}
void SetNow(Time now) { now_ = now; }
private:
// Overriden from DiscardableSharedMemory:
Time Now() const override { return now_; }
Time now_;
};
TEST(DiscardableSharedMemoryTest, CreateAndMap) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
EXPECT_GE(memory.mapped_size(), kDataSize);
EXPECT_TRUE(memory.IsMemoryLocked());
}
TEST(DiscardableSharedMemoryTest, CreateFromHandle) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
EXPECT_TRUE(memory2.IsMemoryLocked());
}
TEST(DiscardableSharedMemoryTest, LockAndUnlock) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
// Memory is initially locked. Unlock it.
memory1.SetNow(Time::FromDoubleT(1));
memory1.Unlock(0, 0);
EXPECT_FALSE(memory1.IsMemoryLocked());
// Lock and unlock memory.
DiscardableSharedMemory::LockResult lock_rv = memory1.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
memory1.SetNow(Time::FromDoubleT(2));
memory1.Unlock(0, 0);
// Lock again before duplicating and passing ownership to new instance.
lock_rv = memory1.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
EXPECT_TRUE(memory1.IsMemoryLocked());
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
// Unlock second instance.
memory2.SetNow(Time::FromDoubleT(3));
memory2.Unlock(0, 0);
// Both memory instances should be unlocked now.
EXPECT_FALSE(memory2.IsMemoryLocked());
EXPECT_FALSE(memory1.IsMemoryLocked());
// Lock second instance before passing ownership back to first instance.
lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
// Memory should still be resident and locked.
rv = memory1.IsMemoryResident();
EXPECT_TRUE(rv);
EXPECT_TRUE(memory1.IsMemoryLocked());
// Unlock first instance.
memory1.SetNow(Time::FromDoubleT(4));
memory1.Unlock(0, 0);
}
TEST(DiscardableSharedMemoryTest, Purge) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
// This should fail as memory is locked.
rv = memory1.Purge(Time::FromDoubleT(1));
EXPECT_FALSE(rv);
memory2.SetNow(Time::FromDoubleT(2));
memory2.Unlock(0, 0);
ASSERT_TRUE(memory2.IsMemoryResident());
// Memory is unlocked, but our usage timestamp is incorrect.
rv = memory1.Purge(Time::FromDoubleT(3));
EXPECT_FALSE(rv);
ASSERT_TRUE(memory2.IsMemoryResident());
// Memory is unlocked and our usage timestamp should be correct.
rv = memory1.Purge(Time::FromDoubleT(4));
EXPECT_TRUE(rv);
// Lock should fail as memory has been purged.
DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
ASSERT_FALSE(memory2.IsMemoryResident());
}
TEST(DiscardableSharedMemoryTest, LastUsed) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
memory2.SetNow(Time::FromDoubleT(1));
memory2.Unlock(0, 0);
EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(1));
DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
// This should fail as memory is locked.
rv = memory1.Purge(Time::FromDoubleT(2));
ASSERT_FALSE(rv);
// Last usage should have been updated to timestamp passed to Purge above.
EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2));
memory2.SetNow(Time::FromDoubleT(3));
memory2.Unlock(0, 0);
// Usage time should be correct for |memory2| instance.
EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(3));
// However, usage time has not changed as far as |memory1| instance knows.
EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2));
// Memory is unlocked, but our usage timestamp is incorrect.
rv = memory1.Purge(Time::FromDoubleT(4));
EXPECT_FALSE(rv);
// The failed purge attempt should have updated usage time to the correct
// value.
EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(3));
// Purge memory through |memory2| instance. The last usage time should be
// set to 0 as a result of this.
rv = memory2.Purge(Time::FromDoubleT(5));
EXPECT_TRUE(rv);
EXPECT_TRUE(memory2.last_known_usage().is_null());
// This should fail as memory has already been purged and |memory1|'s usage
// time is incorrect as a result.
rv = memory1.Purge(Time::FromDoubleT(6));
EXPECT_FALSE(rv);
// The failed purge attempt should have updated usage time to the correct
// value.
EXPECT_TRUE(memory1.last_known_usage().is_null());
// Purge should succeed now that usage time is correct.
rv = memory1.Purge(Time::FromDoubleT(7));
EXPECT_TRUE(rv);
}
TEST(DiscardableSharedMemoryTest, LockShouldAlwaysFailAfterSuccessfulPurge) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
memory2.SetNow(Time::FromDoubleT(1));
memory2.Unlock(0, 0);
rv = memory2.Purge(Time::FromDoubleT(2));
EXPECT_TRUE(rv);
// Lock should fail as memory has been purged.
DiscardableSharedMemory::LockResult lock_rv = memory2.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
}
#if defined(OS_ANDROID)
TEST(DiscardableSharedMemoryTest, LockShouldFailIfPlatformLockPagesFails) {
const uint32_t kDataSize = 1024;
DiscardableSharedMemory memory1;
bool rv1 = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv1);
base::UnsafeSharedMemoryRegion region = memory1.DuplicateRegion();
int fd = region.GetPlatformHandle();
DiscardableSharedMemory memory2(std::move(region));
bool rv2 = memory2.Map(kDataSize);
ASSERT_TRUE(rv2);
// Unlock() the first page of memory, so we can test Lock()ing it.
memory2.Unlock(0, base::GetPageSize());
// To cause ashmem_pin_region() to fail, we arrange for it to be called with
// an invalid file-descriptor, which requires a valid-looking fd (i.e. we
// can't just Close() |memory|), but one on which the operation is invalid.
// We can overwrite the |memory| fd with a handle to a different file using
// dup2(), which has the nice properties that |memory| still has a valid fd
// that it can close, etc without errors, but on which ashmem_pin_region()
// will fail.
base::ScopedFD null(open("/dev/null", O_RDONLY));
ASSERT_EQ(fd, dup2(null.get(), fd));
// Now re-Lock()ing the first page should fail.
DiscardableSharedMemory::LockResult lock_rv =
memory2.Lock(0, base::GetPageSize());
EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv);
}
#endif // defined(OS_ANDROID)
TEST(DiscardableSharedMemoryTest, LockAndUnlockRange) {
const uint32_t kDataSize = 32;
uint32_t data_size_in_bytes = kDataSize * base::GetPageSize();
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(data_size_in_bytes);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(data_size_in_bytes);
ASSERT_TRUE(rv);
// Unlock first page.
memory2.SetNow(Time::FromDoubleT(1));
memory2.Unlock(0, base::GetPageSize());
rv = memory1.Purge(Time::FromDoubleT(2));
EXPECT_FALSE(rv);
// Lock first page again.
memory2.SetNow(Time::FromDoubleT(3));
DiscardableSharedMemory::LockResult lock_rv =
memory2.Lock(0, base::GetPageSize());
EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv);
// Unlock first page.
memory2.SetNow(Time::FromDoubleT(4));
memory2.Unlock(0, base::GetPageSize());
rv = memory1.Purge(Time::FromDoubleT(5));
EXPECT_FALSE(rv);
// Unlock second page.
memory2.SetNow(Time::FromDoubleT(6));
memory2.Unlock(base::GetPageSize(), base::GetPageSize());
rv = memory1.Purge(Time::FromDoubleT(7));
EXPECT_FALSE(rv);
// Unlock anything onwards.
memory2.SetNow(Time::FromDoubleT(8));
memory2.Unlock(2 * base::GetPageSize(), 0);
// Memory is unlocked, but our usage timestamp is incorrect.
rv = memory1.Purge(Time::FromDoubleT(9));
EXPECT_FALSE(rv);
// The failed purge attempt should have updated usage time to the correct
// value.
EXPECT_EQ(Time::FromDoubleT(8), memory1.last_known_usage());
// Purge should now succeed.
rv = memory1.Purge(Time::FromDoubleT(10));
EXPECT_TRUE(rv);
}
TEST(DiscardableSharedMemoryTest, MappedSize) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
EXPECT_LE(kDataSize, memory.mapped_size());
// Mapped size should be 0 after memory segment has been unmapped.
rv = memory.Unmap();
EXPECT_TRUE(rv);
EXPECT_EQ(0u, memory.mapped_size());
}
TEST(DiscardableSharedMemoryTest, Close) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
// Mapped size should be unchanged after memory segment has been closed.
memory.Close();
EXPECT_LE(kDataSize, memory.mapped_size());
// Memory is initially locked. Unlock it.
memory.SetNow(Time::FromDoubleT(1));
memory.Unlock(0, 0);
// Lock and unlock memory.
DiscardableSharedMemory::LockResult lock_rv = memory.Lock(0, 0);
EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv);
memory.SetNow(Time::FromDoubleT(2));
memory.Unlock(0, 0);
}
TEST(DiscardableSharedMemoryTest, ZeroSize) {
TestDiscardableSharedMemory memory;
bool rv = memory.CreateAndMap(0);
ASSERT_TRUE(rv);
EXPECT_LE(0u, memory.mapped_size());
// Memory is initially locked. Unlock it.
memory.SetNow(Time::FromDoubleT(1));
memory.Unlock(0, 0);
// Lock and unlock memory.
DiscardableSharedMemory::LockResult lock_rv = memory.Lock(0, 0);
EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv);
memory.SetNow(Time::FromDoubleT(2));
memory.Unlock(0, 0);
}
// This test checks that zero-filled pages are returned after purging a segment
// when DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE is
// defined and MADV_REMOVE is supported.
#if defined(DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE)
TEST(DiscardableSharedMemoryTest, ZeroFilledPagesAfterPurge) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
UnsafeSharedMemoryRegion shared_region = memory1.DuplicateRegion();
ASSERT_TRUE(shared_region.IsValid());
TestDiscardableSharedMemory memory2(std::move(shared_region));
rv = memory2.Map(kDataSize);
ASSERT_TRUE(rv);
// Initialize all memory to '0xaa'.
memset(memory2.memory(), 0xaa, kDataSize);
// Unlock memory.
memory2.SetNow(Time::FromDoubleT(1));
memory2.Unlock(0, 0);
EXPECT_FALSE(memory1.IsMemoryLocked());
// Memory is unlocked, but our usage timestamp is incorrect.
rv = memory1.Purge(Time::FromDoubleT(2));
EXPECT_FALSE(rv);
rv = memory1.Purge(Time::FromDoubleT(3));
EXPECT_TRUE(rv);
// Check that reading memory after it has been purged is returning
// zero-filled pages.
uint8_t expected_data[kDataSize] = {};
EXPECT_EQ(memcmp(memory2.memory(), expected_data, kDataSize), 0);
}
#endif
TEST(DiscardableSharedMemoryTest, TracingOwnershipEdges) {
const uint32_t kDataSize = 1024;
TestDiscardableSharedMemory memory1;
bool rv = memory1.CreateAndMap(kDataSize);
ASSERT_TRUE(rv);
base::trace_event::MemoryDumpArgs args = {
base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
trace_event::ProcessMemoryDump pmd(args);
trace_event::MemoryAllocatorDump* client_dump =
pmd.CreateAllocatorDump("discardable_manager/map1");
const bool is_owned = false;
memory1.CreateSharedMemoryOwnershipEdge(client_dump, &pmd, is_owned);
const auto* shm_dump = pmd.GetAllocatorDump(
SharedMemoryTracker::GetDumpNameForTracing(memory1.mapped_id()));
EXPECT_TRUE(shm_dump);
EXPECT_EQ(shm_dump->GetSizeInternal(), client_dump->GetSizeInternal());
const auto edges = pmd.allocator_dumps_edges();
EXPECT_EQ(2u, edges.size());
EXPECT_NE(edges.end(), edges.find(shm_dump->guid()));
EXPECT_NE(edges.end(), edges.find(client_dump->guid()));
// TODO(ssid): test for weak global dump once the
// CreateWeakSharedMemoryOwnershipEdge() is fixed, crbug.com/661257.
}
} // namespace base