// Copyright (c) 2012 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 "base/basictypes.h" #if defined(OS_MACOSX) #include "base/mac/scoped_nsautorelease_pool.h" #endif #include "base/memory/scoped_ptr.h" #include "base/memory/shared_memory.h" #include "base/sys_info.h" #include "base/test/multiprocess_test.h" #include "base/threading/platform_thread.h" #include "base/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" #if defined(OS_MACOSX) #include "base/mac/scoped_nsautorelease_pool.h" #endif #if defined(OS_POSIX) #include #endif static const int kNumThreads = 5; static const int kNumTasks = 5; namespace base { namespace { // Each thread will open the shared memory. Each thread will take a different 4 // byte int pointer, and keep changing it, with some small pauses in between. // Verify that each thread's value in the shared memory is always correct. class MultipleThreadMain : public PlatformThread::Delegate { public: explicit MultipleThreadMain(int16 id) : id_(id) {} virtual ~MultipleThreadMain() {} static void CleanUp() { SharedMemory memory; memory.Delete(s_test_name_); } // PlatformThread::Delegate interface. virtual void ThreadMain() OVERRIDE { #if defined(OS_MACOSX) mac::ScopedNSAutoreleasePool pool; #endif const uint32 kDataSize = 1024; SharedMemory memory; bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); EXPECT_TRUE(rv); rv = memory.Map(kDataSize); EXPECT_TRUE(rv); int *ptr = static_cast(memory.memory()) + id_; EXPECT_EQ(0, *ptr); for (int idx = 0; idx < 100; idx++) { *ptr = idx; PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); EXPECT_EQ(*ptr, idx); } // Reset back to 0 for the next test that uses the same name. *ptr = 0; memory.Close(); } private: int16 id_; static const char* const s_test_name_; DISALLOW_COPY_AND_ASSIGN(MultipleThreadMain); }; const char* const MultipleThreadMain::s_test_name_ = "SharedMemoryOpenThreadTest"; // TODO(port): // This test requires the ability to pass file descriptors between processes. // We haven't done that yet in Chrome for POSIX. #if defined(OS_WIN) // Each thread will open the shared memory. Each thread will take the memory, // and keep changing it while trying to lock it, with some small pauses in // between. Verify that each thread's value in the shared memory is always // correct. class MultipleLockThread : public PlatformThread::Delegate { public: explicit MultipleLockThread(int id) : id_(id) {} virtual ~MultipleLockThread() {} // PlatformThread::Delegate interface. virtual void ThreadMain() OVERRIDE { const uint32 kDataSize = sizeof(int); SharedMemoryHandle handle = NULL; { SharedMemory memory1; EXPECT_TRUE(memory1.CreateNamed("SharedMemoryMultipleLockThreadTest", true, kDataSize)); EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); // TODO(paulg): Implement this once we have a posix version of // SharedMemory::ShareToProcess. EXPECT_TRUE(true); } SharedMemory memory2(handle, false); EXPECT_TRUE(memory2.Map(kDataSize)); volatile int* const ptr = static_cast(memory2.memory()); for (int idx = 0; idx < 20; idx++) { memory2.Lock(); int i = (id_ << 16) + idx; *ptr = i; PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); EXPECT_EQ(*ptr, i); memory2.Unlock(); } memory2.Close(); } private: int id_; DISALLOW_COPY_AND_ASSIGN(MultipleLockThread); }; #endif } // namespace // Android doesn't support SharedMemory::Open/Delete/ // CreateNamed(openExisting=true) #if !defined(OS_ANDROID) TEST(SharedMemoryTest, OpenClose) { const uint32 kDataSize = 1024; std::string test_name = "SharedMemoryOpenCloseTest"; // Open two handles to a memory segment, confirm that they are mapped // separately yet point to the same space. SharedMemory memory1; bool rv = memory1.Delete(test_name); EXPECT_TRUE(rv); rv = memory1.Delete(test_name); EXPECT_TRUE(rv); rv = memory1.Open(test_name, false); EXPECT_FALSE(rv); rv = memory1.CreateNamed(test_name, false, kDataSize); EXPECT_TRUE(rv); rv = memory1.Map(kDataSize); EXPECT_TRUE(rv); SharedMemory memory2; rv = memory2.Open(test_name, false); EXPECT_TRUE(rv); rv = memory2.Map(kDataSize); EXPECT_TRUE(rv); EXPECT_NE(memory1.memory(), memory2.memory()); // Compare the pointers. // Make sure we don't segfault. (it actually happened!) ASSERT_NE(memory1.memory(), static_cast(NULL)); ASSERT_NE(memory2.memory(), static_cast(NULL)); // Write data to the first memory segment, verify contents of second. memset(memory1.memory(), '1', kDataSize); EXPECT_EQ(memcmp(memory1.memory(), memory2.memory(), kDataSize), 0); // Close the first memory segment, and verify the second has the right data. memory1.Close(); char *start_ptr = static_cast(memory2.memory()); char *end_ptr = start_ptr + kDataSize; for (char* ptr = start_ptr; ptr < end_ptr; ptr++) EXPECT_EQ(*ptr, '1'); // Close the second memory segment. memory2.Close(); rv = memory1.Delete(test_name); EXPECT_TRUE(rv); rv = memory2.Delete(test_name); EXPECT_TRUE(rv); } TEST(SharedMemoryTest, OpenExclusive) { const uint32 kDataSize = 1024; const uint32 kDataSize2 = 2048; std::ostringstream test_name_stream; test_name_stream << "SharedMemoryOpenExclusiveTest." << Time::Now().ToDoubleT(); std::string test_name = test_name_stream.str(); // Open two handles to a memory segment and check that open_existing works // as expected. SharedMemory memory1; bool rv = memory1.CreateNamed(test_name, false, kDataSize); EXPECT_TRUE(rv); // Memory1 knows it's size because it created it. EXPECT_EQ(memory1.requested_size(), kDataSize); rv = memory1.Map(kDataSize); EXPECT_TRUE(rv); // The mapped memory1 must be at least the size we asked for. EXPECT_GE(memory1.mapped_size(), kDataSize); // The mapped memory1 shouldn't exceed rounding for allocation granularity. EXPECT_LT(memory1.mapped_size(), kDataSize + base::SysInfo::VMAllocationGranularity()); memset(memory1.memory(), 'G', kDataSize); SharedMemory memory2; // Should not be able to create if openExisting is false. rv = memory2.CreateNamed(test_name, false, kDataSize2); EXPECT_FALSE(rv); // Should be able to create with openExisting true. rv = memory2.CreateNamed(test_name, true, kDataSize2); EXPECT_TRUE(rv); // Memory2 shouldn't know the size because we didn't create it. EXPECT_EQ(memory2.requested_size(), 0U); // We should be able to map the original size. rv = memory2.Map(kDataSize); EXPECT_TRUE(rv); // The mapped memory2 must be at least the size of the original. EXPECT_GE(memory2.mapped_size(), kDataSize); // The mapped memory2 shouldn't exceed rounding for allocation granularity. EXPECT_LT(memory2.mapped_size(), kDataSize2 + base::SysInfo::VMAllocationGranularity()); // Verify that opening memory2 didn't truncate or delete memory 1. char *start_ptr = static_cast(memory2.memory()); char *end_ptr = start_ptr + kDataSize; for (char* ptr = start_ptr; ptr < end_ptr; ptr++) { EXPECT_EQ(*ptr, 'G'); } memory1.Close(); memory2.Close(); rv = memory1.Delete(test_name); EXPECT_TRUE(rv); } #endif // Create a set of N threads to each open a shared memory segment and write to // it. Verify that they are always reading/writing consistent data. TEST(SharedMemoryTest, MultipleThreads) { MultipleThreadMain::CleanUp(); // On POSIX we have a problem when 2 threads try to create the shmem // (a file) at exactly the same time, since create both creates the // file and zerofills it. We solve the problem for this unit test // (make it not flaky) by starting with 1 thread, then // intentionally don't clean up its shmem before running with // kNumThreads. int threadcounts[] = { 1, kNumThreads }; for (size_t i = 0; i < arraysize(threadcounts); i++) { int numthreads = threadcounts[i]; scoped_ptr thread_handles; scoped_ptr thread_delegates; thread_handles.reset(new PlatformThreadHandle[numthreads]); thread_delegates.reset(new MultipleThreadMain*[numthreads]); // Spawn the threads. for (int16 index = 0; index < numthreads; index++) { PlatformThreadHandle pth; thread_delegates[index] = new MultipleThreadMain(index); EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); thread_handles[index] = pth; } // Wait for the threads to finish. for (int index = 0; index < numthreads; index++) { PlatformThread::Join(thread_handles[index]); delete thread_delegates[index]; } } MultipleThreadMain::CleanUp(); } // TODO(port): this test requires the MultipleLockThread class // (defined above), which requires the ability to pass file // descriptors between processes. We haven't done that yet in Chrome // for POSIX. #if defined(OS_WIN) // Create a set of threads to each open a shared memory segment and write to it // with the lock held. Verify that they are always reading/writing consistent // data. TEST(SharedMemoryTest, Lock) { PlatformThreadHandle thread_handles[kNumThreads]; MultipleLockThread* thread_delegates[kNumThreads]; // Spawn the threads. for (int index = 0; index < kNumThreads; ++index) { PlatformThreadHandle pth; thread_delegates[index] = new MultipleLockThread(index); EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); thread_handles[index] = pth; } // Wait for the threads to finish. for (int index = 0; index < kNumThreads; ++index) { PlatformThread::Join(thread_handles[index]); delete thread_delegates[index]; } } #endif // Allocate private (unique) shared memory with an empty string for a // name. Make sure several of them don't point to the same thing as // we might expect if the names are equal. TEST(SharedMemoryTest, AnonymousPrivate) { int i, j; int count = 4; bool rv; const uint32 kDataSize = 8192; scoped_ptr memories(new SharedMemory[count]); scoped_ptr pointers(new int*[count]); ASSERT_TRUE(memories.get()); ASSERT_TRUE(pointers.get()); for (i = 0; i < count; i++) { rv = memories[i].CreateAndMapAnonymous(kDataSize); EXPECT_TRUE(rv); int *ptr = static_cast(memories[i].memory()); EXPECT_TRUE(ptr); pointers[i] = ptr; } for (i = 0; i < count; i++) { // zero out the first int in each except for i; for that one, make it 100. for (j = 0; j < count; j++) { if (i == j) pointers[j][0] = 100; else pointers[j][0] = 0; } // make sure there is no bleeding of the 100 into the other pointers for (j = 0; j < count; j++) { if (i == j) EXPECT_EQ(100, pointers[j][0]); else EXPECT_EQ(0, pointers[j][0]); } } for (int i = 0; i < count; i++) { memories[i].Close(); } } TEST(SharedMemoryTest, MapAt) { ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32)); const size_t kCount = SysInfo::VMAllocationGranularity(); const size_t kDataSize = kCount * sizeof(uint32); SharedMemory memory; ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize)); ASSERT_TRUE(memory.Map(kDataSize)); uint32* ptr = static_cast(memory.memory()); ASSERT_NE(ptr, static_cast(NULL)); for (size_t i = 0; i < kCount; ++i) { ptr[i] = i; } memory.Unmap(); off_t offset = SysInfo::VMAllocationGranularity(); ASSERT_TRUE(memory.MapAt(offset, kDataSize - offset)); offset /= sizeof(uint32); ptr = static_cast(memory.memory()); ASSERT_NE(ptr, static_cast(NULL)); for (size_t i = offset; i < kCount; ++i) { EXPECT_EQ(ptr[i - offset], i); } } #if defined(OS_POSIX) // Create a shared memory object, mmap it, and mprotect it to PROT_EXEC. TEST(SharedMemoryTest, AnonymousExecutable) { const uint32 kTestSize = 1 << 16; SharedMemory shared_memory; SharedMemoryCreateOptions options; options.size = kTestSize; options.executable = true; EXPECT_TRUE(shared_memory.Create(options)); EXPECT_TRUE(shared_memory.Map(shared_memory.requested_size())); EXPECT_EQ(0, mprotect(shared_memory.memory(), shared_memory.requested_size(), PROT_READ | PROT_EXEC)); } #endif // Map() will return addresses which are aligned to the platform page size, this // varies from platform to platform though. Since we'd like to advertise a // minimum alignment that callers can count on, test for it here. TEST(SharedMemoryTest, MapMinimumAlignment) { static const int kDataSize = 8192; SharedMemory shared_memory; ASSERT_TRUE(shared_memory.CreateAndMapAnonymous(kDataSize)); EXPECT_EQ(0U, reinterpret_cast( shared_memory.memory()) & (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1)); shared_memory.Close(); } #if !defined(OS_IOS) // iOS does not allow multiple processes. // On POSIX it is especially important we test shmem across processes, // not just across threads. But the test is enabled on all platforms. class SharedMemoryProcessTest : public MultiProcessTest { public: static void CleanUp() { SharedMemory memory; memory.Delete(s_test_name_); } static int TaskTestMain() { int errors = 0; #if defined(OS_MACOSX) mac::ScopedNSAutoreleasePool pool; #endif const uint32 kDataSize = 1024; SharedMemory memory; bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); EXPECT_TRUE(rv); if (rv != true) errors++; rv = memory.Map(kDataSize); EXPECT_TRUE(rv); if (rv != true) errors++; int *ptr = static_cast(memory.memory()); for (int idx = 0; idx < 20; idx++) { memory.Lock(); int i = (1 << 16) + idx; *ptr = i; PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); if (*ptr != i) errors++; memory.Unlock(); } memory.Close(); return errors; } private: static const char* const s_test_name_; }; const char* const SharedMemoryProcessTest::s_test_name_ = "MPMem"; TEST_F(SharedMemoryProcessTest, Tasks) { SharedMemoryProcessTest::CleanUp(); ProcessHandle handles[kNumTasks]; for (int index = 0; index < kNumTasks; ++index) { handles[index] = SpawnChild("SharedMemoryTestMain", false); ASSERT_TRUE(handles[index]); } int exit_code = 0; for (int index = 0; index < kNumTasks; ++index) { EXPECT_TRUE(WaitForExitCode(handles[index], &exit_code)); EXPECT_EQ(0, exit_code); } SharedMemoryProcessTest::CleanUp(); } MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) { return SharedMemoryProcessTest::TaskTestMain(); } #endif // !OS_IOS } // namespace base