diff --git a/media/base/closure_thread.cc b/media/base/closure_thread.cc new file mode 100644 index 0000000000..e737a46fcd --- /dev/null +++ b/media/base/closure_thread.cc @@ -0,0 +1,23 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "media/base/closure_thread.h" + +namespace media { + +ClosureThread::ClosureThread( + const std::string& name_prefix, + const base::Closure& task) + : base::SimpleThread(name_prefix), task_(task) {} + +ClosureThread::~ClosureThread() { + if (!HasBeenJoined()) + Join(); +} + +void ClosureThread::Run() { task_.Run(); } + +} // namespace media diff --git a/media/base/closure_thread.h b/media/base/closure_thread.h new file mode 100644 index 0000000000..0b45e5e980 --- /dev/null +++ b/media/base/closure_thread.h @@ -0,0 +1,47 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_BASE_CLOSURE_THREAD_H_ +#define MEDIA_BASE_CLOSURE_THREAD_H_ + +#include "base/callback.h" +#include "base/threading/simple_thread.h" + +namespace media { + +/// Class for creating a thread which invokes a closure. +/// Start() starts the thread and invokes the given closure inside the thread. +/// +/// NOTE: It is invalid to destroy a ClosureThread without Start() having been +/// called (and a thread never created). +/// +/// Thread Safety: A ClosureThread is not completely thread safe. It is safe to +/// access it from the creating thread or from the newly created thread. This +/// implies that the creator thread should be the thread that calls Join. +class ClosureThread : public base::SimpleThread { + public: + /// Create a ClosureThread. The thread will not be created until Start() is + /// called. + /// @param name_prefix is the thread name prefix. Every thread has a name, + /// in the form of @a name_prefix/TID, for example "my_thread/321". + /// @param task is the Closure to run in the thread. + explicit ClosureThread(const std::string& name_prefix, + const base::Closure& task); + + /// The destructor calls Join automatically if it is not yet joined. + virtual ~ClosureThread(); + + protected: + /// SimpleThread implementation overrides. + virtual void Run() OVERRIDE; + + private: + const base::Closure task_; +}; + +} // namespace media + +#endif // MEDIA_BASE_CLOSURE_THREAD_H_ diff --git a/media/base/closure_thread_unittest.cc b/media/base/closure_thread_unittest.cc new file mode 100644 index 0000000000..73622418cb --- /dev/null +++ b/media/base/closure_thread_unittest.cc @@ -0,0 +1,120 @@ +// Copyright 2014 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "media/base/closure_thread.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Return; + +namespace { + +const char kThreadNamePrefix[] = "TestClosureThread"; + +// Mock operation for testing. +class MockOperation { + public: + MOCK_METHOD0(DoSomething, bool()); +}; + +} // namespace + +namespace media { + +class ClosureThreadTest : public ::testing::Test { + public: + ClosureThreadTest() + : thread_( + new ClosureThread(kThreadNamePrefix, + base::Bind(&ClosureThreadTest::ClosureCallback, + base::Unretained(this)))), + val_(0) {} + + virtual ~ClosureThreadTest() {} + + void ClosureCallback() { + // Exit the loop if DoSomething return false. + while (operation_.DoSomething()) + continue; + } + + void IncrementVal() { ++val_; } + + protected: + int val() { return val_; } + void set_val(int val) { val_ = val; } + + MockOperation operation_; + scoped_ptr thread_; + + private: + int val_; + + DISALLOW_COPY_AND_ASSIGN(ClosureThreadTest); +}; + +TEST_F(ClosureThreadTest, Basic) { + EXPECT_CALL(operation_, DoSomething()).WillOnce(Return(false)); + + ASSERT_EQ(kThreadNamePrefix, thread_->name_prefix()); + ASSERT_FALSE(thread_->HasBeenStarted()); + thread_->Start(); + ASSERT_TRUE(thread_->HasBeenStarted()); + thread_->Join(); +} + +TEST_F(ClosureThreadTest, CheckInteraction) { + base::WaitableEvent event_in_thread(true, false); + base::WaitableEvent event_in_main(true, false); + set_val(8); + + // Expect the operation to be invoked twice: + // 1) When invoked for the first time, increment the value then wait + // for the signal from main event and return true to continue; + // 2) When invoked for the second time, increment the value again and + // return true to quit the closure loop. + EXPECT_CALL(operation_, DoSomething()) + .WillOnce(DoAll(Invoke(this, &ClosureThreadTest::IncrementVal), + Invoke(&event_in_main, &base::WaitableEvent::Signal), + Invoke(&event_in_thread, &base::WaitableEvent::Wait), + Return(true))) + .WillOnce(DoAll(Invoke(this, &ClosureThreadTest::IncrementVal), + Return(false))); + + thread_->Start(); + + // Wait until |thread_| signals the main thread. + event_in_main.Wait(); + EXPECT_EQ(9, val()); + + // Signal |thread_| to continue. + event_in_thread.Signal(); + thread_->Join(); + EXPECT_EQ(10, val()); +} + +TEST_F(ClosureThreadTest, NotJoined) { + EXPECT_CALL(operation_, DoSomething()).WillOnce(Return(false)); + + thread_->Start(); + // Destroy the thread. The thread should be joined automatically. + thread_.reset(); +} + +// Expect death if the thread is destroyed without being started. +TEST_F(ClosureThreadTest, NotStarted) { + ASSERT_FALSE(thread_->HasBeenStarted()); + ClosureThread* thread = thread_.release(); + EXPECT_DEBUG_DEATH(delete thread, ".*Check failed: HasBeenStarted.*"); +} + +} // namespace media diff --git a/media/base/media_base.gyp b/media/base/media_base.gyp index 1af7470254..2a21b3f172 100644 --- a/media/base/media_base.gyp +++ b/media/base/media_base.gyp @@ -63,6 +63,8 @@ 'buffer_writer.h', 'byte_queue.cc', 'byte_queue.h', + 'closure_thread.cc', + 'closure_thread.h', 'container_names.cc', 'container_names.h', 'demuxer.cc', @@ -110,6 +112,7 @@ 'aes_encryptor_unittest.cc', 'bit_reader_unittest.cc', 'buffer_writer_unittest.cc', + 'closure_thread_unittest.cc', 'container_names_unittest.cc', 'fake_prng.cc', # For rsa_key_unittest 'fake_prng.h', # For rsa_key_unittest