| Index: media/audio/cross_process_notification_unittest.cc
|
| diff --git a/media/audio/cross_process_notification_unittest.cc b/media/audio/cross_process_notification_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c045416cd6cf405b74e319c26fd5310590d69b98
|
| --- /dev/null
|
| +++ b/media/audio/cross_process_notification_unittest.cc
|
| @@ -0,0 +1,460 @@
|
| +// 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/compiler_specific.h"
|
| +#include "base/logging.h"
|
| +#include "base/shared_memory.h"
|
| +#include "base/stl_util.h"
|
| +#include "base/test/multiprocess_test.h"
|
| +#include "base/threading/platform_thread.h"
|
| +#include "media/audio/cross_process_notification.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "testing/multiprocess_func_list.h"
|
| +
|
| +#if defined(OS_POSIX)
|
| +#include <utility> // NOLINT
|
| +#endif
|
| +
|
| +namespace {
|
| +
|
| +// Initializes (ctor) and deletes (dtor) two vectors of pairs of
|
| +// CrossProcessNotification instances.
|
| +class NotificationsOwner {
|
| + public:
|
| + // Attempts to create up to |number_of_pairs| number of pairs. Call size()
|
| + // after construction to find out how many pairs were actually created.
|
| + explicit NotificationsOwner(size_t number_of_pairs) {
|
| + CreateMultiplePairs(number_of_pairs);
|
| + }
|
| + ~NotificationsOwner() {
|
| + STLDeleteElements(&a_);
|
| + STLDeleteElements(&b_);
|
| + }
|
| +
|
| + size_t size() const {
|
| + DCHECK_EQ(a_.size(), b_.size());
|
| + return a_.size();
|
| + }
|
| +
|
| + const CrossProcessNotification::Notifications& a() { return a_; }
|
| + const CrossProcessNotification::Notifications& b() { return b_; }
|
| +
|
| + private:
|
| + void CreateMultiplePairs(size_t count) {
|
| + a_.resize(count);
|
| + b_.resize(count);
|
| + size_t i = 0;
|
| + for (; i < count; ++i) {
|
| + a_[i] = new CrossProcessNotification();
|
| + b_[i] = new CrossProcessNotification();
|
| + if (!CrossProcessNotification::InitializePair(a_[i], b_[i])) {
|
| + LOG(WARNING) << "InitializePair failed at " << i;
|
| + delete a_[i];
|
| + delete b_[i];
|
| + break;
|
| + }
|
| + }
|
| + a_.resize(i);
|
| + b_.resize(i);
|
| + }
|
| +
|
| + CrossProcessNotification::Notifications a_;
|
| + CrossProcessNotification::Notifications b_;
|
| +};
|
| +
|
| +// A simple thread that we'll run two instances of. Both threads get a pointer
|
| +// to the same |shared_data| and use a CrossProcessNotification to control when
|
| +// each thread can read/write.
|
| +class SingleNotifierWorker : public base::PlatformThread::Delegate {
|
| + public:
|
| + SingleNotifierWorker(size_t* shared_data, size_t repeats,
|
| + CrossProcessNotification* notifier)
|
| + : shared_data_(shared_data), repeats_(repeats),
|
| + notifier_(notifier) {
|
| + }
|
| + virtual ~SingleNotifierWorker() {}
|
| +
|
| + virtual void ThreadMain() OVERRIDE {
|
| + for (size_t i = 0; i < repeats_; ++i) {
|
| + notifier_->Wait();
|
| + ++(*shared_data_);
|
| + notifier_->Signal();
|
| + }
|
| + }
|
| +
|
| + private:
|
| + size_t* shared_data_;
|
| + size_t repeats_;
|
| + CrossProcessNotification* notifier_;
|
| + DISALLOW_COPY_AND_ASSIGN(SingleNotifierWorker);
|
| +};
|
| +
|
| +// Similar to SingleNotifierWorker, except each instance of this class will
|
| +// have >1 instances of CrossProcessNotification to Wait/Signal and an equal
|
| +// amount of |shared_data| that the notifiers control access to.
|
| +class MultiNotifierWorker : public base::PlatformThread::Delegate {
|
| + public:
|
| + MultiNotifierWorker(size_t* shared_data, size_t repeats,
|
| + const CrossProcessNotification::Notifications* notifiers)
|
| + : shared_data_(shared_data), repeats_(repeats),
|
| + notifiers_(notifiers) {
|
| + }
|
| + virtual ~MultiNotifierWorker() {}
|
| +
|
| + virtual void ThreadMain() OVERRIDE {
|
| + CrossProcessNotification::WaitForMultiple waiter(notifiers_);
|
| + for (size_t i = 0; i < repeats_; ++i) {
|
| + int signaled = waiter.Wait();
|
| + ++shared_data_[signaled];
|
| + (*notifiers_)[signaled]->Signal();
|
| + }
|
| + }
|
| +
|
| + private:
|
| + size_t* shared_data_;
|
| + size_t repeats_;
|
| + const CrossProcessNotification::Notifications* notifiers_;
|
| + size_t count_;
|
| + DISALLOW_COPY_AND_ASSIGN(MultiNotifierWorker);
|
| +};
|
| +
|
| +// A fixed array of bool flags. Each flag uses 1 bit. Use sizeof(FlagArray)
|
| +// to determine how much memory you need. The number of flags will therefore
|
| +// be sizeof(FlagArray) * 8.
|
| +// We use 'struct' to signify that this structures represents compiler
|
| +// independent structured data. I.e. you must be able to map this class
|
| +// to a piece of shared memory of size sizeof(FlagArray) and be able to
|
| +// use the class. No vtables etc.
|
| +// TODO(tommi): Move this to its own header when we start using it for signaling
|
| +// audio devices. As is, it's just here for perf comparison against the
|
| +// "multiple notifiers" approach.
|
| +struct FlagArray {
|
| + public:
|
| + FlagArray() : flags_() {}
|
| +
|
| + bool is_set(size_t index) const {
|
| + return (flags_[index >> 5] & (1 << (index & 31))) ? true : false;
|
| + }
|
| +
|
| + void set(size_t index) {
|
| + flags_[index >> 5] |= (1U << (static_cast<uint32>(index) & 31));
|
| + }
|
| +
|
| + void clear(size_t index) {
|
| + flags_[index >> 5] &= ~(1U << (static_cast<uint32>(index) & 31));
|
| + }
|
| +
|
| + // Returns the number of flags that can be set/checked.
|
| + size_t size() const { return sizeof(flags_) * 8; }
|
| +
|
| + private:
|
| + // 256 * 32 = 8192 flags in 1KB.
|
| + uint32 flags_[256];
|
| + DISALLOW_COPY_AND_ASSIGN(FlagArray);
|
| +};
|
| +
|
| +class MultiNotifierWorkerFlagArray : public base::PlatformThread::Delegate {
|
| + public:
|
| + MultiNotifierWorkerFlagArray(size_t count, FlagArray* signals,
|
| + size_t* shared_data, size_t repeats,
|
| + CrossProcessNotification* notifier)
|
| + : count_(count), signals_(signals), shared_data_(shared_data),
|
| + repeats_(repeats), notifier_(notifier) {
|
| + }
|
| + virtual ~MultiNotifierWorkerFlagArray() {}
|
| +
|
| + virtual void ThreadMain() OVERRIDE {
|
| + for (size_t i = 0; i < repeats_; ++i) {
|
| + notifier_->Wait();
|
| + for (size_t s = 0; s < count_; ++s) {
|
| + if (signals_->is_set(s)) {
|
| + ++shared_data_[s];
|
| + // We don't clear the flag here but simply leave it signaled because
|
| + // we want the other thread to also increment this variable.
|
| + }
|
| + }
|
| + notifier_->Signal();
|
| + }
|
| + }
|
| +
|
| + private:
|
| + size_t count_;
|
| + FlagArray* signals_;
|
| + size_t* shared_data_;
|
| + size_t repeats_;
|
| + CrossProcessNotification* notifier_;
|
| + DISALLOW_COPY_AND_ASSIGN(MultiNotifierWorkerFlagArray);
|
| +};
|
| +
|
| +} // end namespace
|
| +
|
| +TEST(CrossProcessNotification, FlagArray) {
|
| + FlagArray flags;
|
| + EXPECT_GT(flags.size(), 1000U);
|
| + for (size_t i = 0; i < flags.size(); ++i) {
|
| + EXPECT_FALSE(flags.is_set(i));
|
| + flags.set(i);
|
| + EXPECT_TRUE(flags.is_set(i));
|
| + flags.clear(i);
|
| + EXPECT_FALSE(flags.is_set(i));
|
| + }
|
| +}
|
| +
|
| +// Initializes two notifiers, signals the each one and make sure the others
|
| +// wait is satisfied.
|
| +TEST(CrossProcessNotification, Basic) {
|
| + CrossProcessNotification a, b;
|
| + ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
|
| + EXPECT_TRUE(a.IsValid());
|
| + EXPECT_TRUE(b.IsValid());
|
| +
|
| + a.Signal();
|
| + b.Wait();
|
| +
|
| + b.Signal();
|
| + a.Wait();
|
| +}
|
| +
|
| +// Spins two worker threads, each with their own CrossProcessNotification
|
| +// that they use to read and write from a shared memory buffer.
|
| +TEST(CrossProcessNotification, TwoThreads) {
|
| + CrossProcessNotification a, b;
|
| + ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
|
| +
|
| + size_t data = 0;
|
| + const size_t kRepeats = 10000;
|
| + SingleNotifierWorker worker1(&data, kRepeats, &a);
|
| + SingleNotifierWorker worker2(&data, kRepeats, &b);
|
| + base::PlatformThreadHandle thread1, thread2;
|
| + base::PlatformThread::Create(0, &worker1, &thread1);
|
| + base::PlatformThread::Create(0, &worker2, &thread2);
|
| +
|
| + // Start the first thread. They should ping pong a few times and take turns
|
| + // incrementing the shared variable and never step on each other's toes.
|
| + a.Signal();
|
| +
|
| + base::PlatformThread::Join(thread1);
|
| + base::PlatformThread::Join(thread2);
|
| +
|
| + EXPECT_EQ(kRepeats * 2, data);
|
| +}
|
| +
|
| +// Uses a pair of threads to access up to 1000 pieces of synchronized shared
|
| +// data. On regular dev machines, the number of notifiers should be 1000, but on
|
| +// mac and linux bots, the number will be smaller due to the RLIMIT_NOFILE
|
| +// limit. Specifically, linux will have this limit at 1024 which means for this
|
| +// test that the max number of notifiers will be in the range 500-512. On Mac
|
| +// the limit is 256, so |count| will be ~120. Oh, and raising the limit via
|
| +// setrlimit() won't work.
|
| +// DISABLED since the distribution won't be accurate when run on valgrind.
|
| +TEST(CrossProcessNotification, DISABLED_ThousandNotifiersTwoThreads) {
|
| + const size_t kCount = 1000;
|
| + NotificationsOwner pairs(kCount);
|
| + size_t data[kCount] = {0};
|
| + // We use a multiple of the count so that the division in the check below
|
| + // will be nice and round.
|
| + size_t repeats = pairs.size() * 1;
|
| +
|
| + MultiNotifierWorker worker_1(&data[0], repeats, &pairs.a());
|
| + MultiNotifierWorker worker_2(&data[0], repeats, &pairs.b());
|
| + base::PlatformThreadHandle thread_1, thread_2;
|
| + base::PlatformThread::Create(0, &worker_1, &thread_1);
|
| + base::PlatformThread::Create(0, &worker_2, &thread_2);
|
| +
|
| + for (size_t i = 0; i < pairs.size(); ++i)
|
| + pairs.a()[i]->Signal();
|
| +
|
| + base::PlatformThread::Join(thread_1);
|
| + base::PlatformThread::Join(thread_2);
|
| +
|
| + size_t expected_total = pairs.size() * 2;
|
| + size_t total = 0;
|
| + for (size_t i = 0; i < pairs.size(); ++i) {
|
| + // The CrossProcessNotification::WaitForMultiple class should have ensured
|
| + // that all notifiers had the same quality of service.
|
| + EXPECT_EQ(expected_total / pairs.size(), data[i]);
|
| + total += data[i];
|
| + }
|
| + EXPECT_EQ(expected_total, total);
|
| +}
|
| +
|
| +// Functionally equivalent (as far as the shared data goes) to the
|
| +// ThousandNotifiersTwoThreads test but uses a single pair of notifiers +
|
| +// FlagArray for the 1000 signals. This approach is significantly faster.
|
| +TEST(CrossProcessNotification, TwoNotifiersTwoThreads1000Signals) {
|
| + CrossProcessNotification a, b;
|
| + ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
|
| +
|
| + const size_t kCount = 1000;
|
| + FlagArray signals;
|
| + ASSERT_GE(signals.size(), kCount);
|
| + size_t data[kCount] = {0};
|
| +
|
| + // Since this algorithm checks all events each time the notifier is
|
| + // signaled, |repeat| doesn't mean the same thing here as it does in
|
| + // ThousandNotifiersTwoThreads. 1 repeat here is the same as kCount
|
| + // repeats in ThousandNotifiersTwoThreads.
|
| + size_t repeats = 1;
|
| + MultiNotifierWorkerFlagArray worker1(kCount, &signals, &data[0], repeats, &a);
|
| + MultiNotifierWorkerFlagArray worker2(kCount, &signals, &data[0], repeats, &b);
|
| + base::PlatformThreadHandle thread1, thread2;
|
| + base::PlatformThread::Create(0, &worker1, &thread1);
|
| + base::PlatformThread::Create(0, &worker2, &thread2);
|
| +
|
| + for (size_t i = 0; i < kCount; ++i)
|
| + signals.set(i);
|
| + a.Signal();
|
| +
|
| + base::PlatformThread::Join(thread1);
|
| + base::PlatformThread::Join(thread2);
|
| +
|
| + size_t expected_total = kCount * 2;
|
| + size_t total = 0;
|
| + for (size_t i = 0; i < kCount; ++i) {
|
| + // Since for each signal, we process all signaled events, the shared data
|
| + // variables should all be equal.
|
| + EXPECT_EQ(expected_total / kCount, data[i]);
|
| + total += data[i];
|
| + }
|
| + EXPECT_EQ(expected_total, total);
|
| +}
|
| +
|
| +// Test the maximum number of notifiers without spinning further wait
|
| +// threads on Windows. This test assumes we can always create 64 pairs and
|
| +// bails if we can't.
|
| +TEST(CrossProcessNotification, MultipleWaits64) {
|
| + const size_t kCount = 64;
|
| + NotificationsOwner pairs(kCount);
|
| + ASSERT_TRUE(pairs.size() == kCount);
|
| +
|
| + CrossProcessNotification::WaitForMultiple waiter(&pairs.b());
|
| + for (size_t i = 0; i < kCount; ++i) {
|
| + pairs.a()[i]->Signal();
|
| + int index = waiter.Wait();
|
| + EXPECT_EQ(i, static_cast<size_t>(index));
|
| + }
|
| +}
|
| +
|
| +// Tests waiting for more notifiers than the OS supports on one thread.
|
| +// The test will create at most 1000 pairs, but on mac/linux bots the actual
|
| +// number will be lower. See comment about the RLIMIT_NOFILE limit above for
|
| +// more details.
|
| +// DISABLED since the distribution won't be accurate when run on valgrind.
|
| +TEST(CrossProcessNotification, DISABLED_MultipleWaits1000) {
|
| + // A 1000 notifiers requires 16 threads on Windows, including the current
|
| + // one, to perform the wait operation.
|
| + const size_t kCount = 1000;
|
| + NotificationsOwner pairs(kCount);
|
| +
|
| + for (size_t i = 0; i < pairs.size(); ++i) {
|
| + pairs.a()[i]->Signal();
|
| + // To disable the load distribution algorithm and force the extra worker
|
| + // thread(s) to catch the signaled event, we define the |waiter| inside
|
| + // the loop.
|
| + CrossProcessNotification::WaitForMultiple waiter(&pairs.b());
|
| + int index = waiter.Wait();
|
| + EXPECT_EQ(i, static_cast<size_t>(index));
|
| + }
|
| +}
|
| +
|
| +class CrossProcessNotificationMultiProcessTest : public base::MultiProcessTest {
|
| + public:
|
| + static const char kSharedMemName[];
|
| + static const size_t kSharedMemSize = 1024;
|
| +
|
| + protected:
|
| + virtual void SetUp() OVERRIDE {
|
| + base::MultiProcessTest::SetUp();
|
| + }
|
| +
|
| + virtual void TearDown() OVERRIDE {
|
| + base::MultiProcessTest::TearDown();
|
| + }
|
| +};
|
| +
|
| +// static
|
| +const char CrossProcessNotificationMultiProcessTest::kSharedMemName[] =
|
| + "CrossProcessNotificationMultiProcessTest";
|
| +
|
| +namespace {
|
| +// A very crude IPC mechanism that we use to set up the spawned child process
|
| +// and the parent process.
|
| +struct CrudeIpc {
|
| + uint8 ready;
|
| + CrossProcessNotification::IPCHandle handle_1;
|
| + CrossProcessNotification::IPCHandle handle_2;
|
| +};
|
| +} // end namespace
|
| +
|
| +// The main routine of the child process. Waits for the parent process
|
| +// to copy handles over to the child and then uses a CrossProcessNotification to
|
| +// wait and signal to the parent process.
|
| +MULTIPROCESS_TEST_MAIN(CrossProcessNotificationChildMain) {
|
| + base::SharedMemory mem;
|
| + bool ok = mem.CreateNamed(
|
| + CrossProcessNotificationMultiProcessTest::kSharedMemName,
|
| + true,
|
| + CrossProcessNotificationMultiProcessTest::kSharedMemSize);
|
| + DCHECK(ok);
|
| + if (!ok) {
|
| + LOG(ERROR) << "Failed to open shared memory segment.";
|
| + return -1;
|
| + }
|
| +
|
| + mem.Map(CrossProcessNotificationMultiProcessTest::kSharedMemSize);
|
| + CrudeIpc* ipc = reinterpret_cast<CrudeIpc*>(mem.memory());
|
| +
|
| + while (!ipc->ready)
|
| + base::PlatformThread::Sleep(10);
|
| +
|
| + CrossProcessNotification notifier(ipc->handle_1, ipc->handle_2);
|
| + notifier.Wait();
|
| + notifier.Signal();
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +// Spawns a new process and hands a CrossProcessNotification instance to the
|
| +// new process. Once that's done, it waits for the child process to signal
|
| +// it's end and quits.
|
| +TEST_F(CrossProcessNotificationMultiProcessTest, Basic) {
|
| + base::SharedMemory mem;
|
| + mem.Delete(kSharedMemName); // In case a previous run was unsuccessful.
|
| + bool ok = mem.CreateNamed(kSharedMemName, false, kSharedMemSize);
|
| + ASSERT_TRUE(ok);
|
| +
|
| + ASSERT_TRUE(mem.Map(kSharedMemSize));
|
| +
|
| + CrossProcessNotification a, b;
|
| + ASSERT_TRUE(CrossProcessNotification::InitializePair(&a, &b));
|
| + EXPECT_TRUE(a.IsValid());
|
| + EXPECT_TRUE(b.IsValid());
|
| +
|
| + CrudeIpc* ipc = reinterpret_cast<CrudeIpc*>(mem.memory());
|
| + ipc->ready = false;
|
| +
|
| +#if defined(OS_POSIX)
|
| + const int kPosixChildSocket = 20;
|
| + EXPECT_TRUE(b.ShareToProcess(NULL, &ipc->handle_1, &ipc->handle_2));
|
| + base::FileHandleMappingVector fd_mapping_vec;
|
| + fd_mapping_vec.push_back(std::pair<int, int>(ipc->handle_1.fd,
|
| + kPosixChildSocket));
|
| + ipc->handle_1.fd = kPosixChildSocket;
|
| + base::ProcessHandle process = SpawnChild("CrossProcessNotificationChildMain",
|
| + fd_mapping_vec, false);
|
| +#else
|
| + base::ProcessHandle process = SpawnChild("CrossProcessNotificationChildMain",
|
| + false);
|
| + EXPECT_TRUE(b.ShareToProcess(process, &ipc->handle_1, &ipc->handle_2));
|
| +#endif
|
| +
|
| + ipc->ready = true;
|
| +
|
| + a.Signal();
|
| + a.Wait();
|
| +
|
| + int exit_code = -1;
|
| + base::WaitForExitCode(process, &exit_code);
|
| + EXPECT_EQ(0, exit_code);
|
| +}
|
|
|