| // Copyright 2013 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 "mojo/system/dispatcher.h" |
| |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_vector.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/simple_thread.h" |
| #include "mojo/embedder/platform_shared_buffer.h" |
| #include "mojo/system/memory.h" |
| #include "mojo/system/waiter.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace mojo { |
| namespace system { |
| namespace { |
| |
| // Trivial subclass that makes the constructor public. |
| class TrivialDispatcher : public Dispatcher { |
| public: |
| TrivialDispatcher() {} |
| |
| virtual Type GetType() const OVERRIDE { return kTypeUnknown; } |
| |
| private: |
| friend class base::RefCountedThreadSafe<TrivialDispatcher>; |
| virtual ~TrivialDispatcher() {} |
| |
| virtual scoped_refptr<Dispatcher> |
| CreateEquivalentDispatcherAndCloseImplNoLock() OVERRIDE { |
| lock().AssertAcquired(); |
| return scoped_refptr<Dispatcher>(new TrivialDispatcher()); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(TrivialDispatcher); |
| }; |
| |
| TEST(DispatcherTest, Basic) { |
| scoped_refptr<Dispatcher> d(new TrivialDispatcher()); |
| |
| EXPECT_EQ(Dispatcher::kTypeUnknown, d->GetType()); |
| |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| d->WriteMessage( |
| NullUserPointer(), 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| d->ReadMessage(NullUserPointer(), |
| NullUserPointer(), |
| nullptr, |
| nullptr, |
| MOJO_WRITE_MESSAGE_FLAG_NONE)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->WriteData( |
| NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->BeginWriteData( |
| NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndWriteData(0)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->ReadData( |
| NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->BeginReadData( |
| NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndReadData(0)); |
| Waiter w; |
| w.Init(); |
| HandleSignalsState hss; |
| EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, |
| d->AddWaiter(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss)); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| // Okay to remove even if it wasn't added (or was already removed). |
| hss = HandleSignalsState(); |
| d->RemoveWaiter(&w, &hss); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| hss = HandleSignalsState(); |
| d->RemoveWaiter(&w, &hss); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| |
| EXPECT_EQ(MOJO_RESULT_OK, d->Close()); |
| |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| d->WriteMessage( |
| NullUserPointer(), 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| d->ReadMessage(NullUserPointer(), |
| NullUserPointer(), |
| nullptr, |
| nullptr, |
| MOJO_WRITE_MESSAGE_FLAG_NONE)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->WriteData( |
| NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->BeginWriteData( |
| NullUserPointer(), NullUserPointer(), MOJO_WRITE_DATA_FLAG_NONE)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndWriteData(0)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->ReadData( |
| NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE)); |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| d->BeginReadData( |
| NullUserPointer(), NullUserPointer(), MOJO_READ_DATA_FLAG_NONE)); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndReadData(0)); |
| hss = HandleSignalsState(); |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| d->AddWaiter(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss)); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| hss = HandleSignalsState(); |
| d->RemoveWaiter(&w, &hss); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| } |
| |
| class ThreadSafetyStressThread : public base::SimpleThread { |
| public: |
| enum DispatcherOp { |
| CLOSE = 0, |
| WRITE_MESSAGE, |
| READ_MESSAGE, |
| WRITE_DATA, |
| BEGIN_WRITE_DATA, |
| END_WRITE_DATA, |
| READ_DATA, |
| BEGIN_READ_DATA, |
| END_READ_DATA, |
| DUPLICATE_BUFFER_HANDLE, |
| MAP_BUFFER, |
| ADD_WAITER, |
| REMOVE_WAITER, |
| DISPATCHER_OP_COUNT |
| }; |
| |
| ThreadSafetyStressThread(base::WaitableEvent* event, |
| scoped_refptr<Dispatcher> dispatcher, |
| DispatcherOp op) |
| : base::SimpleThread("thread_safety_stress_thread"), |
| event_(event), |
| dispatcher_(dispatcher), |
| op_(op) { |
| CHECK_LE(0, op_); |
| CHECK_LT(op_, DISPATCHER_OP_COUNT); |
| } |
| |
| virtual ~ThreadSafetyStressThread() { Join(); } |
| |
| private: |
| virtual void Run() OVERRIDE { |
| event_->Wait(); |
| |
| waiter_.Init(); |
| switch (op_) { |
| case CLOSE: { |
| MojoResult r = dispatcher_->Close(); |
| EXPECT_TRUE(r == MOJO_RESULT_OK || r == MOJO_RESULT_INVALID_ARGUMENT) |
| << "Result: " << r; |
| break; |
| } |
| case WRITE_MESSAGE: |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->WriteMessage( |
| NullUserPointer(), 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE)); |
| break; |
| case READ_MESSAGE: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->ReadMessage(NullUserPointer(), |
| NullUserPointer(), |
| nullptr, |
| nullptr, |
| MOJO_WRITE_MESSAGE_FLAG_NONE)); |
| break; |
| case WRITE_DATA: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->WriteData(NullUserPointer(), |
| NullUserPointer(), |
| MOJO_WRITE_DATA_FLAG_NONE)); |
| break; |
| case BEGIN_WRITE_DATA: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->BeginWriteData(NullUserPointer(), |
| NullUserPointer(), |
| MOJO_WRITE_DATA_FLAG_NONE)); |
| break; |
| case END_WRITE_DATA: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher_->EndWriteData(0)); |
| break; |
| case READ_DATA: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->ReadData(NullUserPointer(), |
| NullUserPointer(), |
| MOJO_READ_DATA_FLAG_NONE)); |
| break; |
| case BEGIN_READ_DATA: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->BeginReadData(NullUserPointer(), |
| NullUserPointer(), |
| MOJO_READ_DATA_FLAG_NONE)); |
| break; |
| case END_READ_DATA: |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher_->EndReadData(0)); |
| break; |
| case DUPLICATE_BUFFER_HANDLE: { |
| scoped_refptr<Dispatcher> unused; |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->DuplicateBufferHandle(NullUserPointer(), &unused)); |
| break; |
| } |
| case MAP_BUFFER: { |
| scoped_ptr<embedder::PlatformSharedBufferMapping> unused; |
| EXPECT_EQ( |
| MOJO_RESULT_INVALID_ARGUMENT, |
| dispatcher_->MapBuffer(0u, 0u, MOJO_MAP_BUFFER_FLAG_NONE, &unused)); |
| break; |
| } |
| case ADD_WAITER: { |
| HandleSignalsState hss; |
| MojoResult r = |
| dispatcher_->AddWaiter(&waiter_, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss); |
| EXPECT_TRUE(r == MOJO_RESULT_FAILED_PRECONDITION || |
| r == MOJO_RESULT_INVALID_ARGUMENT); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| break; |
| } |
| case REMOVE_WAITER: { |
| HandleSignalsState hss; |
| dispatcher_->RemoveWaiter(&waiter_, &hss); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Always try to remove the waiter, in case we added it. |
| HandleSignalsState hss; |
| dispatcher_->RemoveWaiter(&waiter_, &hss); |
| EXPECT_EQ(0u, hss.satisfied_signals); |
| EXPECT_EQ(0u, hss.satisfiable_signals); |
| } |
| |
| base::WaitableEvent* const event_; |
| const scoped_refptr<Dispatcher> dispatcher_; |
| const DispatcherOp op_; |
| |
| Waiter waiter_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ThreadSafetyStressThread); |
| }; |
| |
| TEST(DispatcherTest, ThreadSafetyStress) { |
| static const size_t kRepeatCount = 20; |
| static const size_t kNumThreads = 100; |
| |
| for (size_t i = 0; i < kRepeatCount; i++) { |
| // Manual reset, not initially signalled. |
| base::WaitableEvent event(true, false); |
| scoped_refptr<Dispatcher> d(new TrivialDispatcher()); |
| |
| { |
| ScopedVector<ThreadSafetyStressThread> threads; |
| for (size_t j = 0; j < kNumThreads; j++) { |
| ThreadSafetyStressThread::DispatcherOp op = |
| static_cast<ThreadSafetyStressThread::DispatcherOp>( |
| (i + j) % ThreadSafetyStressThread::DISPATCHER_OP_COUNT); |
| threads.push_back(new ThreadSafetyStressThread(&event, d, op)); |
| threads.back()->Start(); |
| } |
| // Kicks off real work on the threads: |
| event.Signal(); |
| } // Joins all the threads. |
| |
| // One of the threads should already have closed the dispatcher. |
| EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->Close()); |
| } |
| } |
| |
| TEST(DispatcherTest, ThreadSafetyStressNoClose) { |
| static const size_t kRepeatCount = 20; |
| static const size_t kNumThreads = 100; |
| |
| for (size_t i = 0; i < kRepeatCount; i++) { |
| // Manual reset, not initially signalled. |
| base::WaitableEvent event(true, false); |
| scoped_refptr<Dispatcher> d(new TrivialDispatcher()); |
| |
| { |
| ScopedVector<ThreadSafetyStressThread> threads; |
| for (size_t j = 0; j < kNumThreads; j++) { |
| ThreadSafetyStressThread::DispatcherOp op = |
| static_cast<ThreadSafetyStressThread::DispatcherOp>( |
| (i + j) % (ThreadSafetyStressThread::DISPATCHER_OP_COUNT - 1) + |
| 1); |
| threads.push_back(new ThreadSafetyStressThread(&event, d, op)); |
| threads.back()->Start(); |
| } |
| // Kicks off real work on the threads: |
| event.Signal(); |
| } // Joins all the threads. |
| |
| EXPECT_EQ(MOJO_RESULT_OK, d->Close()); |
| } |
| } |
| |
| } // namespace |
| } // namespace system |
| } // namespace mojo |