summaryrefslogtreecommitdiffstats
path: root/mojo/public/cpp/utility
diff options
context:
space:
mode:
authorrockot <rockot@chromium.org>2015-11-12 17:33:59 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-13 01:34:47 +0000
commit85dce086001825a2faa4e75755a669f5e08a1cad (patch)
tree722de1d974f799b3d1ee1ca4c81bb8b0fa75a95d /mojo/public/cpp/utility
parent415b73b1a400a994a86e6f29709aa0271e895dd5 (diff)
downloadchromium_src-85dce086001825a2faa4e75755a669f5e08a1cad.zip
chromium_src-85dce086001825a2faa4e75755a669f5e08a1cad.tar.gz
chromium_src-85dce086001825a2faa4e75755a669f5e08a1cad.tar.bz2
Move third_party/mojo/src/mojo/public to mojo/public
BUG=None NOPRESUBMIT=true Review URL: https://codereview.chromium.org/1410053006 Cr-Commit-Position: refs/heads/master@{#359461}
Diffstat (limited to 'mojo/public/cpp/utility')
-rw-r--r--mojo/public/cpp/utility/BUILD.gn35
-rw-r--r--mojo/public/cpp/utility/lib/mutex.cc52
-rw-r--r--mojo/public/cpp/utility/lib/run_loop.cc267
-rw-r--r--mojo/public/cpp/utility/lib/thread.cc64
-rw-r--r--mojo/public/cpp/utility/lib/thread_local.h54
-rw-r--r--mojo/public/cpp/utility/lib/thread_local_posix.cc39
-rw-r--r--mojo/public/cpp/utility/lib/thread_local_win.cc39
-rw-r--r--mojo/public/cpp/utility/mutex.h70
-rw-r--r--mojo/public/cpp/utility/run_loop.h156
-rw-r--r--mojo/public/cpp/utility/run_loop_handler.h25
-rw-r--r--mojo/public/cpp/utility/tests/BUILD.gn32
-rw-r--r--mojo/public/cpp/utility/tests/mutex_unittest.cc259
-rw-r--r--mojo/public/cpp/utility/tests/run_loop_unittest.cc425
-rw-r--r--mojo/public/cpp/utility/tests/thread_unittest.cc106
-rw-r--r--mojo/public/cpp/utility/thread.h62
15 files changed, 1685 insertions, 0 deletions
diff --git a/mojo/public/cpp/utility/BUILD.gn b/mojo/public/cpp/utility/BUILD.gn
new file mode 100644
index 0000000..96c1d11
--- /dev/null
+++ b/mojo/public/cpp/utility/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2014 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.
+
+import("../../mojo_sdk.gni")
+
+mojo_sdk_source_set("utility") {
+ sources = [
+ "lib/mutex.cc",
+ "lib/run_loop.cc",
+ "lib/thread.cc",
+ "lib/thread_local.h",
+ "lib/thread_local_posix.cc",
+ "lib/thread_local_win.cc",
+ "mutex.h",
+ "run_loop.h",
+ "run_loop_handler.h",
+ "thread.h",
+ ]
+
+ mojo_sdk_deps = [
+ "mojo/public/cpp/bindings:callback",
+ "mojo/public/cpp/system",
+ ]
+
+ if (is_win) {
+ # See crbug.com/342893:
+ sources -= [
+ "lib/mutex.cc",
+ "lib/thread.cc",
+ "mutex.h",
+ "thread.h",
+ ]
+ }
+}
diff --git a/mojo/public/cpp/utility/lib/mutex.cc b/mojo/public/cpp/utility/lib/mutex.cc
new file mode 100644
index 0000000..23370e1
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/mutex.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 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/public/cpp/utility/mutex.h"
+
+#include <assert.h>
+#include <errno.h>
+
+namespace mojo {
+
+// Release builds have inlined (non-error-checking) definitions in the header.
+#if !defined(NDEBUG)
+Mutex::Mutex() {
+ pthread_mutexattr_t mutexattr;
+ int rv = pthread_mutexattr_init(&mutexattr);
+ assert(rv == 0);
+ rv = pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK);
+ assert(rv == 0);
+ rv = pthread_mutex_init(&mutex_, &mutexattr);
+ assert(rv == 0);
+ rv = pthread_mutexattr_destroy(&mutexattr);
+ assert(rv == 0);
+}
+
+Mutex::~Mutex() {
+ int rv = pthread_mutex_destroy(&mutex_);
+ assert(rv == 0);
+}
+
+void Mutex::Lock() {
+ int rv = pthread_mutex_lock(&mutex_);
+ assert(rv == 0);
+}
+
+void Mutex::Unlock() {
+ int rv = pthread_mutex_unlock(&mutex_);
+ assert(rv == 0);
+}
+
+bool Mutex::TryLock() {
+ int rv = pthread_mutex_trylock(&mutex_);
+ assert(rv == 0 || rv == EBUSY);
+ return rv == 0;
+}
+
+void Mutex::AssertHeld() {
+ assert(pthread_mutex_lock(&mutex_) == EDEADLK);
+}
+#endif // !defined(NDEBUG)
+
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/lib/run_loop.cc b/mojo/public/cpp/utility/lib/run_loop.cc
new file mode 100644
index 0000000..7faf748
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/run_loop.cc
@@ -0,0 +1,267 @@
+// Copyright 2014 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/public/cpp/utility/run_loop.h"
+
+#include <assert.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "mojo/public/cpp/utility/lib/thread_local.h"
+#include "mojo/public/cpp/utility/run_loop_handler.h"
+
+namespace mojo {
+namespace {
+
+internal::ThreadLocalPointer<RunLoop> current_run_loop;
+
+const MojoTimeTicks kInvalidTimeTicks = static_cast<MojoTimeTicks>(0);
+
+} // namespace
+
+// State needed for one iteration of WaitMany().
+struct RunLoop::WaitState {
+ WaitState() : deadline(MOJO_DEADLINE_INDEFINITE) {}
+
+ std::vector<Handle> handles;
+ std::vector<MojoHandleSignals> handle_signals;
+ MojoDeadline deadline;
+};
+
+struct RunLoop::RunState {
+ RunState() : should_quit(false) {}
+
+ bool should_quit;
+};
+
+RunLoop::RunLoop()
+ : run_state_(nullptr), next_handler_id_(0), next_sequence_number_(0) {
+ assert(!current());
+ current_run_loop.Set(this);
+}
+
+RunLoop::~RunLoop() {
+ assert(current() == this);
+ NotifyHandlers(MOJO_RESULT_ABORTED, IGNORE_DEADLINE);
+ current_run_loop.Set(nullptr);
+}
+
+// static
+void RunLoop::SetUp() {
+ current_run_loop.Allocate();
+}
+
+// static
+void RunLoop::TearDown() {
+ assert(!current());
+ current_run_loop.Free();
+}
+
+// static
+RunLoop* RunLoop::current() {
+ return current_run_loop.Get();
+}
+
+void RunLoop::AddHandler(RunLoopHandler* handler,
+ const Handle& handle,
+ MojoHandleSignals handle_signals,
+ MojoDeadline deadline) {
+ assert(current() == this);
+ assert(handler);
+ assert(handle.is_valid());
+ // Assume it's an error if someone tries to reregister an existing handle.
+ assert(0u == handler_data_.count(handle));
+ HandlerData handler_data;
+ handler_data.handler = handler;
+ handler_data.handle_signals = handle_signals;
+ handler_data.deadline =
+ (deadline == MOJO_DEADLINE_INDEFINITE)
+ ? kInvalidTimeTicks
+ : GetTimeTicksNow() + static_cast<MojoTimeTicks>(deadline);
+ handler_data.id = next_handler_id_++;
+ handler_data_[handle] = handler_data;
+}
+
+void RunLoop::RemoveHandler(const Handle& handle) {
+ assert(current() == this);
+ handler_data_.erase(handle);
+}
+
+bool RunLoop::HasHandler(const Handle& handle) const {
+ return handler_data_.find(handle) != handler_data_.end();
+}
+
+void RunLoop::Run() {
+ RunInternal(UNTIL_EMPTY);
+}
+
+void RunLoop::RunUntilIdle() {
+ RunInternal(UNTIL_IDLE);
+}
+
+void RunLoop::RunInternal(RunMode run_mode) {
+ assert(current() == this);
+ RunState* old_state = run_state_;
+ RunState run_state;
+ run_state_ = &run_state;
+ for (;;) {
+ bool did_work = DoDelayedWork();
+ if (run_state.should_quit)
+ break;
+ did_work |= Wait(run_mode == UNTIL_IDLE);
+ if (run_state.should_quit)
+ break;
+ if (!did_work && run_mode == UNTIL_IDLE)
+ break;
+ }
+ run_state_ = old_state;
+}
+
+bool RunLoop::DoDelayedWork() {
+ MojoTimeTicks now = GetTimeTicksNow();
+ if (!delayed_tasks_.empty() && delayed_tasks_.top().run_time <= now) {
+ PendingTask task = delayed_tasks_.top();
+ delayed_tasks_.pop();
+ task.task.Run();
+ return true;
+ }
+ return false;
+}
+
+void RunLoop::Quit() {
+ assert(current() == this);
+ if (run_state_)
+ run_state_->should_quit = true;
+}
+
+void RunLoop::PostDelayedTask(const Closure& task, MojoTimeTicks delay) {
+ assert(current() == this);
+ MojoTimeTicks run_time = delay + GetTimeTicksNow();
+ delayed_tasks_.push(PendingTask(task, run_time, next_sequence_number_++));
+}
+
+bool RunLoop::Wait(bool non_blocking) {
+ const WaitState wait_state = GetWaitState(non_blocking);
+ if (wait_state.handles.empty()) {
+ if (delayed_tasks_.empty())
+ Quit();
+ return false;
+ }
+
+ const WaitManyResult wmr =
+ WaitMany(wait_state.handles, wait_state.handle_signals,
+ wait_state.deadline, nullptr);
+
+ if (!wmr.IsIndexValid()) {
+ assert(wmr.result == MOJO_RESULT_DEADLINE_EXCEEDED);
+ return NotifyHandlers(MOJO_RESULT_DEADLINE_EXCEEDED, CHECK_DEADLINE);
+ }
+
+ Handle handle = wait_state.handles[wmr.index];
+ assert(handler_data_.find(handle) != handler_data_.end());
+ RunLoopHandler* handler = handler_data_[handle].handler;
+
+ switch (wmr.result) {
+ case MOJO_RESULT_OK:
+ handler->OnHandleReady(handle);
+ return true;
+ case MOJO_RESULT_INVALID_ARGUMENT:
+ case MOJO_RESULT_FAILED_PRECONDITION:
+ // Remove the handle first, this way if OnHandleError() tries to remove
+ // the handle our iterator isn't invalidated.
+ handler_data_.erase(handle);
+ handler->OnHandleError(handle, wmr.result);
+ return true;
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool RunLoop::NotifyHandlers(MojoResult error, CheckDeadline check) {
+ bool notified = false;
+
+ // Make a copy in case someone tries to add/remove new handlers as part of
+ // notifying.
+ const HandleToHandlerData cloned_handlers(handler_data_);
+ const MojoTimeTicks now(GetTimeTicksNow());
+ for (HandleToHandlerData::const_iterator i = cloned_handlers.begin();
+ i != cloned_handlers.end();
+ ++i) {
+ // Only check deadline exceeded if that's what we're notifying.
+ if (check == CHECK_DEADLINE &&
+ (i->second.deadline == kInvalidTimeTicks || i->second.deadline > now)) {
+ continue;
+ }
+
+ // Since we're iterating over a clone of the handlers, verify the handler
+ // is still valid before notifying.
+ if (handler_data_.find(i->first) == handler_data_.end() ||
+ handler_data_[i->first].id != i->second.id) {
+ continue;
+ }
+
+ RunLoopHandler* handler = i->second.handler;
+ handler_data_.erase(i->first);
+ handler->OnHandleError(i->first, error);
+ notified = true;
+ }
+
+ return notified;
+}
+
+RunLoop::WaitState RunLoop::GetWaitState(bool non_blocking) const {
+ WaitState wait_state;
+ MojoTimeTicks min_time = kInvalidTimeTicks;
+ for (HandleToHandlerData::const_iterator i = handler_data_.begin();
+ i != handler_data_.end();
+ ++i) {
+ wait_state.handles.push_back(i->first);
+ wait_state.handle_signals.push_back(i->second.handle_signals);
+ if (!non_blocking && i->second.deadline != kInvalidTimeTicks &&
+ (min_time == kInvalidTimeTicks || i->second.deadline < min_time)) {
+ min_time = i->second.deadline;
+ }
+ }
+ if (!delayed_tasks_.empty()) {
+ MojoTimeTicks delayed_min_time = delayed_tasks_.top().run_time;
+ if (min_time == kInvalidTimeTicks)
+ min_time = delayed_min_time;
+ else
+ min_time = std::min(min_time, delayed_min_time);
+ }
+ if (non_blocking) {
+ wait_state.deadline = static_cast<MojoDeadline>(0);
+ } else if (min_time != kInvalidTimeTicks) {
+ const MojoTimeTicks now = GetTimeTicksNow();
+ if (min_time < now)
+ wait_state.deadline = static_cast<MojoDeadline>(0);
+ else
+ wait_state.deadline = static_cast<MojoDeadline>(min_time - now);
+ }
+ return wait_state;
+}
+
+RunLoop::PendingTask::PendingTask(const Closure& task,
+ MojoTimeTicks run_time,
+ uint64_t sequence_number)
+ : task(task), run_time(run_time), sequence_number(sequence_number) {
+}
+
+RunLoop::PendingTask::~PendingTask() {
+}
+
+bool RunLoop::PendingTask::operator<(const RunLoop::PendingTask& other) const {
+ if (run_time != other.run_time) {
+ // std::priority_queue<> puts the least element at the end of the queue. We
+ // want the soonest eligible task to be at the head of the queue, so
+ // run_times further in the future are considered lesser.
+ return run_time > other.run_time;
+ }
+
+ return sequence_number > other.sequence_number;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/lib/thread.cc b/mojo/public/cpp/utility/lib/thread.cc
new file mode 100644
index 0000000..40f0bdd
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/thread.cc
@@ -0,0 +1,64 @@
+// Copyright 2014 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/public/cpp/utility/thread.h"
+
+#include <assert.h>
+
+namespace mojo {
+
+Thread::Thread() : options_(), thread_(), started_(false), joined_(false) {
+}
+
+Thread::Thread(const Options& options)
+ : options_(options), thread_(), started_(false), joined_(false) {
+}
+
+Thread::~Thread() {
+ // If it was started, it must have been joined.
+ assert(!started_ || joined_);
+}
+
+void Thread::Start() {
+ assert(!started_);
+ assert(!joined_);
+
+ pthread_attr_t attr;
+ int rv = pthread_attr_init(&attr);
+ MOJO_ALLOW_UNUSED_LOCAL(rv);
+ assert(rv == 0);
+
+ // Non-default stack size?
+ if (options_.stack_size() != 0) {
+ rv = pthread_attr_setstacksize(&attr, options_.stack_size());
+ assert(rv == 0);
+ }
+
+ started_ = true;
+ rv = pthread_create(&thread_, &attr, &ThreadRunTrampoline, this);
+ assert(rv == 0);
+
+ rv = pthread_attr_destroy(&attr);
+ assert(rv == 0);
+}
+
+void Thread::Join() {
+ // Must have been started but not yet joined.
+ assert(started_);
+ assert(!joined_);
+
+ joined_ = true;
+ int rv = pthread_join(thread_, nullptr);
+ MOJO_ALLOW_UNUSED_LOCAL(rv);
+ assert(rv == 0);
+}
+
+// static
+void* Thread::ThreadRunTrampoline(void* arg) {
+ Thread* self = static_cast<Thread*>(arg);
+ self->Run();
+ return nullptr;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/lib/thread_local.h b/mojo/public/cpp/utility/lib/thread_local.h
new file mode 100644
index 0000000..f5461ee
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/thread_local.h
@@ -0,0 +1,54 @@
+// Copyright 2014 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 MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_
+#define MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_
+
+#ifndef _WIN32
+#include <pthread.h>
+#endif
+
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace internal {
+
+// Helper functions that abstract the cross-platform APIs.
+struct ThreadLocalPlatform {
+#ifdef _WIN32
+ typedef unsigned long SlotType;
+#else
+ typedef pthread_key_t SlotType;
+#endif
+
+ static void AllocateSlot(SlotType* slot);
+ static void FreeSlot(SlotType slot);
+ static void* GetValueFromSlot(SlotType slot);
+ static void SetValueInSlot(SlotType slot, void* value);
+};
+
+// This class is intended to be statically allocated.
+template <typename P>
+class ThreadLocalPointer {
+ public:
+ ThreadLocalPointer() : slot_() {}
+
+ void Allocate() { ThreadLocalPlatform::AllocateSlot(&slot_); }
+
+ void Free() { ThreadLocalPlatform::FreeSlot(slot_); }
+
+ P* Get() {
+ return static_cast<P*>(ThreadLocalPlatform::GetValueFromSlot(slot_));
+ }
+
+ void Set(P* value) { ThreadLocalPlatform::SetValueInSlot(slot_, value); }
+
+ private:
+ ThreadLocalPlatform::SlotType slot_;
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_UTILITY_LIB_THREAD_LOCAL_H_
diff --git a/mojo/public/cpp/utility/lib/thread_local_posix.cc b/mojo/public/cpp/utility/lib/thread_local_posix.cc
new file mode 100644
index 0000000..ea7343e
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/thread_local_posix.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 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/public/cpp/utility/lib/thread_local.h"
+
+#include <assert.h>
+
+namespace mojo {
+namespace internal {
+
+// static
+void ThreadLocalPlatform::AllocateSlot(SlotType* slot) {
+ if (pthread_key_create(slot, nullptr) != 0) {
+ assert(false);
+ }
+}
+
+// static
+void ThreadLocalPlatform::FreeSlot(SlotType slot) {
+ if (pthread_key_delete(slot) != 0) {
+ assert(false);
+ }
+}
+
+// static
+void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) {
+ return pthread_getspecific(slot);
+}
+
+// static
+void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) {
+ if (pthread_setspecific(slot, value) != 0) {
+ assert(false);
+ }
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/lib/thread_local_win.cc b/mojo/public/cpp/utility/lib/thread_local_win.cc
new file mode 100644
index 0000000..b8239cb
--- /dev/null
+++ b/mojo/public/cpp/utility/lib/thread_local_win.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 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/public/cpp/utility/lib/thread_local.h"
+
+#include <windows.h>
+#include <assert.h>
+
+namespace mojo {
+namespace internal {
+
+// static
+void ThreadLocalPlatform::AllocateSlot(SlotType* slot) {
+ *slot = TlsAlloc();
+ assert(*slot != TLS_OUT_OF_INDEXES);
+}
+
+// static
+void ThreadLocalPlatform::FreeSlot(SlotType slot) {
+ if (!TlsFree(slot)) {
+ assert(false);
+ }
+}
+
+// static
+void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) {
+ return TlsGetValue(slot);
+}
+
+// static
+void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) {
+ if (!TlsSetValue(slot, value)) {
+ assert(false);
+ }
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/mutex.h b/mojo/public/cpp/utility/mutex.h
new file mode 100644
index 0000000..4dc4aee
--- /dev/null
+++ b/mojo/public/cpp/utility/mutex.h
@@ -0,0 +1,70 @@
+// Copyright 2014 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 MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_
+#define MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_
+
+#ifdef _WIN32
+#error "Not implemented: See crbug.com/342893."
+#endif
+
+#include <pthread.h>
+
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+#ifdef NDEBUG
+// Note: Make a C++ constant for |PTHREAD_MUTEX_INITIALIZER|. (We can't directly
+// use the C macro in an initializer list, since it might expand to |{ ... }|.)
+namespace internal {
+const pthread_mutex_t kPthreadMutexInitializer = PTHREAD_MUTEX_INITIALIZER;
+}
+#endif
+
+class Mutex {
+ public:
+#ifdef NDEBUG
+ Mutex() : mutex_(internal::kPthreadMutexInitializer) {}
+ ~Mutex() { pthread_mutex_destroy(&mutex_); }
+
+ void Lock() { pthread_mutex_lock(&mutex_); }
+ void Unlock() { pthread_mutex_unlock(&mutex_); }
+ bool TryLock() { return pthread_mutex_trylock(&mutex_) == 0; }
+
+ void AssertHeld() {}
+#else
+ Mutex();
+ ~Mutex();
+
+ void Lock();
+ void Unlock();
+ bool TryLock();
+
+ void AssertHeld();
+#endif
+
+ private:
+ pthread_mutex_t mutex_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Mutex);
+};
+
+class MutexLock {
+ public:
+ explicit MutexLock(Mutex* mutex) : mutex_(mutex) { mutex_->Lock(); }
+ ~MutexLock() { mutex_->Unlock(); }
+
+ private:
+ Mutex* const mutex_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MutexLock);
+};
+
+// Catch bug where variable name is omitted (e.g., |MutexLock (&mu)|).
+#define MutexLock(x) static_assert(0, "MutexLock() missing variable name");
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_UTILITY_MUTEX_H_
diff --git a/mojo/public/cpp/utility/run_loop.h b/mojo/public/cpp/utility/run_loop.h
new file mode 100644
index 0000000..4673eaa
--- /dev/null
+++ b/mojo/public/cpp/utility/run_loop.h
@@ -0,0 +1,156 @@
+// 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.
+
+#ifndef MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_
+#define MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_
+
+#include <map>
+#include <queue>
+
+#include "mojo/public/cpp/bindings/callback.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+class RunLoopHandler;
+
+// Watches handles for signals and calls event handlers when they occur. Also
+// executes delayed tasks. This class should only be used by a single thread.
+class RunLoop {
+ public:
+ RunLoop();
+ ~RunLoop();
+
+ // Sets up state needed for RunLoop. This must be invoked before creating a
+ // RunLoop.
+ static void SetUp();
+
+ // Cleans state created by Setup().
+ static void TearDown();
+
+ // Returns the RunLoop for the current thread. Returns null if not yet
+ // created.
+ static RunLoop* current();
+
+ // Registers a RunLoopHandler for the specified handle. It is an error to
+ // register more than one handler for a handle, and crashes the process.
+ //
+ // The handler's OnHandleReady() method is invoked after one of the signals in
+ // |handle_signals| occurs. Note that the handler remains registered until
+ // explicitly removed or an error occurs.
+ //
+ // The handler's OnHandleError() method is invoked if the deadline elapses, an
+ // error is detected, or the RunLoop is being destroyed. The handler is
+ // automatically unregistered before calling OnHandleError(), so it will not
+ // receive any further notifications.
+ void AddHandler(RunLoopHandler* handler,
+ const Handle& handle,
+ MojoHandleSignals handle_signals,
+ MojoDeadline deadline);
+ void RemoveHandler(const Handle& handle);
+ bool HasHandler(const Handle& handle) const;
+
+ // Runs the loop servicing handles and tasks as they are ready. This returns
+ // when Quit() is invoked, or there are no more handles nor tasks.
+ void Run();
+
+ // Runs the loop servicing any handles and tasks that are ready. Does not wait
+ // for handles or tasks to become ready before returning. Returns early if
+ // Quit() is invoked.
+ void RunUntilIdle();
+
+ void Quit();
+
+ // Adds a task to be performed after delay has elapsed. Must be posted to the
+ // current thread's RunLoop.
+ void PostDelayedTask(const Closure& task, MojoTimeTicks delay);
+
+ private:
+ struct RunState;
+ struct WaitState;
+
+ // Contains the data needed to track a request to AddHandler().
+ struct HandlerData {
+ HandlerData()
+ : handler(nullptr),
+ handle_signals(MOJO_HANDLE_SIGNAL_NONE),
+ deadline(0),
+ id(0) {}
+
+ RunLoopHandler* handler;
+ MojoHandleSignals handle_signals;
+ MojoTimeTicks deadline;
+ // See description of |RunLoop::next_handler_id_| for details.
+ int id;
+ };
+
+ typedef std::map<Handle, HandlerData> HandleToHandlerData;
+
+ // Used for NotifyHandlers to specify whether HandlerData's |deadline|
+ // should be checked prior to notifying.
+ enum CheckDeadline { CHECK_DEADLINE, IGNORE_DEADLINE };
+
+ // Mode of operation of the run loop.
+ enum RunMode { UNTIL_EMPTY, UNTIL_IDLE };
+
+ // Runs the loop servicing any handles and tasks that are ready. If
+ // |run_mode| is |UNTIL_IDLE|, does not wait for handles or tasks to become
+ // ready before returning. Returns early if Quit() is invoked.
+ void RunInternal(RunMode run_mode);
+
+ // Do one unit of delayed work, if eligible. Returns true is a task was run.
+ bool DoDelayedWork();
+
+ // Waits for a handle to be ready or until the next task must be run. Returns
+ // after servicing at least one handle (or there are no more handles) unless
+ // a task must be run or |non_blocking| is true, in which case it will also
+ // return if no task is registered and servicing at least one handle would
+ // require blocking. Returns true if a RunLoopHandler was notified.
+ bool Wait(bool non_blocking);
+
+ // Notifies handlers of |error|. If |check| == CHECK_DEADLINE, this will
+ // only notify handlers whose deadline has expired and skips the rest.
+ // Returns true if a RunLoopHandler was notified.
+ bool NotifyHandlers(MojoResult error, CheckDeadline check);
+
+ // Returns the state needed to pass to WaitMany().
+ WaitState GetWaitState(bool non_blocking) const;
+
+ HandleToHandlerData handler_data_;
+
+ // If non-null we're running (inside Run()). Member references a value on the
+ // stack.
+ RunState* run_state_;
+
+ // An ever increasing value assigned to each HandlerData::id. Used to detect
+ // uniqueness while notifying. That is, while notifying expired timers we copy
+ // |handler_data_| and only notify handlers whose id match. If the id does not
+ // match it means the handler was removed then added so that we shouldn't
+ // notify it.
+ int next_handler_id_;
+
+ struct PendingTask {
+ PendingTask(const Closure& task,
+ MojoTimeTicks runtime,
+ uint64_t sequence_number);
+ ~PendingTask();
+
+ bool operator<(const PendingTask& other) const;
+
+ Closure task;
+ MojoTimeTicks run_time;
+ uint64_t sequence_number;
+ };
+ // An ever increasing sequence number attached to each pending task in order
+ // to preserve relative order of tasks posted at the 'same' time.
+ uint64_t next_sequence_number_;
+ typedef std::priority_queue<PendingTask> DelayedTaskQueue;
+ DelayedTaskQueue delayed_tasks_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoop);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_H_
diff --git a/mojo/public/cpp/utility/run_loop_handler.h b/mojo/public/cpp/utility/run_loop_handler.h
new file mode 100644
index 0000000..69838d5
--- /dev/null
+++ b/mojo/public/cpp/utility/run_loop_handler.h
@@ -0,0 +1,25 @@
+// 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.
+
+#ifndef MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_
+#define MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_
+
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+
+// Used by RunLoop to notify when a handle is either ready or has become
+// invalid.
+class RunLoopHandler {
+ public:
+ virtual void OnHandleReady(const Handle& handle) = 0;
+ virtual void OnHandleError(const Handle& handle, MojoResult result) = 0;
+
+ protected:
+ virtual ~RunLoopHandler() {}
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_UTILITY_RUN_LOOP_HANDLER_H_
diff --git a/mojo/public/cpp/utility/tests/BUILD.gn b/mojo/public/cpp/utility/tests/BUILD.gn
new file mode 100644
index 0000000..acbbc9f
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/BUILD.gn
@@ -0,0 +1,32 @@
+# Copyright 2014 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.
+
+import("../../../mojo_sdk.gni")
+
+mojo_sdk_source_set("tests") {
+ testonly = true
+
+ sources = [
+ "run_loop_unittest.cc",
+ ]
+
+ deps = [
+ "//testing/gtest",
+ ]
+
+ mojo_sdk_deps = [
+ "mojo/public/cpp/environment:standalone",
+ "mojo/public/cpp/system",
+ "mojo/public/cpp/test_support:test_utils",
+ "mojo/public/cpp/utility",
+ ]
+
+ # crbug.com/342893
+ if (!is_win) {
+ sources += [
+ "mutex_unittest.cc",
+ "thread_unittest.cc",
+ ]
+ }
+}
diff --git a/mojo/public/cpp/utility/tests/mutex_unittest.cc b/mojo/public/cpp/utility/tests/mutex_unittest.cc
new file mode 100644
index 0000000..78e95c5
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/mutex_unittest.cc
@@ -0,0 +1,259 @@
+// Copyright 2014 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/public/cpp/utility/mutex.h"
+
+#include <stdlib.h> // For |rand()|.
+#include <time.h> // For |nanosleep()| (defined by POSIX).
+
+#include <vector>
+
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/utility/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+TEST(MutexTest, TrivialSingleThreaded) {
+ Mutex mutex;
+
+ mutex.Lock();
+ mutex.AssertHeld();
+ mutex.Unlock();
+
+ EXPECT_TRUE(mutex.TryLock());
+ mutex.AssertHeld();
+ mutex.Unlock();
+
+ {
+ MutexLock lock(&mutex);
+ mutex.AssertHeld();
+ }
+
+ EXPECT_TRUE(mutex.TryLock());
+ mutex.Unlock();
+}
+
+class Fiddler {
+ public:
+ enum Type { kTypeLock, kTypeTry };
+ Fiddler(size_t times_to_lock,
+ Type type,
+ bool should_sleep,
+ Mutex* mutex,
+ int* shared_value)
+ : times_to_lock_(times_to_lock),
+ type_(type),
+ should_sleep_(should_sleep),
+ mutex_(mutex),
+ shared_value_(shared_value) {
+ }
+
+ ~Fiddler() {
+ }
+
+ void Fiddle() {
+ for (size_t i = 0; i < times_to_lock_;) {
+ switch (type_) {
+ case kTypeLock: {
+ mutex_->Lock();
+ int old_shared_value = *shared_value_;
+ if (should_sleep_)
+ SleepALittle();
+ *shared_value_ = old_shared_value + 1;
+ mutex_->Unlock();
+ i++;
+ break;
+ }
+ case kTypeTry:
+ if (mutex_->TryLock()) {
+ int old_shared_value = *shared_value_;
+ if (should_sleep_)
+ SleepALittle();
+ *shared_value_ = old_shared_value + 1;
+ mutex_->Unlock();
+ i++;
+ } else {
+ SleepALittle(); // Don't spin.
+ }
+ break;
+ }
+ }
+ }
+
+ private:
+ static void SleepALittle() {
+ static const long kNanosPerMilli = 1000000;
+ struct timespec req = {
+ 0, // Seconds.
+ (rand() % 10) * kNanosPerMilli // Nanoseconds.
+ };
+ int rv = nanosleep(&req, nullptr);
+ MOJO_ALLOW_UNUSED_LOCAL(rv);
+ assert(rv == 0);
+ }
+
+ const size_t times_to_lock_;
+ const Type type_;
+ const bool should_sleep_;
+ Mutex* const mutex_;
+ int* const shared_value_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Fiddler);
+};
+
+class FiddlerThread : public Thread {
+ public:
+ // Takes ownership of |fiddler|.
+ FiddlerThread(Fiddler* fiddler)
+ : fiddler_(fiddler) {
+ }
+
+ ~FiddlerThread() override { delete fiddler_; }
+
+ void Run() override { fiddler_->Fiddle(); }
+
+ private:
+ Fiddler* const fiddler_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(FiddlerThread);
+};
+
+// This does a stress test (that also checks exclusion).
+TEST(MutexTest, ThreadedStress) {
+ static const size_t kNumThreads = 20;
+ static const int kTimesToLockEach = 20;
+ assert(kNumThreads % 4 == 0);
+
+ Mutex mutex;
+ int shared_value = 0;
+
+ std::vector<FiddlerThread*> fiddler_threads;
+
+ for (size_t i = 0; i < kNumThreads; i += 4) {
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeLock, false, &mutex, &shared_value)));
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeTry, false, &mutex, &shared_value)));
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value)));
+ fiddler_threads.push_back(new FiddlerThread(new Fiddler(
+ kTimesToLockEach, Fiddler::kTypeTry, true, &mutex, &shared_value)));
+ }
+
+ for (size_t i = 0; i < kNumThreads; i++)
+ fiddler_threads[i]->Start();
+
+ // Do some fiddling ourselves.
+ Fiddler(kTimesToLockEach, Fiddler::kTypeLock, true, &mutex, &shared_value)
+ .Fiddle();
+
+ // Join.
+ for (size_t i = 0; i < kNumThreads; i++)
+ fiddler_threads[i]->Join();
+
+ EXPECT_EQ(static_cast<int>(kNumThreads + 1) * kTimesToLockEach, shared_value);
+
+ // Delete.
+ for (size_t i = 0; i < kNumThreads; i++)
+ delete fiddler_threads[i];
+ fiddler_threads.clear();
+}
+
+class TryThread : public Thread {
+ public:
+ explicit TryThread(Mutex* mutex) : mutex_(mutex), try_lock_succeeded_() {}
+ ~TryThread() override {}
+
+ void Run() override {
+ try_lock_succeeded_ = mutex_->TryLock();
+ if (try_lock_succeeded_)
+ mutex_->Unlock();
+ }
+
+ bool try_lock_succeeded() const { return try_lock_succeeded_; }
+
+ private:
+ Mutex* const mutex_;
+ bool try_lock_succeeded_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TryThread);
+};
+
+TEST(MutexTest, TryLock) {
+ Mutex mutex;
+
+ // |TryLock()| should succeed -- we don't have the lock.
+ {
+ TryThread thread(&mutex);
+ thread.Start();
+ thread.Join();
+ EXPECT_TRUE(thread.try_lock_succeeded());
+ }
+
+ // Take the lock.
+ ASSERT_TRUE(mutex.TryLock());
+
+ // Now it should fail.
+ {
+ TryThread thread(&mutex);
+ thread.Start();
+ thread.Join();
+ EXPECT_FALSE(thread.try_lock_succeeded());
+ }
+
+ // Release the lock.
+ mutex.Unlock();
+
+ // It should succeed again.
+ {
+ TryThread thread(&mutex);
+ thread.Start();
+ thread.Join();
+ EXPECT_TRUE(thread.try_lock_succeeded());
+ }
+}
+
+
+// Tests of assertions for Debug builds.
+#if !defined(NDEBUG)
+// Test |AssertHeld()| (which is an actual user API).
+TEST(MutexTest, DebugAssertHeldFailure) {
+ Mutex mutex;
+ EXPECT_DEATH_IF_SUPPORTED(mutex.AssertHeld(), "");
+}
+
+// Test other consistency checks.
+TEST(MutexTest, DebugAssertionFailures) {
+ // Unlock without lock held.
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Unlock();
+ }, "");
+
+ // Lock with lock held (on same thread).
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Lock();
+ mutex.Lock();
+ }, "");
+
+ // Try lock with lock held.
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Lock();
+ mutex.TryLock();
+ }, "");
+
+ // Destroy lock with lock held.
+ EXPECT_DEATH_IF_SUPPORTED({
+ Mutex mutex;
+ mutex.Lock();
+ }, "");
+}
+#endif // !defined(NDEBUG)
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/tests/run_loop_unittest.cc b/mojo/public/cpp/utility/tests/run_loop_unittest.cc
new file mode 100644
index 0000000..4ab4876
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/run_loop_unittest.cc
@@ -0,0 +1,425 @@
+// Copyright 2014 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/public/cpp/utility/run_loop.h"
+
+#include <string>
+
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/cpp/utility/run_loop_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class TestRunLoopHandler : public RunLoopHandler {
+ public:
+ TestRunLoopHandler()
+ : ready_count_(0),
+ error_count_(0),
+ last_error_result_(MOJO_RESULT_OK) {
+ }
+ ~TestRunLoopHandler() override {}
+
+ void clear_ready_count() { ready_count_ = 0; }
+ int ready_count() const { return ready_count_; }
+
+ void clear_error_count() { error_count_ = 0; }
+ int error_count() const { return error_count_; }
+
+ MojoResult last_error_result() const { return last_error_result_; }
+
+ // RunLoopHandler:
+ void OnHandleReady(const Handle& handle) override { ready_count_++; }
+ void OnHandleError(const Handle& handle, MojoResult result) override {
+ error_count_++;
+ last_error_result_ = result;
+ }
+
+ private:
+ int ready_count_;
+ int error_count_;
+ MojoResult last_error_result_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestRunLoopHandler);
+};
+
+class RunLoopTest : public testing::Test {
+ public:
+ RunLoopTest() {}
+
+ void SetUp() override {
+ Test::SetUp();
+ RunLoop::SetUp();
+ }
+ void TearDown() override {
+ RunLoop::TearDown();
+ Test::TearDown();
+ }
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RunLoopTest);
+};
+
+// Trivial test to verify Run() with no added handles returns.
+TEST_F(RunLoopTest, ExitsWithNoHandles) {
+ RunLoop run_loop;
+ run_loop.Run();
+}
+
+class RemoveOnReadyRunLoopHandler : public TestRunLoopHandler {
+ public:
+ RemoveOnReadyRunLoopHandler() : run_loop_(nullptr) {}
+ ~RemoveOnReadyRunLoopHandler() override {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ void OnHandleReady(const Handle& handle) override {
+ run_loop_->RemoveHandler(handle);
+ TestRunLoopHandler::OnHandleReady(handle);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveOnReadyRunLoopHandler);
+};
+
+// Verifies RunLoop quits when no more handles (handle is removed when ready).
+TEST_F(RunLoopTest, HandleReady) {
+ RemoveOnReadyRunLoopHandler handler;
+ MessagePipe test_pipe;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string()));
+
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE);
+ run_loop.Run();
+ EXPECT_EQ(1, handler.ready_count());
+ EXPECT_EQ(0, handler.error_count());
+ EXPECT_FALSE(run_loop.HasHandler(test_pipe.handle0.get()));
+}
+
+class QuitOnReadyRunLoopHandler : public TestRunLoopHandler {
+ public:
+ QuitOnReadyRunLoopHandler() : run_loop_(nullptr) {}
+ ~QuitOnReadyRunLoopHandler() override {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ void OnHandleReady(const Handle& handle) override {
+ run_loop_->Quit();
+ TestRunLoopHandler::OnHandleReady(handle);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnReadyRunLoopHandler);
+};
+
+// Verifies Quit() from OnHandleReady() quits the loop.
+TEST_F(RunLoopTest, QuitFromReady) {
+ QuitOnReadyRunLoopHandler handler;
+ MessagePipe test_pipe;
+ EXPECT_TRUE(test::WriteTextMessage(test_pipe.handle1.get(), std::string()));
+
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE);
+ run_loop.Run();
+ EXPECT_EQ(1, handler.ready_count());
+ EXPECT_EQ(0, handler.error_count());
+ EXPECT_TRUE(run_loop.HasHandler(test_pipe.handle0.get()));
+}
+
+class QuitOnErrorRunLoopHandler : public TestRunLoopHandler {
+ public:
+ QuitOnErrorRunLoopHandler() : run_loop_(nullptr) {}
+ ~QuitOnErrorRunLoopHandler() override {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ void OnHandleError(const Handle& handle, MojoResult result) override {
+ run_loop_->Quit();
+ TestRunLoopHandler::OnHandleError(handle, result);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(QuitOnErrorRunLoopHandler);
+};
+
+// Verifies Quit() when the deadline is reached works.
+TEST_F(RunLoopTest, QuitWhenDeadlineExpired) {
+ QuitOnErrorRunLoopHandler handler;
+ MessagePipe test_pipe;
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ static_cast<MojoDeadline>(10000));
+ run_loop.Run();
+ EXPECT_EQ(0, handler.ready_count());
+ EXPECT_EQ(1, handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, handler.last_error_result());
+ EXPECT_FALSE(run_loop.HasHandler(test_pipe.handle0.get()));
+}
+
+// Test that handlers are notified of loop destruction.
+TEST_F(RunLoopTest, Destruction) {
+ TestRunLoopHandler handler;
+ MessagePipe test_pipe;
+ {
+ RunLoop run_loop;
+ run_loop.AddHandler(&handler,
+ test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ }
+ EXPECT_EQ(1, handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, handler.last_error_result());
+}
+
+class RemoveManyRunLoopHandler : public TestRunLoopHandler {
+ public:
+ RemoveManyRunLoopHandler() : run_loop_(nullptr) {}
+ ~RemoveManyRunLoopHandler() override {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+ void add_handle(const Handle& handle) { handles_.push_back(handle); }
+
+ // RunLoopHandler:
+ void OnHandleError(const Handle& handle, MojoResult result) override {
+ for (size_t i = 0; i < handles_.size(); i++)
+ run_loop_->RemoveHandler(handles_[i]);
+ TestRunLoopHandler::OnHandleError(handle, result);
+ }
+
+ private:
+ std::vector<Handle> handles_;
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveManyRunLoopHandler);
+};
+
+// Test that handlers are notified of loop destruction.
+TEST_F(RunLoopTest, MultipleHandleDestruction) {
+ RemoveManyRunLoopHandler odd_handler;
+ TestRunLoopHandler even_handler;
+ MessagePipe test_pipe1, test_pipe2, test_pipe3;
+ {
+ RunLoop run_loop;
+ odd_handler.set_run_loop(&run_loop);
+ odd_handler.add_handle(test_pipe1.handle0.get());
+ odd_handler.add_handle(test_pipe3.handle0.get());
+ run_loop.AddHandler(&odd_handler,
+ test_pipe1.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ run_loop.AddHandler(&even_handler,
+ test_pipe2.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ run_loop.AddHandler(&odd_handler,
+ test_pipe3.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ }
+ EXPECT_EQ(1, odd_handler.error_count());
+ EXPECT_EQ(1, even_handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, odd_handler.last_error_result());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, even_handler.last_error_result());
+}
+
+class AddHandlerOnErrorHandler : public TestRunLoopHandler {
+ public:
+ AddHandlerOnErrorHandler() : run_loop_(nullptr) {}
+ ~AddHandlerOnErrorHandler() override {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+
+ // RunLoopHandler:
+ void OnHandleError(const Handle& handle, MojoResult result) override {
+ run_loop_->AddHandler(this, handle,
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ TestRunLoopHandler::OnHandleError(handle, result);
+ }
+
+ private:
+ RunLoop* run_loop_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AddHandlerOnErrorHandler);
+};
+
+TEST_F(RunLoopTest, AddHandlerOnError) {
+ AddHandlerOnErrorHandler handler;
+ MessagePipe test_pipe;
+ {
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ run_loop.AddHandler(&handler,
+ test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ }
+ EXPECT_EQ(1, handler.error_count());
+ EXPECT_EQ(MOJO_RESULT_ABORTED, handler.last_error_result());
+}
+
+TEST_F(RunLoopTest, Current) {
+ EXPECT_TRUE(RunLoop::current() == nullptr);
+ {
+ RunLoop run_loop;
+ EXPECT_EQ(&run_loop, RunLoop::current());
+ }
+ EXPECT_TRUE(RunLoop::current() == nullptr);
+}
+
+class NestingRunLoopHandler : public TestRunLoopHandler {
+ public:
+ static const size_t kDepthLimit;
+ static const char kSignalMagic;
+
+ NestingRunLoopHandler()
+ : run_loop_(nullptr),
+ pipe_(nullptr),
+ depth_(0),
+ reached_depth_limit_(false) {}
+
+ ~NestingRunLoopHandler() override {}
+
+ void set_run_loop(RunLoop* run_loop) { run_loop_ = run_loop; }
+ void set_pipe(MessagePipe* pipe) { pipe_ = pipe; }
+ bool reached_depth_limit() const { return reached_depth_limit_; }
+
+ // RunLoopHandler:
+ void OnHandleReady(const Handle& handle) override {
+ TestRunLoopHandler::OnHandleReady(handle);
+ EXPECT_EQ(handle.value(), pipe_->handle0.get().value());
+
+ ReadSignal();
+ size_t current_depth = ++depth_;
+ if (current_depth < kDepthLimit) {
+ WriteSignal();
+ run_loop_->Run();
+ if (current_depth == kDepthLimit - 1) {
+ // The topmost loop Quit()-ed, so its parent takes back the
+ // control without exeeding deadline.
+ EXPECT_EQ(error_count(), 0);
+ } else {
+ EXPECT_EQ(error_count(), 1);
+ }
+
+ } else {
+ EXPECT_EQ(current_depth, kDepthLimit);
+ reached_depth_limit_ = true;
+ run_loop_->Quit();
+ }
+ --depth_;
+ }
+
+ void WriteSignal() {
+ char write_byte = kSignalMagic;
+ MojoResult write_result =
+ WriteMessageRaw(pipe_->handle1.get(), &write_byte, 1, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ EXPECT_EQ(write_result, MOJO_RESULT_OK);
+ }
+
+ void ReadSignal() {
+ char read_byte = 0;
+ uint32_t bytes_read = 1;
+ uint32_t handles_read = 0;
+ MojoResult read_result =
+ ReadMessageRaw(pipe_->handle0.get(), &read_byte, &bytes_read, nullptr,
+ &handles_read, MOJO_READ_MESSAGE_FLAG_NONE);
+ EXPECT_EQ(read_result, MOJO_RESULT_OK);
+ EXPECT_EQ(read_byte, kSignalMagic);
+ }
+
+ private:
+ RunLoop* run_loop_;
+ MessagePipe* pipe_;
+ size_t depth_;
+ bool reached_depth_limit_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(NestingRunLoopHandler);
+};
+
+const size_t NestingRunLoopHandler::kDepthLimit = 10;
+const char NestingRunLoopHandler::kSignalMagic = 'X';
+
+TEST_F(RunLoopTest, NestedRun) {
+ NestingRunLoopHandler handler;
+ MessagePipe test_pipe;
+ RunLoop run_loop;
+ handler.set_run_loop(&run_loop);
+ handler.set_pipe(&test_pipe);
+ run_loop.AddHandler(&handler, test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ static_cast<MojoDeadline>(10000));
+ handler.WriteSignal();
+ run_loop.Run();
+
+ EXPECT_TRUE(handler.reached_depth_limit());
+ // Got MOJO_RESULT_DEADLINE_EXCEEDED once then removed from the
+ // RunLoop's handler list.
+ EXPECT_EQ(handler.error_count(), 1);
+ EXPECT_EQ(handler.last_error_result(), MOJO_RESULT_DEADLINE_EXCEEDED);
+}
+
+struct Task {
+ Task(int num, std::vector<int>* sequence) : num(num), sequence(sequence) {}
+
+ void Run() const { sequence->push_back(num); }
+
+ int num;
+ std::vector<int>* sequence;
+};
+
+TEST_F(RunLoopTest, DelayedTaskOrder) {
+ std::vector<int> sequence;
+ RunLoop run_loop;
+ run_loop.PostDelayedTask(Closure(Task(1, &sequence)), 0);
+ run_loop.PostDelayedTask(Closure(Task(2, &sequence)), 0);
+ run_loop.PostDelayedTask(Closure(Task(3, &sequence)), 0);
+ run_loop.RunUntilIdle();
+
+ ASSERT_EQ(3u, sequence.size());
+ EXPECT_EQ(1, sequence[0]);
+ EXPECT_EQ(2, sequence[1]);
+ EXPECT_EQ(3, sequence[2]);
+}
+
+struct QuittingTask {
+ explicit QuittingTask(RunLoop* run_loop) : run_loop(run_loop) {}
+
+ void Run() const { run_loop->Quit(); }
+
+ RunLoop* run_loop;
+};
+
+TEST_F(RunLoopTest, QuitFromDelayedTask) {
+ TestRunLoopHandler handler;
+ MessagePipe test_pipe;
+ RunLoop run_loop;
+ run_loop.AddHandler(&handler,
+ test_pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE);
+ run_loop.PostDelayedTask(Closure(QuittingTask(&run_loop)), 0);
+ run_loop.Run();
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/tests/thread_unittest.cc b/mojo/public/cpp/utility/tests/thread_unittest.cc
new file mode 100644
index 0000000..57c4ad9
--- /dev/null
+++ b/mojo/public/cpp/utility/tests/thread_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 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/public/cpp/utility/thread.h"
+
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class SetIntThread : public Thread {
+ public:
+ SetIntThread(int* int_to_set, int value)
+ : int_to_set_(int_to_set),
+ value_(value) {
+ }
+ SetIntThread(const Options& options, int* int_to_set, int value)
+ : Thread(options),
+ int_to_set_(int_to_set),
+ value_(value) {
+ }
+
+ ~SetIntThread() override {}
+
+ void Run() override { *int_to_set_ = value_; }
+
+ private:
+ int* const int_to_set_;
+ const int value_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SetIntThread);
+};
+
+TEST(ThreadTest, CreateAndJoin) {
+ int value = 0;
+
+ // Not starting the thread should result in a no-op.
+ {
+ SetIntThread thread(&value, 1234567);
+ }
+ EXPECT_EQ(0, value);
+
+ // Start and join.
+ {
+ SetIntThread thread(&value, 12345678);
+ thread.Start();
+ thread.Join();
+ EXPECT_EQ(12345678, value);
+ }
+
+ // Ditto, with non-default (but reasonable) stack size.
+ {
+ Thread::Options options;
+ options.set_stack_size(1024 * 1024); // 1 MB.
+ SetIntThread thread(options, &value, 12345678);
+ thread.Start();
+ thread.Join();
+ EXPECT_EQ(12345678, value);
+ }
+}
+
+// Tests of assertions for Debug builds.
+// Note: It's okay to create threads, despite gtest having to fork. (The threads
+// are in the child process.)
+#if !defined(NDEBUG)
+TEST(ThreadTest, DebugAssertionFailures) {
+ // Can only start once.
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ SetIntThread thread(&value, 1);
+ thread.Start();
+ thread.Start();
+ }, "");
+
+ // Must join (if you start).
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ SetIntThread thread(&value, 2);
+ thread.Start();
+ }, "");
+
+ // Can only join once.
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ SetIntThread thread(&value, 3);
+ thread.Start();
+ thread.Join();
+ thread.Join();
+ }, "");
+
+ // Stack too big (we're making certain assumptions here).
+ EXPECT_DEATH_IF_SUPPORTED({
+ int value = 0;
+ Thread::Options options;
+ options.set_stack_size(static_cast<size_t>(-1));
+ SetIntThread thread(options, &value, 4);
+ thread.Start();
+ thread.Join();
+ }, "");
+}
+#endif // !defined(NDEBUG)
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/utility/thread.h b/mojo/public/cpp/utility/thread.h
new file mode 100644
index 0000000..b7d10ee
--- /dev/null
+++ b/mojo/public/cpp/utility/thread.h
@@ -0,0 +1,62 @@
+// Copyright 2014 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 MOJO_PUBLIC_CPP_UTILITY_THREAD_H_
+#define MOJO_PUBLIC_CPP_UTILITY_THREAD_H_
+
+#ifdef _WIN32
+#error "Not implemented: See crbug.com/342893."
+#endif
+
+#include <pthread.h>
+#include <stddef.h>
+
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+// This class is thread-friendly, not thread-safe (e.g., you mustn't call
+// |Join()| from multiple threads and/or simultaneously try to destroy the
+// object).
+class Thread {
+ public:
+ // TODO(vtl): Support non-joinable? priority?
+ class Options {
+ public:
+ Options() : stack_size_(0) {}
+
+ // A stack size of 0 means the default.
+ size_t stack_size() const { return stack_size_; }
+ void set_stack_size(size_t stack_size) { stack_size_ = stack_size; }
+
+ private:
+ size_t stack_size_;
+
+ // Copy and assign allowed.
+ };
+
+ // TODO(vtl): Add name or name prefix?
+ Thread();
+ explicit Thread(const Options& options);
+ virtual ~Thread();
+
+ void Start();
+ void Join();
+
+ virtual void Run() = 0;
+
+ private:
+ static void* ThreadRunTrampoline(void* arg);
+
+ const Options options_;
+ pthread_t thread_;
+ bool started_;
+ bool joined_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Thread);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_UTILITY_THREAD_H_