diff options
author | rockot <rockot@chromium.org> | 2015-11-12 17:33:59 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-13 01:34:47 +0000 |
commit | 85dce086001825a2faa4e75755a669f5e08a1cad (patch) | |
tree | 722de1d974f799b3d1ee1ca4c81bb8b0fa75a95d /mojo/public/cpp/utility | |
parent | 415b73b1a400a994a86e6f29709aa0271e895dd5 (diff) | |
download | chromium_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.gn | 35 | ||||
-rw-r--r-- | mojo/public/cpp/utility/lib/mutex.cc | 52 | ||||
-rw-r--r-- | mojo/public/cpp/utility/lib/run_loop.cc | 267 | ||||
-rw-r--r-- | mojo/public/cpp/utility/lib/thread.cc | 64 | ||||
-rw-r--r-- | mojo/public/cpp/utility/lib/thread_local.h | 54 | ||||
-rw-r--r-- | mojo/public/cpp/utility/lib/thread_local_posix.cc | 39 | ||||
-rw-r--r-- | mojo/public/cpp/utility/lib/thread_local_win.cc | 39 | ||||
-rw-r--r-- | mojo/public/cpp/utility/mutex.h | 70 | ||||
-rw-r--r-- | mojo/public/cpp/utility/run_loop.h | 156 | ||||
-rw-r--r-- | mojo/public/cpp/utility/run_loop_handler.h | 25 | ||||
-rw-r--r-- | mojo/public/cpp/utility/tests/BUILD.gn | 32 | ||||
-rw-r--r-- | mojo/public/cpp/utility/tests/mutex_unittest.cc | 259 | ||||
-rw-r--r-- | mojo/public/cpp/utility/tests/run_loop_unittest.cc | 425 | ||||
-rw-r--r-- | mojo/public/cpp/utility/tests/thread_unittest.cc | 106 | ||||
-rw-r--r-- | mojo/public/cpp/utility/thread.h | 62 |
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_ |