summaryrefslogtreecommitdiffstats
path: root/remoting/base
diff options
context:
space:
mode:
authorwez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-10 03:56:12 +0000
committerwez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-10 03:56:12 +0000
commitb35254d80932c7a1be75e2a6f271219929a8a3be (patch)
treef2b7065cb8499e088c73e56aafc091e461139f31 /remoting/base
parent42eadc92b2656fe21c3228f4432d28ddac36c6bd (diff)
downloadchromium_src-b35254d80932c7a1be75e2a6f271219929a8a3be.zip
chromium_src-b35254d80932c7a1be75e2a6f271219929a8a3be.tar.gz
chromium_src-b35254d80932c7a1be75e2a6f271219929a8a3be.tar.bz2
Initial implementation of AutoThread.
AutoThreads work much like base::Threads with the difference that the AutoThread will run until the last reference to its TaskRunner has been dropped. When the owner deletes the AutoThread they will be blocked until the thread is ready to be joined. AutoThread simplifies teardown of multi-threaded code by ensuring that threads persist until no code remains that may need to use them. BUG=145856 Review URL: https://chromiumcodereview.appspot.com/10919081 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@161030 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/base')
-rw-r--r--remoting/base/auto_thread.cc159
-rw-r--r--remoting/base/auto_thread.h95
-rw-r--r--remoting/base/auto_thread_unittest.cc115
3 files changed, 369 insertions, 0 deletions
diff --git a/remoting/base/auto_thread.cc b/remoting/base/auto_thread.cc
new file mode 100644
index 0000000..324ccc3
--- /dev/null
+++ b/remoting/base/auto_thread.cc
@@ -0,0 +1,159 @@
+// 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 "remoting/base/auto_thread.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/thread_local.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/synchronization/waitable_event.h"
+#include "remoting/base/auto_thread_task_runner.h"
+
+namespace remoting {
+
+// Used to pass data to ThreadMain. This structure is allocated on the stack
+// from within StartWithType.
+struct AutoThread::StartupData {
+ MessageLoop::Type loop_type;
+
+ // Used to receive the AutoThreadTaskRunner for the thread.
+ scoped_refptr<AutoThreadTaskRunner> task_runner;
+
+ // Used to synchronize thread startup.
+ base::WaitableEvent event;
+
+ explicit StartupData(MessageLoop::Type type)
+ : loop_type(type),
+ event(false, false) {}
+};
+
+// static
+scoped_refptr<AutoThreadTaskRunner> AutoThread::CreateWithType(
+ const char* name,
+ scoped_refptr<AutoThreadTaskRunner> joiner,
+ MessageLoop::Type type) {
+ AutoThread* thread = new AutoThread(name, joiner);
+ scoped_refptr<AutoThreadTaskRunner> task_runner = thread->StartWithType(type);
+ if (task_runner.get()) {
+ return task_runner;
+ } else {
+ delete thread;
+ return NULL;
+ }
+}
+
+// static
+scoped_refptr<AutoThreadTaskRunner> AutoThread::Create(
+ const char* name, scoped_refptr<AutoThreadTaskRunner> joiner) {
+ return CreateWithType(name, joiner, MessageLoop::TYPE_DEFAULT);
+}
+
+AutoThread::AutoThread(const char* name)
+ : startup_data_(NULL),
+ thread_(0),
+ name_(name),
+ was_quit_properly_(false) {
+}
+
+AutoThread::AutoThread(const char* name, AutoThreadTaskRunner* joiner)
+ : startup_data_(NULL),
+ thread_(0),
+ name_(name),
+ was_quit_properly_(false),
+ joiner_(joiner) {
+}
+
+AutoThread::~AutoThread() {
+ DCHECK(!startup_data_);
+
+ // Wait for the thread to exit.
+ if (thread_) {
+ base::PlatformThread::Join(thread_);
+ }
+}
+
+scoped_refptr<AutoThreadTaskRunner>
+AutoThread::StartWithType(MessageLoop::Type type) {
+ DCHECK(!thread_);
+
+ StartupData startup_data(type);
+ startup_data_ = &startup_data;
+
+ if (!base::PlatformThread::Create(0, this, &thread_)) {
+ DLOG(ERROR) << "failed to create thread";
+ startup_data_ = NULL;
+ return NULL;
+ }
+
+ // Wait for the thread to start and initialize message_loop_
+ // TODO(wez): Since at this point we know the MessageLoop _will_ run, and
+ // the thread lifetime is controlled by the AutoThreadTaskRunner, we would
+ // ideally return the AutoThreadTaskRunner to the caller without waiting for
+ // the thread to signal us.
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ startup_data.event.Wait();
+
+ // set it to NULL so we don't keep a pointer to some object on the stack.
+ startup_data_ = NULL;
+
+ DCHECK(startup_data.task_runner.get());
+ return startup_data.task_runner;
+}
+
+scoped_refptr<AutoThreadTaskRunner> AutoThread::Start() {
+ return StartWithType(MessageLoop::TYPE_DEFAULT);
+}
+
+void AutoThread::QuitThread(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ if (!task_runner->BelongsToCurrentThread()) {
+ task_runner->PostTask(FROM_HERE, base::Bind(&AutoThread::QuitThread,
+ base::Unretained(this),
+ task_runner));
+ return;
+ }
+
+ MessageLoop::current()->Quit();
+ was_quit_properly_ = true;
+
+ if (joiner_) {
+ joiner_->PostTask(FROM_HERE, base::Bind(&AutoThread::JoinAndDeleteThread,
+ base::Unretained(this)));
+ }
+}
+
+void AutoThread::JoinAndDeleteThread() {
+ delete this;
+}
+
+void AutoThread::ThreadMain() {
+ // The message loop for this thread.
+ MessageLoop message_loop(startup_data_->loop_type);
+
+ // Complete the initialization of our AutoThread object.
+ base::PlatformThread::SetName(name_.c_str());
+ ANNOTATE_THREAD_NAME(name_.c_str()); // Tell the name to race detector.
+ message_loop.set_thread_name(name_);
+
+ // Return an AutoThreadTaskRunner that will cleanly quit this thread when
+ // no more references to it remain.
+ startup_data_->task_runner =
+ new AutoThreadTaskRunner(message_loop.message_loop_proxy(),
+ base::Bind(&AutoThread::QuitThread,
+ base::Unretained(this),
+ message_loop.message_loop_proxy()));
+
+ startup_data_->event.Signal();
+ // startup_data_ can't be touched anymore since the starting thread is now
+ // unlocked.
+
+ message_loop.Run();
+
+ // Assert that MessageLoop::Quit was called by AutoThread::QuitThread.
+ DCHECK(was_quit_properly_);
+}
+
+} // namespace base
diff --git a/remoting/base/auto_thread.h b/remoting/base/auto_thread.h
new file mode 100644
index 0000000..4a34976
--- /dev/null
+++ b/remoting/base/auto_thread.h
@@ -0,0 +1,95 @@
+// 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.
+
+#ifndef REMOTING_BASE_AUTO_THREAD_H_
+#define REMOTING_BASE_AUTO_THREAD_H_
+
+#include <string>
+
+#include "base/message_loop.h"
+#include "base/threading/platform_thread.h"
+#include "remoting/base/auto_thread_task_runner.h"
+
+namespace remoting {
+
+// Thread implementation that runs a MessageLoop on a new thread, and manages
+// the lifetime of the MessageLoop and thread by tracking references to the
+// thread's TaskRunner. The caller passes the thread's TaskRunner to each
+// object that needs to run code on the thread, and when no references to the
+// TaskRunner remain, the thread will exit. When the caller destroys this
+// object they will be blocked until the thread exits.
+// All pending tasks queued on the thread's message loop will run to completion
+// before the thread is terminated.
+//
+// After the thread is stopped, the destruction sequence is:
+//
+// (1) Thread::CleanUp()
+// (2) MessageLoop::~MessageLoop
+// (3.b) MessageLoop::DestructionObserver::WillDestroyCurrentMessageLoop
+class AutoThread : base::PlatformThread::Delegate {
+ public:
+ // Create an AutoThread with the specified message-loop |type| and |name|.
+ // The supplied AutoThreadTaskRunner will be used to join and delete the
+ // new thread when no references to it remain.
+ static scoped_refptr<AutoThreadTaskRunner> CreateWithType(
+ const char* name,
+ scoped_refptr<AutoThreadTaskRunner> joiner,
+ MessageLoop::Type type);
+ static scoped_refptr<AutoThreadTaskRunner> Create(
+ const char* name,
+ scoped_refptr<AutoThreadTaskRunner> joiner);
+
+ // Construct the AutoThread. |name| identifies the thread for debugging.
+ explicit AutoThread(const char* name);
+
+ // Waits for the thread to exit, and then destroys it.
+ virtual ~AutoThread();
+
+ // Starts a the thread, running the specified type of MessageLoop. Returns
+ // an AutoThreadTaskRunner through which tasks may be posted to the thread
+ // if successful, or NULL on failure.
+ //
+ // Note: This function can't be called on Windows with the loader lock held;
+ // i.e. during a DllMain, global object construction or destruction, atexit()
+ // callback.
+ //
+ // NOTE: You must not call this MessageLoop's Quit method directly. The
+ // thread will exit when no references to the TaskRunner remain.
+ scoped_refptr<AutoThreadTaskRunner> StartWithType(MessageLoop::Type type);
+
+ // Shorthand for StartWithType(MessageLoop::TYPE_DEFAULT).
+ scoped_refptr<AutoThreadTaskRunner> Start();
+
+ private:
+ AutoThread(const char* name, AutoThreadTaskRunner* joiner);
+
+ void QuitThread(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+ void JoinAndDeleteThread();
+
+ // base::PlatformThread::Delegate methods:
+ virtual void ThreadMain() OVERRIDE;
+
+ // Used to pass data to ThreadMain.
+ struct StartupData;
+ StartupData* startup_data_;
+
+ // The thread's handle.
+ base::PlatformThreadHandle thread_;
+
+ // The name of the thread. Used for debugging purposes.
+ std::string name_;
+
+ // Flag used to indicate whether MessageLoop was quit properly.
+ // This allows us to detect premature exit via MessageLoop::Quit().
+ bool was_quit_properly_;
+
+ // AutoThreadTaskRunner to post a task to to join & delete this thread.
+ scoped_refptr<AutoThreadTaskRunner> joiner_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoThread);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_AUTO_THREAD_H_
diff --git a/remoting/base/auto_thread_unittest.cc b/remoting/base/auto_thread_unittest.cc
new file mode 100644
index 0000000..65f4aa5
--- /dev/null
+++ b/remoting/base/auto_thread_unittest.cc
@@ -0,0 +1,115 @@
+// 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/bind.h"
+#include "base/memory/ref_counted.h"
+#include "remoting/base/auto_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kThreadName[] = "Test thread";
+const char kThreadName2[] = "Test thread 2";
+
+void SetFlagTask(bool* success) {
+ *success = true;
+}
+
+void PostSetFlagTask(
+ scoped_refptr<base::TaskRunner> task_runner,
+ bool* success) {
+ task_runner->PostTask(FROM_HERE, base::Bind(&SetFlagTask, success));
+}
+
+} // namespace
+
+namespace remoting {
+
+class AutoThreadTest : public testing::Test {
+ public:
+ AutoThreadTest() : message_loop_quit_correctly_(false) {
+ }
+
+ void RunMessageLoop() {
+ // Release |main_task_runner_|, then run |message_loop_| until other
+ // references created in tests are gone. We also post a delayed quit
+ // task to |message_loop_| so the test will not hang on failure.
+ main_task_runner_ = NULL;
+ message_loop_.PostDelayedTask(
+ FROM_HERE, MessageLoop::QuitClosure(), base::TimeDelta::FromSeconds(5));
+ message_loop_.Run();
+ }
+
+ virtual void SetUp() OVERRIDE {
+ main_task_runner_ = new AutoThreadTaskRunner(
+ message_loop_.message_loop_proxy(),
+ base::Bind(&AutoThreadTest::QuitMainMessageLoop,
+ base::Unretained(this)));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Verify that |message_loop_| was quit by the AutoThreadTaskRunner.
+ EXPECT_TRUE(message_loop_quit_correctly_);
+ }
+
+ protected:
+ void QuitMainMessageLoop() {
+ message_loop_quit_correctly_ = true;
+ message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ }
+
+
+ MessageLoop message_loop_;
+ bool message_loop_quit_correctly_;
+ scoped_refptr<AutoThreadTaskRunner> main_task_runner_;
+};
+
+TEST_F(AutoThreadTest, StartAndStop) {
+ // Create an AutoThread joined by our MessageLoop.
+ scoped_refptr<base::TaskRunner> task_runner =
+ AutoThread::Create(kThreadName, main_task_runner_);
+ EXPECT_TRUE(task_runner.get());
+
+ task_runner = NULL;
+ RunMessageLoop();
+}
+
+TEST_F(AutoThreadTest, ProcessTask) {
+ // Create an AutoThread joined by our MessageLoop.
+ scoped_refptr<base::TaskRunner> task_runner =
+ AutoThread::Create(kThreadName, main_task_runner_);
+ EXPECT_TRUE(task_runner.get());
+
+ // Post a task to it.
+ bool success = false;
+ task_runner->PostTask(FROM_HERE, base::Bind(&SetFlagTask, &success));
+
+ task_runner = NULL;
+ RunMessageLoop();
+
+ EXPECT_TRUE(success);
+}
+
+TEST_F(AutoThreadTest, ThreadDependency) {
+ // Create two AutoThreads joined by our MessageLoop.
+ scoped_refptr<base::TaskRunner> task_runner1 =
+ AutoThread::Create(kThreadName, main_task_runner_);
+ EXPECT_TRUE(task_runner1.get());
+ scoped_refptr<base::TaskRunner> task_runner2 =
+ AutoThread::Create(kThreadName, main_task_runner_);
+ EXPECT_TRUE(task_runner2.get());
+
+ // Post a task to thread 1 that will post a task to thread 2.
+ bool success = false;
+ task_runner1->PostTask(FROM_HERE,
+ base::Bind(&PostSetFlagTask, task_runner2, &success));
+
+ task_runner1 = NULL;
+ task_runner2 = NULL;
+ RunMessageLoop();
+
+ EXPECT_TRUE(success);
+}
+
+} // namespace remoting