Move message_loop to the message_loop directory.
Keep a forwarding header to avoid updating all callers.
BUG=
TBR=avi
Review URL: https://chromiumcodereview.appspot.com/15682017
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@206279 0039d316-1c4b-4281-b951-d872f2087c98
CrOS-Libchrome-Original-Commit: 186ced8ea96ae3fd284b2f6252b6cf74f549a061
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
new file mode 100644
index 0000000..0364a52
--- /dev/null
+++ b/base/message_loop/message_loop.cc
@@ -0,0 +1,834 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop/message_loop.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy_impl.h"
+#include "base/message_pump_default.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/run_loop.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread_local.h"
+#include "base/time.h"
+#include "base/tracked_objects.h"
+
+#if defined(OS_MACOSX)
+#include "base/message_pump_mac.h"
+#endif
+#if defined(OS_POSIX) && !defined(OS_IOS)
+#include "base/message_pump_libevent.h"
+#endif
+#if defined(OS_ANDROID)
+#include "base/message_pump_android.h"
+#endif
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#endif
+
+namespace base {
+
+namespace {
+
+// A lazily created thread local storage for quick access to a thread's message
+// loop, if one exists. This should be safe and free of static constructors.
+LazyInstance<base::ThreadLocalPointer<MessageLoop> > lazy_tls_ptr =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Logical events for Histogram profiling. Run with -message-loop-histogrammer
+// to get an accounting of messages and actions taken on each thread.
+const int kTaskRunEvent = 0x1;
+const int kTimerEvent = 0x2;
+
+// Provide range of message IDs for use in histogramming and debug display.
+const int kLeastNonZeroMessageId = 1;
+const int kMaxMessageId = 1099;
+const int kNumberOfDistinctMessagesDisplayed = 1100;
+
+// Provide a macro that takes an expression (such as a constant, or macro
+// constant) and creates a pair to initalize an array of pairs. In this case,
+// our pair consists of the expressions value, and the "stringized" version
+// of the expression (i.e., the exrpression put in quotes). For example, if
+// we have:
+// #define FOO 2
+// #define BAR 5
+// then the following:
+// VALUE_TO_NUMBER_AND_NAME(FOO + BAR)
+// will expand to:
+// {7, "FOO + BAR"}
+// We use the resulting array as an argument to our histogram, which reads the
+// number as a bucket identifier, and proceeds to use the corresponding name
+// in the pair (i.e., the quoted string) when printing out a histogram.
+#define VALUE_TO_NUMBER_AND_NAME(name) {name, #name},
+
+const LinearHistogram::DescriptionPair event_descriptions_[] = {
+ // Provide some pretty print capability in our histogram for our internal
+ // messages.
+
+ // A few events we handle (kindred to messages), and used to profile actions.
+ VALUE_TO_NUMBER_AND_NAME(kTaskRunEvent)
+ VALUE_TO_NUMBER_AND_NAME(kTimerEvent)
+
+ {-1, NULL} // The list must be null terminated, per API to histogram.
+};
+
+bool enable_histogrammer_ = false;
+
+MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = NULL;
+
+// Create a process-wide unique ID to represent this task in trace events. This
+// will be mangled with a Process ID hash to reduce the likelyhood of colliding
+// with MessageLoop pointers on other processes.
+uint64 GetTaskTraceID(const PendingTask& task, MessageLoop* loop) {
+ return (static_cast<uint64>(task.sequence_num) << 32) |
+ static_cast<uint64>(reinterpret_cast<intptr_t>(loop));
+}
+
+} // namespace
+
+//------------------------------------------------------------------------------
+
+#if defined(OS_WIN)
+
+// Upon a SEH exception in this thread, it restores the original unhandled
+// exception filter.
+static int SEHFilter(LPTOP_LEVEL_EXCEPTION_FILTER old_filter) {
+ ::SetUnhandledExceptionFilter(old_filter);
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+// Retrieves a pointer to the current unhandled exception filter. There
+// is no standalone getter method.
+static LPTOP_LEVEL_EXCEPTION_FILTER GetTopSEHFilter() {
+ LPTOP_LEVEL_EXCEPTION_FILTER top_filter = NULL;
+ top_filter = ::SetUnhandledExceptionFilter(0);
+ ::SetUnhandledExceptionFilter(top_filter);
+ return top_filter;
+}
+
+#endif // defined(OS_WIN)
+
+//------------------------------------------------------------------------------
+
+MessageLoop::TaskObserver::TaskObserver() {
+}
+
+MessageLoop::TaskObserver::~TaskObserver() {
+}
+
+MessageLoop::DestructionObserver::~DestructionObserver() {
+}
+
+//------------------------------------------------------------------------------
+
+MessageLoop::MessageLoop(Type type)
+ : type_(type),
+ nestable_tasks_allowed_(true),
+ exception_restoration_(false),
+ message_histogram_(NULL),
+ run_loop_(NULL),
+#if defined(OS_WIN)
+ os_modal_loop_(false),
+#endif // OS_WIN
+ next_sequence_num_(0) {
+ DCHECK(!current()) << "should only have one message loop per thread";
+ lazy_tls_ptr.Pointer()->Set(this);
+
+ message_loop_proxy_ = new MessageLoopProxyImpl();
+ thread_task_runner_handle_.reset(
+ new ThreadTaskRunnerHandle(message_loop_proxy_));
+
+// TODO(rvargas): Get rid of the OS guards.
+#if defined(OS_WIN)
+#define MESSAGE_PUMP_UI new MessagePumpForUI()
+#define MESSAGE_PUMP_IO new MessagePumpForIO()
+#elif defined(OS_IOS)
+#define MESSAGE_PUMP_UI MessagePumpMac::Create()
+#define MESSAGE_PUMP_IO new MessagePumpIOSForIO()
+#elif defined(OS_MACOSX)
+#define MESSAGE_PUMP_UI MessagePumpMac::Create()
+#define MESSAGE_PUMP_IO new MessagePumpLibevent()
+#elif defined(OS_NACL)
+// Currently NaCl doesn't have a UI MessageLoop.
+// TODO(abarth): Figure out if we need this.
+#define MESSAGE_PUMP_UI NULL
+// ipc_channel_nacl.cc uses a worker thread to do socket reads currently, and
+// doesn't require extra support for watching file descriptors.
+#define MESSAGE_PUMP_IO new MessagePumpDefault();
+#elif defined(OS_POSIX) // POSIX but not MACOSX.
+#define MESSAGE_PUMP_UI new MessagePumpForUI()
+#define MESSAGE_PUMP_IO new MessagePumpLibevent()
+#else
+#error Not implemented
+#endif
+
+ if (type_ == TYPE_UI) {
+ if (message_pump_for_ui_factory_)
+ pump_ = message_pump_for_ui_factory_();
+ else
+ pump_ = MESSAGE_PUMP_UI;
+ } else if (type_ == TYPE_IO) {
+ pump_ = MESSAGE_PUMP_IO;
+ } else {
+ DCHECK_EQ(TYPE_DEFAULT, type_);
+ pump_ = new MessagePumpDefault();
+ }
+}
+
+MessageLoop::~MessageLoop() {
+ DCHECK_EQ(this, current());
+
+ DCHECK(!run_loop_);
+
+ // Clean up any unprocessed tasks, but take care: deleting a task could
+ // result in the addition of more tasks (e.g., via DeleteSoon). We set a
+ // limit on the number of times we will allow a deleted task to generate more
+ // tasks. Normally, we should only pass through this loop once or twice. If
+ // we end up hitting the loop limit, then it is probably due to one task that
+ // is being stubborn. Inspect the queues to see who is left.
+ bool did_work;
+ for (int i = 0; i < 100; ++i) {
+ DeletePendingTasks();
+ ReloadWorkQueue();
+ // If we end up with empty queues, then break out of the loop.
+ did_work = DeletePendingTasks();
+ if (!did_work)
+ break;
+ }
+ DCHECK(!did_work);
+
+ // Let interested parties have one last shot at accessing this.
+ FOR_EACH_OBSERVER(DestructionObserver, destruction_observers_,
+ WillDestroyCurrentMessageLoop());
+
+ thread_task_runner_handle_.reset();
+
+ // Tell the message_loop_proxy that we are dying.
+ static_cast<MessageLoopProxyImpl*>(message_loop_proxy_.get())->
+ WillDestroyCurrentMessageLoop();
+ message_loop_proxy_ = NULL;
+
+ // OK, now make it so that no one can find us.
+ lazy_tls_ptr.Pointer()->Set(NULL);
+
+#if defined(OS_WIN)
+ // If we left the high-resolution timer activated, deactivate it now.
+ // Doing this is not-critical, it is mainly to make sure we track
+ // the high resolution timer activations properly in our unit tests.
+ if (!high_resolution_timer_expiration_.is_null()) {
+ Time::ActivateHighResolutionTimer(false);
+ high_resolution_timer_expiration_ = TimeTicks();
+ }
+#endif
+}
+
+// static
+MessageLoop* MessageLoop::current() {
+ // TODO(darin): sadly, we cannot enable this yet since people call us even
+ // when they have no intention of using us.
+ // DCHECK(loop) << "Ouch, did you forget to initialize me?";
+ return lazy_tls_ptr.Pointer()->Get();
+}
+
+// static
+void MessageLoop::EnableHistogrammer(bool enable) {
+ enable_histogrammer_ = enable;
+}
+
+// static
+bool MessageLoop::InitMessagePumpForUIFactory(MessagePumpFactory* factory) {
+ if (message_pump_for_ui_factory_)
+ return false;
+
+ message_pump_for_ui_factory_ = factory;
+ return true;
+}
+
+void MessageLoop::AddDestructionObserver(
+ DestructionObserver* destruction_observer) {
+ DCHECK_EQ(this, current());
+ destruction_observers_.AddObserver(destruction_observer);
+}
+
+void MessageLoop::RemoveDestructionObserver(
+ DestructionObserver* destruction_observer) {
+ DCHECK_EQ(this, current());
+ destruction_observers_.RemoveObserver(destruction_observer);
+}
+
+void MessageLoop::PostTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task) {
+ DCHECK(!task.is_null()) << from_here.ToString();
+ PendingTask pending_task(
+ from_here, task, CalculateDelayedRuntime(TimeDelta()), true);
+ AddToIncomingQueue(&pending_task, false);
+}
+
+bool MessageLoop::TryPostTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task) {
+ DCHECK(!task.is_null()) << from_here.ToString();
+ PendingTask pending_task(
+ from_here, task, CalculateDelayedRuntime(TimeDelta()), true);
+ return AddToIncomingQueue(&pending_task, true);
+}
+
+void MessageLoop::PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task,
+ TimeDelta delay) {
+ DCHECK(!task.is_null()) << from_here.ToString();
+ PendingTask pending_task(
+ from_here, task, CalculateDelayedRuntime(delay), true);
+ AddToIncomingQueue(&pending_task, false);
+}
+
+void MessageLoop::PostNonNestableTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task) {
+ DCHECK(!task.is_null()) << from_here.ToString();
+ PendingTask pending_task(
+ from_here, task, CalculateDelayedRuntime(TimeDelta()), false);
+ AddToIncomingQueue(&pending_task, false);
+}
+
+void MessageLoop::PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const Closure& task,
+ TimeDelta delay) {
+ DCHECK(!task.is_null()) << from_here.ToString();
+ PendingTask pending_task(
+ from_here, task, CalculateDelayedRuntime(delay), false);
+ AddToIncomingQueue(&pending_task, false);
+}
+
+void MessageLoop::Run() {
+ RunLoop run_loop;
+ run_loop.Run();
+}
+
+void MessageLoop::RunUntilIdle() {
+ RunLoop run_loop;
+ run_loop.RunUntilIdle();
+}
+
+void MessageLoop::QuitWhenIdle() {
+ DCHECK_EQ(this, current());
+ if (run_loop_) {
+ run_loop_->quit_when_idle_received_ = true;
+ } else {
+ NOTREACHED() << "Must be inside Run to call Quit";
+ }
+}
+
+void MessageLoop::QuitNow() {
+ DCHECK_EQ(this, current());
+ if (run_loop_) {
+ pump_->Quit();
+ } else {
+ NOTREACHED() << "Must be inside Run to call Quit";
+ }
+}
+
+bool MessageLoop::IsType(Type type) const {
+ return type_ == type;
+}
+
+static void QuitCurrentWhenIdle() {
+ MessageLoop::current()->QuitWhenIdle();
+}
+
+// static
+Closure MessageLoop::QuitWhenIdleClosure() {
+ return Bind(&QuitCurrentWhenIdle);
+}
+
+void MessageLoop::SetNestableTasksAllowed(bool allowed) {
+ if (nestable_tasks_allowed_ != allowed) {
+ nestable_tasks_allowed_ = allowed;
+ if (!nestable_tasks_allowed_)
+ return;
+ // Start the native pump if we are not already pumping.
+ pump_->ScheduleWork();
+ }
+}
+
+bool MessageLoop::NestableTasksAllowed() const {
+ return nestable_tasks_allowed_;
+}
+
+bool MessageLoop::IsNested() {
+ return run_loop_->run_depth_ > 1;
+}
+
+void MessageLoop::AddTaskObserver(TaskObserver* task_observer) {
+ DCHECK_EQ(this, current());
+ task_observers_.AddObserver(task_observer);
+}
+
+void MessageLoop::RemoveTaskObserver(TaskObserver* task_observer) {
+ DCHECK_EQ(this, current());
+ task_observers_.RemoveObserver(task_observer);
+}
+
+void MessageLoop::AssertIdle() const {
+ // We only check |incoming_queue_|, since we don't want to lock |work_queue_|.
+ AutoLock lock(incoming_queue_lock_);
+ DCHECK(incoming_queue_.empty());
+}
+
+bool MessageLoop::is_running() const {
+ DCHECK_EQ(this, current());
+ return run_loop_ != NULL;
+}
+
+//------------------------------------------------------------------------------
+
+// Runs the loop in two different SEH modes:
+// enable_SEH_restoration_ = false : any unhandled exception goes to the last
+// one that calls SetUnhandledExceptionFilter().
+// enable_SEH_restoration_ = true : any unhandled exception goes to the filter
+// that was existed before the loop was run.
+void MessageLoop::RunHandler() {
+#if defined(OS_WIN)
+ if (exception_restoration_) {
+ RunInternalInSEHFrame();
+ return;
+ }
+#endif
+
+ RunInternal();
+}
+
+#if defined(OS_WIN)
+__declspec(noinline) void MessageLoop::RunInternalInSEHFrame() {
+ LPTOP_LEVEL_EXCEPTION_FILTER current_filter = GetTopSEHFilter();
+ __try {
+ RunInternal();
+ } __except(SEHFilter(current_filter)) {
+ }
+ return;
+}
+#endif
+
+void MessageLoop::RunInternal() {
+ DCHECK_EQ(this, current());
+
+ StartHistogrammer();
+
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ if (run_loop_->dispatcher_ && type() == TYPE_UI) {
+ static_cast<MessagePumpForUI*>(pump_.get())->
+ RunWithDispatcher(this, run_loop_->dispatcher_);
+ return;
+ }
+#endif
+
+ pump_->Run(this);
+}
+
+bool MessageLoop::ProcessNextDelayedNonNestableTask() {
+ if (run_loop_->run_depth_ != 1)
+ return false;
+
+ if (deferred_non_nestable_work_queue_.empty())
+ return false;
+
+ PendingTask pending_task = deferred_non_nestable_work_queue_.front();
+ deferred_non_nestable_work_queue_.pop();
+
+ RunTask(pending_task);
+ return true;
+}
+
+void MessageLoop::RunTask(const PendingTask& pending_task) {
+ TRACE_EVENT_FLOW_END0("task", "MessageLoop::PostTask",
+ TRACE_ID_MANGLE(GetTaskTraceID(pending_task, this)));
+ TRACE_EVENT2("task", "MessageLoop::RunTask",
+ "src_file", pending_task.posted_from.file_name(),
+ "src_func", pending_task.posted_from.function_name());
+ DCHECK(nestable_tasks_allowed_);
+ // Execute the task and assume the worst: It is probably not reentrant.
+ nestable_tasks_allowed_ = false;
+
+ // Before running the task, store the program counter where it was posted
+ // and deliberately alias it to ensure it is on the stack if the task
+ // crashes. Be careful not to assume that the variable itself will have the
+ // expected value when displayed by the optimizer in an optimized build.
+ // Look at a memory dump of the stack.
+ const void* program_counter =
+ pending_task.posted_from.program_counter();
+ debug::Alias(&program_counter);
+
+ HistogramEvent(kTaskRunEvent);
+
+ tracked_objects::TrackedTime start_time =
+ tracked_objects::ThreadData::NowForStartOfRun(pending_task.birth_tally);
+
+ FOR_EACH_OBSERVER(TaskObserver, task_observers_,
+ WillProcessTask(pending_task));
+ pending_task.task.Run();
+ FOR_EACH_OBSERVER(TaskObserver, task_observers_,
+ DidProcessTask(pending_task));
+
+ tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(pending_task,
+ start_time, tracked_objects::ThreadData::NowForEndOfRun());
+
+ nestable_tasks_allowed_ = true;
+}
+
+bool MessageLoop::DeferOrRunPendingTask(const PendingTask& pending_task) {
+ if (pending_task.nestable || run_loop_->run_depth_ == 1) {
+ RunTask(pending_task);
+ // Show that we ran a task (Note: a new one might arrive as a
+ // consequence!).
+ return true;
+ }
+
+ // We couldn't run the task now because we're in a nested message loop
+ // and the task isn't nestable.
+ deferred_non_nestable_work_queue_.push(pending_task);
+ return false;
+}
+
+void MessageLoop::AddToDelayedWorkQueue(const PendingTask& pending_task) {
+ // Move to the delayed work queue.
+ delayed_work_queue_.push(pending_task);
+}
+
+void MessageLoop::ReloadWorkQueue() {
+ // We can improve performance of our loading tasks from incoming_queue_ to
+ // work_queue_ by waiting until the last minute (work_queue_ is empty) to
+ // load. That reduces the number of locks-per-task significantly when our
+ // queues get large.
+ if (!work_queue_.empty())
+ return; // Wait till we *really* need to lock and load.
+
+ // Acquire all we can from the inter-thread queue with one lock acquisition.
+ {
+ AutoLock lock(incoming_queue_lock_);
+ if (incoming_queue_.empty())
+ return;
+ incoming_queue_.Swap(&work_queue_); // Constant time
+ DCHECK(incoming_queue_.empty());
+ }
+}
+
+bool MessageLoop::DeletePendingTasks() {
+ bool did_work = !work_queue_.empty();
+ while (!work_queue_.empty()) {
+ PendingTask pending_task = work_queue_.front();
+ work_queue_.pop();
+ if (!pending_task.delayed_run_time.is_null()) {
+ // We want to delete delayed tasks in the same order in which they would
+ // normally be deleted in case of any funny dependencies between delayed
+ // tasks.
+ AddToDelayedWorkQueue(pending_task);
+ }
+ }
+ did_work |= !deferred_non_nestable_work_queue_.empty();
+ while (!deferred_non_nestable_work_queue_.empty()) {
+ deferred_non_nestable_work_queue_.pop();
+ }
+ did_work |= !delayed_work_queue_.empty();
+
+ // Historically, we always delete the task regardless of valgrind status. It's
+ // not completely clear why we want to leak them in the loops above. This
+ // code is replicating legacy behavior, and should not be considered
+ // absolutely "correct" behavior. See TODO above about deleting all tasks
+ // when it's safe.
+ while (!delayed_work_queue_.empty()) {
+ delayed_work_queue_.pop();
+ }
+ return did_work;
+}
+
+TimeTicks MessageLoop::CalculateDelayedRuntime(TimeDelta delay) {
+ TimeTicks delayed_run_time;
+ if (delay > TimeDelta()) {
+ delayed_run_time = TimeTicks::Now() + delay;
+
+#if defined(OS_WIN)
+ if (high_resolution_timer_expiration_.is_null()) {
+ // Windows timers are granular to 15.6ms. If we only set high-res
+ // timers for those under 15.6ms, then a 18ms timer ticks at ~32ms,
+ // which as a percentage is pretty inaccurate. So enable high
+ // res timers for any timer which is within 2x of the granularity.
+ // This is a tradeoff between accuracy and power management.
+ bool needs_high_res_timers = delay.InMilliseconds() <
+ (2 * Time::kMinLowResolutionThresholdMs);
+ if (needs_high_res_timers) {
+ if (Time::ActivateHighResolutionTimer(true)) {
+ high_resolution_timer_expiration_ = TimeTicks::Now() +
+ TimeDelta::FromMilliseconds(kHighResolutionTimerModeLeaseTimeMs);
+ }
+ }
+ }
+#endif
+ } else {
+ DCHECK_EQ(delay.InMilliseconds(), 0) << "delay should not be negative";
+ }
+
+#if defined(OS_WIN)
+ if (!high_resolution_timer_expiration_.is_null()) {
+ if (TimeTicks::Now() > high_resolution_timer_expiration_) {
+ Time::ActivateHighResolutionTimer(false);
+ high_resolution_timer_expiration_ = TimeTicks();
+ }
+ }
+#endif
+
+ return delayed_run_time;
+}
+
+// Possibly called on a background thread!
+bool MessageLoop::AddToIncomingQueue(PendingTask* pending_task,
+ bool use_try_lock) {
+ // Warning: Don't try to short-circuit, and handle this thread's tasks more
+ // directly, as it could starve handling of foreign threads. Put every task
+ // into this queue.
+
+ scoped_refptr<MessagePump> pump;
+ {
+ if (use_try_lock) {
+ if (!incoming_queue_lock_.Try()) {
+ pending_task->task.Reset();
+ return false;
+ }
+ } else {
+ incoming_queue_lock_.Acquire();
+ }
+ AutoLock locked(incoming_queue_lock_, AutoLock::AlreadyAcquired());
+ // Initialize the sequence number. The sequence number is used for delayed
+ // tasks (to faciliate FIFO sorting when two tasks have the same
+ // delayed_run_time value) and for identifying the task in about:tracing.
+ pending_task->sequence_num = next_sequence_num_++;
+
+ TRACE_EVENT_FLOW_BEGIN0("task", "MessageLoop::PostTask",
+ TRACE_ID_MANGLE(GetTaskTraceID(*pending_task, this)));
+
+ bool was_empty = incoming_queue_.empty();
+ incoming_queue_.push(*pending_task);
+ pending_task->task.Reset();
+ if (!was_empty)
+ return true; // Someone else should have started the sub-pump.
+
+ pump = pump_;
+ }
+ // Since the incoming_queue_ may contain a task that destroys this message
+ // loop, we cannot exit incoming_queue_lock_ until we are done with |this|.
+ // We use a stack-based reference to the message pump so that we can call
+ // ScheduleWork outside of incoming_queue_lock_.
+
+ pump->ScheduleWork();
+ return true;
+}
+
+//------------------------------------------------------------------------------
+// Method and data for histogramming events and actions taken by each instance
+// on each thread.
+
+void MessageLoop::StartHistogrammer() {
+#if !defined(OS_NACL) // NaCl build has no metrics code.
+ if (enable_histogrammer_ && !message_histogram_
+ && StatisticsRecorder::IsActive()) {
+ DCHECK(!thread_name_.empty());
+ message_histogram_ = LinearHistogram::FactoryGetWithRangeDescription(
+ "MsgLoop:" + thread_name_,
+ kLeastNonZeroMessageId, kMaxMessageId,
+ kNumberOfDistinctMessagesDisplayed,
+ message_histogram_->kHexRangePrintingFlag,
+ event_descriptions_);
+ }
+#endif
+}
+
+void MessageLoop::HistogramEvent(int event) {
+#if !defined(OS_NACL)
+ if (message_histogram_)
+ message_histogram_->Add(event);
+#endif
+}
+
+bool MessageLoop::DoWork() {
+ if (!nestable_tasks_allowed_) {
+ // Task can't be executed right now.
+ return false;
+ }
+
+ for (;;) {
+ ReloadWorkQueue();
+ if (work_queue_.empty())
+ break;
+
+ // Execute oldest task.
+ do {
+ PendingTask pending_task = work_queue_.front();
+ work_queue_.pop();
+ if (!pending_task.delayed_run_time.is_null()) {
+ AddToDelayedWorkQueue(pending_task);
+ // If we changed the topmost task, then it is time to reschedule.
+ if (delayed_work_queue_.top().task.Equals(pending_task.task))
+ pump_->ScheduleDelayedWork(pending_task.delayed_run_time);
+ } else {
+ if (DeferOrRunPendingTask(pending_task))
+ return true;
+ }
+ } while (!work_queue_.empty());
+ }
+
+ // Nothing happened.
+ return false;
+}
+
+bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) {
+ if (!nestable_tasks_allowed_ || delayed_work_queue_.empty()) {
+ recent_time_ = *next_delayed_work_time = TimeTicks();
+ return false;
+ }
+
+ // When we "fall behind," there will be a lot of tasks in the delayed work
+ // queue that are ready to run. To increase efficiency when we fall behind,
+ // we will only call Time::Now() intermittently, and then process all tasks
+ // that are ready to run before calling it again. As a result, the more we
+ // fall behind (and have a lot of ready-to-run delayed tasks), the more
+ // efficient we'll be at handling the tasks.
+
+ TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time;
+ if (next_run_time > recent_time_) {
+ recent_time_ = TimeTicks::Now(); // Get a better view of Now();
+ if (next_run_time > recent_time_) {
+ *next_delayed_work_time = next_run_time;
+ return false;
+ }
+ }
+
+ PendingTask pending_task = delayed_work_queue_.top();
+ delayed_work_queue_.pop();
+
+ if (!delayed_work_queue_.empty())
+ *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time;
+
+ return DeferOrRunPendingTask(pending_task);
+}
+
+bool MessageLoop::DoIdleWork() {
+ if (ProcessNextDelayedNonNestableTask())
+ return true;
+
+ if (run_loop_->quit_when_idle_received_)
+ pump_->Quit();
+
+ return false;
+}
+
+void MessageLoop::DeleteSoonInternal(const tracked_objects::Location& from_here,
+ void(*deleter)(const void*),
+ const void* object) {
+ PostNonNestableTask(from_here, Bind(deleter, object));
+}
+
+void MessageLoop::ReleaseSoonInternal(
+ const tracked_objects::Location& from_here,
+ void(*releaser)(const void*),
+ const void* object) {
+ PostNonNestableTask(from_here, Bind(releaser, object));
+}
+
+//------------------------------------------------------------------------------
+// MessageLoopForUI
+
+#if defined(OS_WIN)
+void MessageLoopForUI::DidProcessMessage(const MSG& message) {
+ pump_win()->DidProcessMessage(message);
+}
+#endif // defined(OS_WIN)
+
+#if defined(OS_ANDROID)
+void MessageLoopForUI::Start() {
+ // No Histogram support for UI message loop as it is managed by Java side
+ static_cast<MessagePumpForUI*>(pump_.get())->Start(this);
+}
+#endif
+
+#if defined(OS_IOS)
+void MessageLoopForUI::Attach() {
+ static_cast<MessagePumpUIApplication*>(pump_.get())->Attach(this);
+}
+#endif
+
+#if !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID)
+void MessageLoopForUI::AddObserver(Observer* observer) {
+ pump_ui()->AddObserver(observer);
+}
+
+void MessageLoopForUI::RemoveObserver(Observer* observer) {
+ pump_ui()->RemoveObserver(observer);
+}
+
+#endif // !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID)
+
+//------------------------------------------------------------------------------
+// MessageLoopForIO
+
+#if defined(OS_WIN)
+
+void MessageLoopForIO::RegisterIOHandler(HANDLE file, IOHandler* handler) {
+ pump_io()->RegisterIOHandler(file, handler);
+}
+
+bool MessageLoopForIO::RegisterJobObject(HANDLE job, IOHandler* handler) {
+ return pump_io()->RegisterJobObject(job, handler);
+}
+
+bool MessageLoopForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
+ return pump_io()->WaitForIOCompletion(timeout, filter);
+}
+
+#elif defined(OS_IOS)
+
+bool MessageLoopForIO::WatchFileDescriptor(int fd,
+ bool persistent,
+ Mode mode,
+ FileDescriptorWatcher *controller,
+ Watcher *delegate) {
+ return pump_io()->WatchFileDescriptor(
+ fd,
+ persistent,
+ mode,
+ controller,
+ delegate);
+}
+
+#elif defined(OS_POSIX) && !defined(OS_NACL)
+
+bool MessageLoopForIO::WatchFileDescriptor(int fd,
+ bool persistent,
+ Mode mode,
+ FileDescriptorWatcher *controller,
+ Watcher *delegate) {
+ return pump_libevent()->WatchFileDescriptor(
+ fd,
+ persistent,
+ mode,
+ controller,
+ delegate);
+}
+
+#endif
+
+} // namespace base
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
new file mode 100644
index 0000000..d26b673
--- /dev/null
+++ b/base/message_loop/message_loop.h
@@ -0,0 +1,733 @@
+// 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 BASE_MESSAGE_LOOP_MESSAGE_LOOP_H_
+#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_H_
+
+#include <queue>
+#include <string>
+
+#include "base/base_export.h"
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/message_pump.h"
+#include "base/observer_list.h"
+#include "base/pending_task.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "base/synchronization/lock.h"
+#include "base/tracking_info.h"
+#include "base/time.h"
+
+#if defined(OS_WIN)
+// We need this to declare base::MessagePumpWin::Dispatcher, which we should
+// really just eliminate.
+#include "base/message_pump_win.h"
+#elif defined(OS_IOS)
+#include "base/message_pump_io_ios.h"
+#elif defined(OS_POSIX)
+#include "base/message_pump_libevent.h"
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+
+#if defined(USE_AURA) && defined(USE_X11) && !defined(OS_NACL)
+#include "base/message_pump_aurax11.h"
+#elif defined(USE_OZONE) && !defined(OS_NACL)
+#include "base/message_pump_ozone.h"
+#else
+#include "base/message_pump_gtk.h"
+#endif
+
+#endif
+#endif
+
+namespace base {
+class HistogramBase;
+class MessageLoopLockTest;
+class RunLoop;
+class ThreadTaskRunnerHandle;
+#if defined(OS_ANDROID)
+class MessagePumpForUI;
+#endif
+
+// A MessageLoop is used to process events for a particular thread. There is
+// at most one MessageLoop instance per thread.
+//
+// Events include at a minimum Task instances submitted to PostTask and its
+// variants. Depending on the type of message pump used by the MessageLoop
+// other events such as UI messages may be processed. On Windows APC calls (as
+// time permits) and signals sent to a registered set of HANDLEs may also be
+// processed.
+//
+// NOTE: Unless otherwise specified, a MessageLoop's methods may only be called
+// on the thread where the MessageLoop's Run method executes.
+//
+// NOTE: MessageLoop has task reentrancy protection. This means that if a
+// task is being processed, a second task cannot start until the first task is
+// finished. Reentrancy can happen when processing a task, and an inner
+// message pump is created. That inner pump then processes native messages
+// which could implicitly start an inner task. Inner message pumps are created
+// with dialogs (DialogBox), common dialogs (GetOpenFileName), OLE functions
+// (DoDragDrop), printer functions (StartDoc) and *many* others.
+//
+// Sample workaround when inner task processing is needed:
+// HRESULT hr;
+// {
+// MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
+// hr = DoDragDrop(...); // Implicitly runs a modal message loop.
+// }
+// // Process |hr| (the result returned by DoDragDrop()).
+//
+// Please be SURE your task is reentrant (nestable) and all global variables
+// are stable and accessible before calling SetNestableTasksAllowed(true).
+//
+class BASE_EXPORT MessageLoop : public base::MessagePump::Delegate {
+ public:
+
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ typedef base::MessagePumpDispatcher Dispatcher;
+ typedef base::MessagePumpObserver Observer;
+#endif
+
+ // A MessageLoop has a particular type, which indicates the set of
+ // asynchronous events it may process in addition to tasks and timers.
+ //
+ // TYPE_DEFAULT
+ // This type of ML only supports tasks and timers.
+ //
+ // TYPE_UI
+ // This type of ML also supports native UI events (e.g., Windows messages).
+ // See also MessageLoopForUI.
+ //
+ // TYPE_IO
+ // This type of ML also supports asynchronous IO. See also
+ // MessageLoopForIO.
+ //
+ enum Type {
+ TYPE_DEFAULT,
+ TYPE_UI,
+ TYPE_IO
+ };
+
+ // Normally, it is not necessary to instantiate a MessageLoop. Instead, it
+ // is typical to make use of the current thread's MessageLoop instance.
+ explicit MessageLoop(Type type = TYPE_DEFAULT);
+ virtual ~MessageLoop();
+
+ // Returns the MessageLoop object for the current thread, or null if none.
+ static MessageLoop* current();
+
+ static void EnableHistogrammer(bool enable_histogrammer);
+
+ typedef base::MessagePump* (MessagePumpFactory)();
+ // Uses the given base::MessagePumpForUIFactory to override the default
+ // MessagePump implementation for 'TYPE_UI'. Returns true if the factory
+ // was successfully registered.
+ static bool InitMessagePumpForUIFactory(MessagePumpFactory* factory);
+
+ // A DestructionObserver is notified when the current MessageLoop is being
+ // destroyed. These observers are notified prior to MessageLoop::current()
+ // being changed to return NULL. This gives interested parties the chance to
+ // do final cleanup that depends on the MessageLoop.
+ //
+ // NOTE: Any tasks posted to the MessageLoop during this notification will
+ // not be run. Instead, they will be deleted.
+ //
+ class BASE_EXPORT DestructionObserver {
+ public:
+ virtual void WillDestroyCurrentMessageLoop() = 0;
+
+ protected:
+ virtual ~DestructionObserver();
+ };
+
+ // Add a DestructionObserver, which will start receiving notifications
+ // immediately.
+ void AddDestructionObserver(DestructionObserver* destruction_observer);
+
+ // Remove a DestructionObserver. It is safe to call this method while a
+ // DestructionObserver is receiving a notification callback.
+ void RemoveDestructionObserver(DestructionObserver* destruction_observer);
+
+ // The "PostTask" family of methods call the task's Run method asynchronously
+ // from within a message loop at some point in the future.
+ //
+ // With the PostTask variant, tasks are invoked in FIFO order, inter-mixed
+ // with normal UI or IO event processing. With the PostDelayedTask variant,
+ // tasks are called after at least approximately 'delay_ms' have elapsed.
+ //
+ // The NonNestable variants work similarly except that they promise never to
+ // dispatch the task from a nested invocation of MessageLoop::Run. Instead,
+ // such tasks get deferred until the top-most MessageLoop::Run is executing.
+ //
+ // The MessageLoop takes ownership of the Task, and deletes it after it has
+ // been Run().
+ //
+ // PostTask(from_here, task) is equivalent to
+ // PostDelayedTask(from_here, task, 0).
+ //
+ // The TryPostTask is meant for the cases where the calling thread cannot
+ // block. If posting the task will block, the call returns false, the task
+ // is not posted but the task is consumed anyways.
+ //
+ // NOTE: These methods may be called on any thread. The Task will be invoked
+ // on the thread that executes MessageLoop::Run().
+ void PostTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task);
+
+ bool TryPostTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task);
+
+ void PostDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay);
+
+ void PostNonNestableTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task);
+
+ void PostNonNestableDelayedTask(
+ const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay);
+
+ // A variant on PostTask that deletes the given object. This is useful
+ // if the object needs to live until the next run of the MessageLoop (for
+ // example, deleting a RenderProcessHost from within an IPC callback is not
+ // good).
+ //
+ // NOTE: This method may be called on any thread. The object will be deleted
+ // on the thread that executes MessageLoop::Run(). If this is not the same
+ // as the thread that calls PostDelayedTask(FROM_HERE, ), then T MUST inherit
+ // from RefCountedThreadSafe<T>!
+ template <class T>
+ void DeleteSoon(const tracked_objects::Location& from_here, const T* object) {
+ base::subtle::DeleteHelperInternal<T, void>::DeleteViaSequencedTaskRunner(
+ this, from_here, object);
+ }
+
+ // A variant on PostTask that releases the given reference counted object
+ // (by calling its Release method). This is useful if the object needs to
+ // live until the next run of the MessageLoop, or if the object needs to be
+ // released on a particular thread.
+ //
+ // NOTE: This method may be called on any thread. The object will be
+ // released (and thus possibly deleted) on the thread that executes
+ // MessageLoop::Run(). If this is not the same as the thread that calls
+ // PostDelayedTask(FROM_HERE, ), then T MUST inherit from
+ // RefCountedThreadSafe<T>!
+ template <class T>
+ void ReleaseSoon(const tracked_objects::Location& from_here,
+ const T* object) {
+ base::subtle::ReleaseHelperInternal<T, void>::ReleaseViaSequencedTaskRunner(
+ this, from_here, object);
+ }
+
+ // Deprecated: use RunLoop instead.
+ // Run the message loop.
+ void Run();
+
+ // Deprecated: use RunLoop instead.
+ // Process all pending tasks, windows messages, etc., but don't wait/sleep.
+ // Return as soon as all items that can be run are taken care of.
+ void RunUntilIdle();
+
+ // TODO(jbates) remove this. crbug.com/131220. See QuitWhenIdle().
+ void Quit() { QuitWhenIdle(); }
+
+ // Deprecated: use RunLoop instead.
+ //
+ // Signals the Run method to return when it becomes idle. It will continue to
+ // process pending messages and future messages as long as they are enqueued.
+ // Warning: if the MessageLoop remains busy, it may never quit. Only use this
+ // Quit method when looping procedures (such as web pages) have been shut
+ // down.
+ //
+ // This method may only be called on the same thread that called Run, and Run
+ // must still be on the call stack.
+ //
+ // Use QuitClosure variants if you need to Quit another thread's MessageLoop,
+ // but note that doing so is fairly dangerous if the target thread makes
+ // nested calls to MessageLoop::Run. The problem being that you won't know
+ // which nested run loop you are quitting, so be careful!
+ void QuitWhenIdle();
+
+ // Deprecated: use RunLoop instead.
+ //
+ // This method is a variant of Quit, that does not wait for pending messages
+ // to be processed before returning from Run.
+ void QuitNow();
+
+ // TODO(jbates) remove this. crbug.com/131220. See QuitWhenIdleClosure().
+ static base::Closure QuitClosure() { return QuitWhenIdleClosure(); }
+
+ // Deprecated: use RunLoop instead.
+ // Construct a Closure that will call QuitWhenIdle(). Useful to schedule an
+ // arbitrary MessageLoop to QuitWhenIdle.
+ static base::Closure QuitWhenIdleClosure();
+
+ // Returns true if this loop is |type|. This allows subclasses (especially
+ // those in tests) to specialize how they are identified.
+ virtual bool IsType(Type type) const;
+
+ // Returns the type passed to the constructor.
+ Type type() const { return type_; }
+
+ // Optional call to connect the thread name with this loop.
+ void set_thread_name(const std::string& thread_name) {
+ DCHECK(thread_name_.empty()) << "Should not rename this thread!";
+ thread_name_ = thread_name;
+ }
+ const std::string& thread_name() const { return thread_name_; }
+
+ // Gets the message loop proxy associated with this message loop.
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy() {
+ return message_loop_proxy_.get();
+ }
+
+ // Enables or disables the recursive task processing. This happens in the case
+ // of recursive message loops. Some unwanted message loop may occurs when
+ // using common controls or printer functions. By default, recursive task
+ // processing is disabled.
+ //
+ // Please utilize |ScopedNestableTaskAllower| instead of calling these methods
+ // directly. In general nestable message loops are to be avoided. They are
+ // dangerous and difficult to get right, so please use with extreme caution.
+ //
+ // The specific case where tasks get queued is:
+ // - The thread is running a message loop.
+ // - It receives a task #1 and execute it.
+ // - The task #1 implicitly start a message loop, like a MessageBox in the
+ // unit test. This can also be StartDoc or GetSaveFileName.
+ // - The thread receives a task #2 before or while in this second message
+ // loop.
+ // - With NestableTasksAllowed set to true, the task #2 will run right away.
+ // Otherwise, it will get executed right after task #1 completes at "thread
+ // message loop level".
+ void SetNestableTasksAllowed(bool allowed);
+ bool NestableTasksAllowed() const;
+
+ // Enables nestable tasks on |loop| while in scope.
+ class ScopedNestableTaskAllower {
+ public:
+ explicit ScopedNestableTaskAllower(MessageLoop* loop)
+ : loop_(loop),
+ old_state_(loop_->NestableTasksAllowed()) {
+ loop_->SetNestableTasksAllowed(true);
+ }
+ ~ScopedNestableTaskAllower() {
+ loop_->SetNestableTasksAllowed(old_state_);
+ }
+
+ private:
+ MessageLoop* loop_;
+ bool old_state_;
+ };
+
+ // Enables or disables the restoration during an exception of the unhandled
+ // exception filter that was active when Run() was called. This can happen
+ // if some third party code call SetUnhandledExceptionFilter() and never
+ // restores the previous filter.
+ void set_exception_restoration(bool restore) {
+ exception_restoration_ = restore;
+ }
+
+ // Returns true if we are currently running a nested message loop.
+ bool IsNested();
+
+ // A TaskObserver is an object that receives task notifications from the
+ // MessageLoop.
+ //
+ // NOTE: A TaskObserver implementation should be extremely fast!
+ class BASE_EXPORT TaskObserver {
+ public:
+ TaskObserver();
+
+ // This method is called before processing a task.
+ virtual void WillProcessTask(const base::PendingTask& pending_task) = 0;
+
+ // This method is called after processing a task.
+ virtual void DidProcessTask(const base::PendingTask& pending_task) = 0;
+
+ protected:
+ virtual ~TaskObserver();
+ };
+
+ // These functions can only be called on the same thread that |this| is
+ // running on.
+ void AddTaskObserver(TaskObserver* task_observer);
+ void RemoveTaskObserver(TaskObserver* task_observer);
+
+ // Returns true if the message loop has high resolution timers enabled.
+ // Provided for testing.
+ bool high_resolution_timers_enabled() {
+#if defined(OS_WIN)
+ return !high_resolution_timer_expiration_.is_null();
+#else
+ return true;
+#endif
+ }
+
+ // When we go into high resolution timer mode, we will stay in hi-res mode
+ // for at least 1s.
+ static const int kHighResolutionTimerModeLeaseTimeMs = 1000;
+
+ // Asserts that the MessageLoop is "idle".
+ void AssertIdle() const;
+
+#if defined(OS_WIN)
+ void set_os_modal_loop(bool os_modal_loop) {
+ os_modal_loop_ = os_modal_loop;
+ }
+
+ bool os_modal_loop() const {
+ return os_modal_loop_;
+ }
+#endif // OS_WIN
+
+ // Can only be called from the thread that owns the MessageLoop.
+ bool is_running() const;
+
+ //----------------------------------------------------------------------------
+ protected:
+
+#if defined(OS_WIN)
+ base::MessagePumpWin* pump_win() {
+ return static_cast<base::MessagePumpWin*>(pump_.get());
+ }
+#elif defined(OS_POSIX) && !defined(OS_IOS)
+ base::MessagePumpLibevent* pump_libevent() {
+ return static_cast<base::MessagePumpLibevent*>(pump_.get());
+ }
+#endif
+
+ scoped_refptr<base::MessagePump> pump_;
+
+ private:
+ friend class base::RunLoop;
+ friend class base::MessageLoopLockTest;
+
+ // A function to encapsulate all the exception handling capability in the
+ // stacks around the running of a main message loop. It will run the message
+ // loop in a SEH try block or not depending on the set_SEH_restoration()
+ // flag invoking respectively RunInternalInSEHFrame() or RunInternal().
+ void RunHandler();
+
+#if defined(OS_WIN)
+ __declspec(noinline) void RunInternalInSEHFrame();
+#endif
+
+ // A surrounding stack frame around the running of the message loop that
+ // supports all saving and restoring of state, as is needed for any/all (ugly)
+ // recursive calls.
+ void RunInternal();
+
+ // Called to process any delayed non-nestable tasks.
+ bool ProcessNextDelayedNonNestableTask();
+
+ // Runs the specified PendingTask.
+ void RunTask(const base::PendingTask& pending_task);
+
+ // Calls RunTask or queues the pending_task on the deferred task list if it
+ // cannot be run right now. Returns true if the task was run.
+ bool DeferOrRunPendingTask(const base::PendingTask& pending_task);
+
+ // Adds the pending task to delayed_work_queue_.
+ void AddToDelayedWorkQueue(const base::PendingTask& pending_task);
+
+ // This function attempts to add pending task to our incoming_queue_.
+ // The append can only possibly fail when |use_try_lock| is true.
+ //
+ // When |use_try_lock| is true, then this call will avoid blocking if
+ // the related lock is already held, and will in that case (when the
+ // lock is contended) fail to perform the append, and will return false.
+ //
+ // If the call succeeds to append to the queue, then this call
+ // will return true.
+ //
+ // In all cases, the caller retains ownership of |pending_task|, but this
+ // function will reset the value of pending_task->task. This is needed to
+ // ensure that the posting call stack does not retain pending_task->task
+ // beyond this function call.
+ bool AddToIncomingQueue(base::PendingTask* pending_task, bool use_try_lock);
+
+ // Load tasks from the incoming_queue_ into work_queue_ if the latter is
+ // empty. The former requires a lock to access, while the latter is directly
+ // accessible on this thread.
+ void ReloadWorkQueue();
+
+ // Delete tasks that haven't run yet without running them. Used in the
+ // destructor to make sure all the task's destructors get called. Returns
+ // true if some work was done.
+ bool DeletePendingTasks();
+
+ // Calculates the time at which a PendingTask should run.
+ base::TimeTicks CalculateDelayedRuntime(base::TimeDelta delay);
+
+ // Start recording histogram info about events and action IF it was enabled
+ // and IF the statistics recorder can accept a registration of our histogram.
+ void StartHistogrammer();
+
+ // Add occurrence of event to our histogram, so that we can see what is being
+ // done in a specific MessageLoop instance (i.e., specific thread).
+ // If message_histogram_ is NULL, this is a no-op.
+ void HistogramEvent(int event);
+
+ // base::MessagePump::Delegate methods:
+ virtual bool DoWork() OVERRIDE;
+ virtual bool DoDelayedWork(base::TimeTicks* next_delayed_work_time) OVERRIDE;
+ virtual bool DoIdleWork() OVERRIDE;
+
+ Type type_;
+
+ // A list of tasks that need to be processed by this instance. Note that
+ // this queue is only accessed (push/pop) by our current thread.
+ base::TaskQueue work_queue_;
+
+ // Contains delayed tasks, sorted by their 'delayed_run_time' property.
+ base::DelayedTaskQueue delayed_work_queue_;
+
+ // A recent snapshot of Time::Now(), used to check delayed_work_queue_.
+ base::TimeTicks recent_time_;
+
+ // A queue of non-nestable tasks that we had to defer because when it came
+ // time to execute them we were in a nested message loop. They will execute
+ // once we're out of nested message loops.
+ base::TaskQueue deferred_non_nestable_work_queue_;
+
+ ObserverList<DestructionObserver> destruction_observers_;
+
+ // A recursion block that prevents accidentally running additional tasks when
+ // insider a (accidentally induced?) nested message pump.
+ bool nestable_tasks_allowed_;
+
+ bool exception_restoration_;
+
+ std::string thread_name_;
+ // A profiling histogram showing the counts of various messages and events.
+ base::HistogramBase* message_histogram_;
+
+ // An incoming queue of tasks that are acquired under a mutex for processing
+ // on this instance's thread. These tasks have not yet been sorted out into
+ // items for our work_queue_ vs delayed_work_queue_.
+ base::TaskQueue incoming_queue_;
+ // Protect access to incoming_queue_.
+ mutable base::Lock incoming_queue_lock_;
+
+ base::RunLoop* run_loop_;
+
+#if defined(OS_WIN)
+ base::TimeTicks high_resolution_timer_expiration_;
+ // Should be set to true before calling Windows APIs like TrackPopupMenu, etc
+ // which enter a modal message loop.
+ bool os_modal_loop_;
+#endif
+
+ // The next sequence number to use for delayed tasks. Updating this counter is
+ // protected by incoming_queue_lock_.
+ int next_sequence_num_;
+
+ ObserverList<TaskObserver> task_observers_;
+
+ // The message loop proxy associated with this message loop, if one exists.
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
+ scoped_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_;
+
+ template <class T, class R> friend class base::subtle::DeleteHelperInternal;
+ template <class T, class R> friend class base::subtle::ReleaseHelperInternal;
+
+ void DeleteSoonInternal(const tracked_objects::Location& from_here,
+ void(*deleter)(const void*),
+ const void* object);
+ void ReleaseSoonInternal(const tracked_objects::Location& from_here,
+ void(*releaser)(const void*),
+ const void* object);
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoop);
+};
+
+//-----------------------------------------------------------------------------
+// MessageLoopForUI extends MessageLoop with methods that are particular to a
+// MessageLoop instantiated with TYPE_UI.
+//
+// This class is typically used like so:
+// MessageLoopForUI::current()->...call some method...
+//
+class BASE_EXPORT MessageLoopForUI : public MessageLoop {
+ public:
+#if defined(OS_WIN)
+ typedef base::MessagePumpForUI::MessageFilter MessageFilter;
+#endif
+
+ MessageLoopForUI() : MessageLoop(TYPE_UI) {
+ }
+
+ // Returns the MessageLoopForUI of the current thread.
+ static MessageLoopForUI* current() {
+ MessageLoop* loop = MessageLoop::current();
+ DCHECK(loop);
+ DCHECK_EQ(MessageLoop::TYPE_UI, loop->type());
+ return static_cast<MessageLoopForUI*>(loop);
+ }
+
+#if defined(OS_WIN)
+ void DidProcessMessage(const MSG& message);
+#endif // defined(OS_WIN)
+
+#if defined(OS_IOS)
+ // On iOS, the main message loop cannot be Run(). Instead call Attach(),
+ // which connects this MessageLoop to the UI thread's CFRunLoop and allows
+ // PostTask() to work.
+ void Attach();
+#endif
+
+#if defined(OS_ANDROID)
+ // On Android, the UI message loop is handled by Java side. So Run() should
+ // never be called. Instead use Start(), which will forward all the native UI
+ // events to the Java message loop.
+ void Start();
+#elif !defined(OS_MACOSX)
+
+ // Please see message_pump_win/message_pump_glib for definitions of these
+ // methods.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+#if defined(OS_WIN)
+ // Plese see MessagePumpForUI for definitions of this method.
+ void SetMessageFilter(scoped_ptr<MessageFilter> message_filter) {
+ pump_ui()->SetMessageFilter(message_filter.Pass());
+ }
+#endif
+
+ protected:
+#if defined(USE_AURA) && defined(USE_X11) && !defined(OS_NACL)
+ friend class base::MessagePumpAuraX11;
+#endif
+#if defined(USE_OZONE) && !defined(OS_NACL)
+ friend class base::MessagePumpOzone;
+#endif
+
+ // TODO(rvargas): Make this platform independent.
+ base::MessagePumpForUI* pump_ui() {
+ return static_cast<base::MessagePumpForUI*>(pump_.get());
+ }
+#endif // !defined(OS_MACOSX)
+};
+
+// Do not add any member variables to MessageLoopForUI! This is important b/c
+// MessageLoopForUI is often allocated via MessageLoop(TYPE_UI). Any extra
+// data that you need should be stored on the MessageLoop's pump_ instance.
+COMPILE_ASSERT(sizeof(MessageLoop) == sizeof(MessageLoopForUI),
+ MessageLoopForUI_should_not_have_extra_member_variables);
+
+//-----------------------------------------------------------------------------
+// MessageLoopForIO extends MessageLoop with methods that are particular to a
+// MessageLoop instantiated with TYPE_IO.
+//
+// This class is typically used like so:
+// MessageLoopForIO::current()->...call some method...
+//
+class BASE_EXPORT MessageLoopForIO : public MessageLoop {
+ public:
+#if defined(OS_WIN)
+ typedef base::MessagePumpForIO::IOHandler IOHandler;
+ typedef base::MessagePumpForIO::IOContext IOContext;
+ typedef base::MessagePumpForIO::IOObserver IOObserver;
+#elif defined(OS_IOS)
+ typedef base::MessagePumpIOSForIO::Watcher Watcher;
+ typedef base::MessagePumpIOSForIO::FileDescriptorWatcher
+ FileDescriptorWatcher;
+ typedef base::MessagePumpIOSForIO::IOObserver IOObserver;
+
+ enum Mode {
+ WATCH_READ = base::MessagePumpIOSForIO::WATCH_READ,
+ WATCH_WRITE = base::MessagePumpIOSForIO::WATCH_WRITE,
+ WATCH_READ_WRITE = base::MessagePumpIOSForIO::WATCH_READ_WRITE
+ };
+#elif defined(OS_POSIX)
+ typedef base::MessagePumpLibevent::Watcher Watcher;
+ typedef base::MessagePumpLibevent::FileDescriptorWatcher
+ FileDescriptorWatcher;
+ typedef base::MessagePumpLibevent::IOObserver IOObserver;
+
+ enum Mode {
+ WATCH_READ = base::MessagePumpLibevent::WATCH_READ,
+ WATCH_WRITE = base::MessagePumpLibevent::WATCH_WRITE,
+ WATCH_READ_WRITE = base::MessagePumpLibevent::WATCH_READ_WRITE
+ };
+
+#endif
+
+ MessageLoopForIO() : MessageLoop(TYPE_IO) {
+ }
+
+ // Returns the MessageLoopForIO of the current thread.
+ static MessageLoopForIO* current() {
+ MessageLoop* loop = MessageLoop::current();
+ DCHECK_EQ(MessageLoop::TYPE_IO, loop->type());
+ return static_cast<MessageLoopForIO*>(loop);
+ }
+
+ void AddIOObserver(IOObserver* io_observer) {
+ pump_io()->AddIOObserver(io_observer);
+ }
+
+ void RemoveIOObserver(IOObserver* io_observer) {
+ pump_io()->RemoveIOObserver(io_observer);
+ }
+
+#if defined(OS_WIN)
+ // Please see MessagePumpWin for definitions of these methods.
+ void RegisterIOHandler(HANDLE file, IOHandler* handler);
+ bool RegisterJobObject(HANDLE job, IOHandler* handler);
+ bool WaitForIOCompletion(DWORD timeout, IOHandler* filter);
+
+ protected:
+ // TODO(rvargas): Make this platform independent.
+ base::MessagePumpForIO* pump_io() {
+ return static_cast<base::MessagePumpForIO*>(pump_.get());
+ }
+
+#elif defined(OS_IOS)
+ // Please see MessagePumpIOSForIO for definition.
+ bool WatchFileDescriptor(int fd,
+ bool persistent,
+ Mode mode,
+ FileDescriptorWatcher *controller,
+ Watcher *delegate);
+
+ private:
+ base::MessagePumpIOSForIO* pump_io() {
+ return static_cast<base::MessagePumpIOSForIO*>(pump_.get());
+ }
+
+#elif defined(OS_POSIX)
+ // Please see MessagePumpLibevent for definition.
+ bool WatchFileDescriptor(int fd,
+ bool persistent,
+ Mode mode,
+ FileDescriptorWatcher* controller,
+ Watcher* delegate);
+
+ private:
+ base::MessagePumpLibevent* pump_io() {
+ return static_cast<base::MessagePumpLibevent*>(pump_.get());
+ }
+#endif // defined(OS_POSIX)
+};
+
+// Do not add any member variables to MessageLoopForIO! This is important b/c
+// MessageLoopForIO is often allocated via MessageLoop(TYPE_IO). Any extra
+// data that you need should be stored on the MessageLoop's pump_ instance.
+COMPILE_ASSERT(sizeof(MessageLoop) == sizeof(MessageLoopForIO),
+ MessageLoopForIO_should_not_have_extra_member_variables);
+
+} // namespace base
+
+#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_H_
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
new file mode 100644
index 0000000..0f85b14
--- /dev/null
+++ b/base/message_loop/message_loop_unittest.cc
@@ -0,0 +1,2116 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop.h"
+#include "base/pending_task.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/message_pump_win.h"
+#include "base/win/scoped_handle.h"
+#endif
+
+namespace base {
+
+class MessageLoopLockTest {
+ public:
+ static void LockWaitUnLock(MessageLoop* loop,
+ base::WaitableEvent* caller_wait,
+ base::WaitableEvent* caller_signal) {
+
+ loop->incoming_queue_lock_.Acquire();
+ caller_wait->Signal();
+ caller_signal->Wait();
+ loop->incoming_queue_lock_.Release();
+ }
+};
+
+// TODO(darin): Platform-specific MessageLoop tests should be grouped together
+// to avoid chopping this file up with so many #ifdefs.
+
+namespace {
+
+class Foo : public RefCounted<Foo> {
+ public:
+ Foo() : test_count_(0) {
+ }
+
+ void Test0() {
+ ++test_count_;
+ }
+
+ void Test1ConstRef(const std::string& a) {
+ ++test_count_;
+ result_.append(a);
+ }
+
+ void Test1Ptr(std::string* a) {
+ ++test_count_;
+ result_.append(*a);
+ }
+
+ void Test1Int(int a) {
+ test_count_ += a;
+ }
+
+ void Test2Ptr(std::string* a, std::string* b) {
+ ++test_count_;
+ result_.append(*a);
+ result_.append(*b);
+ }
+
+ void Test2Mixed(const std::string& a, std::string* b) {
+ ++test_count_;
+ result_.append(a);
+ result_.append(*b);
+ }
+
+ int test_count() const { return test_count_; }
+ const std::string& result() const { return result_; }
+
+ private:
+ friend class RefCounted<Foo>;
+
+ ~Foo() {}
+
+ int test_count_;
+ std::string result_;
+};
+
+void RunTest_PostTask(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Add tests to message loop
+ scoped_refptr<Foo> foo(new Foo());
+ std::string a("a"), b("b"), c("c"), d("d");
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test0, foo.get()));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1ConstRef, foo.get(), a));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1Ptr, foo.get(), &b));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1Int, foo.get(), 100));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test2Ptr, foo.get(), &a, &c));
+
+ // TryPost with no contention. It must succeed.
+ EXPECT_TRUE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind(
+ &Foo::Test2Mixed, foo.get(), a, &d)));
+
+ // TryPost with simulated contention. It must fail. We wait for a helper
+ // thread to lock the queue, we TryPost on this thread and finally we
+ // signal the helper to unlock and exit.
+ WaitableEvent wait(true, false);
+ WaitableEvent signal(true, false);
+ Thread thread("RunTest_PostTask_helper");
+ thread.Start();
+ thread.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&MessageLoopLockTest::LockWaitUnLock,
+ MessageLoop::current(),
+ &wait,
+ &signal));
+
+ wait.Wait();
+ EXPECT_FALSE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind(
+ &Foo::Test2Mixed, foo.get(), a, &d)));
+ signal.Signal();
+
+ // After all tests, post a message that will shut down the message loop
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &MessageLoop::Quit, Unretained(MessageLoop::current())));
+
+ // Now kick things off
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(foo->test_count(), 105);
+ EXPECT_EQ(foo->result(), "abacad");
+}
+
+void RunTest_PostTask_SEH(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Add tests to message loop
+ scoped_refptr<Foo> foo(new Foo());
+ std::string a("a"), b("b"), c("c"), d("d");
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test0, foo.get()));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1ConstRef, foo.get(), a));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1Ptr, foo.get(), &b));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1Int, foo.get(), 100));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test2Ptr, foo.get(), &a, &c));
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &Foo::Test2Mixed, foo.get(), a, &d));
+
+ // After all tests, post a message that will shut down the message loop
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &MessageLoop::Quit, Unretained(MessageLoop::current())));
+
+ // Now kick things off with the SEH block active.
+ MessageLoop::current()->set_exception_restoration(true);
+ MessageLoop::current()->Run();
+ MessageLoop::current()->set_exception_restoration(false);
+
+ EXPECT_EQ(foo->test_count(), 105);
+ EXPECT_EQ(foo->result(), "abacad");
+}
+
+// This function runs slowly to simulate a large amount of work being done.
+static void SlowFunc(TimeDelta pause, int* quit_counter) {
+ PlatformThread::Sleep(pause);
+ if (--(*quit_counter) == 0)
+ MessageLoop::current()->QuitWhenIdle();
+}
+
+// This function records the time when Run was called in a Time object, which is
+// useful for building a variety of MessageLoop tests.
+static void RecordRunTimeFunc(Time* run_time, int* quit_counter) {
+ *run_time = Time::Now();
+
+ // Cause our Run function to take some time to execute. As a result we can
+ // count on subsequent RecordRunTimeFunc()s running at a future time,
+ // without worry about the resolution of our system clock being an issue.
+ SlowFunc(TimeDelta::FromMilliseconds(10), quit_counter);
+}
+
+void RunTest_PostDelayedTask_Basic(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Test that PostDelayedTask results in a delayed task.
+
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ int num_tasks = 1;
+ Time run_time;
+
+ loop.PostDelayedTask(
+ FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks),
+ kDelay);
+
+ Time time_before_run = Time::Now();
+ loop.Run();
+ Time time_after_run = Time::Now();
+
+ EXPECT_EQ(0, num_tasks);
+ EXPECT_LT(kDelay, time_after_run - time_before_run);
+}
+
+void RunTest_PostDelayedTask_InDelayOrder(
+ MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Test that two tasks with different delays run in the right order.
+ int num_tasks = 2;
+ Time run_time1, run_time2;
+
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time1, &num_tasks),
+ TimeDelta::FromMilliseconds(200));
+ // If we get a large pause in execution (due to a context switch) here, this
+ // test could fail.
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time2, &num_tasks),
+ TimeDelta::FromMilliseconds(10));
+
+ loop.Run();
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_TRUE(run_time2 < run_time1);
+}
+
+void RunTest_PostDelayedTask_InPostOrder(
+ MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Test that two tasks with the same delay run in the order in which they
+ // were posted.
+ //
+ // NOTE: This is actually an approximate test since the API only takes a
+ // "delay" parameter, so we are not exactly simulating two tasks that get
+ // posted at the exact same time. It would be nice if the API allowed us to
+ // specify the desired run time.
+
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ int num_tasks = 2;
+ Time run_time1, run_time2;
+
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay);
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), kDelay);
+
+ loop.Run();
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_TRUE(run_time1 < run_time2);
+}
+
+void RunTest_PostDelayedTask_InPostOrder_2(
+ MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Test that a delayed task still runs after a normal tasks even if the
+ // normal tasks take a long time to run.
+
+ const TimeDelta kPause = TimeDelta::FromMilliseconds(50);
+
+ int num_tasks = 2;
+ Time run_time;
+
+ loop.PostTask(FROM_HERE, Bind(&SlowFunc, kPause, &num_tasks));
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time, &num_tasks),
+ TimeDelta::FromMilliseconds(10));
+
+ Time time_before_run = Time::Now();
+ loop.Run();
+ Time time_after_run = Time::Now();
+
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_LT(kPause, time_after_run - time_before_run);
+}
+
+void RunTest_PostDelayedTask_InPostOrder_3(
+ MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Test that a delayed task still runs after a pile of normal tasks. The key
+ // difference between this test and the previous one is that here we return
+ // the MessageLoop a lot so we give the MessageLoop plenty of opportunities
+ // to maybe run the delayed task. It should know not to do so until the
+ // delayed task's delay has passed.
+
+ int num_tasks = 11;
+ Time run_time1, run_time2;
+
+ // Clutter the ML with tasks.
+ for (int i = 1; i < num_tasks; ++i)
+ loop.PostTask(FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time1, &num_tasks));
+
+ loop.PostDelayedTask(
+ FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks),
+ TimeDelta::FromMilliseconds(1));
+
+ loop.Run();
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_TRUE(run_time2 > run_time1);
+}
+
+void RunTest_PostDelayedTask_SharedTimer(
+ MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ // Test that the interval of the timer, used to run the next delayed task, is
+ // set to a value corresponding to when the next delayed task should run.
+
+ // By setting num_tasks to 1, we ensure that the first task to run causes the
+ // run loop to exit.
+ int num_tasks = 1;
+ Time run_time1, run_time2;
+
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time1, &num_tasks),
+ TimeDelta::FromSeconds(1000));
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time2, &num_tasks),
+ TimeDelta::FromMilliseconds(10));
+
+ Time start_time = Time::Now();
+
+ loop.Run();
+ EXPECT_EQ(0, num_tasks);
+
+ // Ensure that we ran in far less time than the slower timer.
+ TimeDelta total_time = Time::Now() - start_time;
+ EXPECT_GT(5000, total_time.InMilliseconds());
+
+ // In case both timers somehow run at nearly the same time, sleep a little
+ // and then run all pending to force them both to have run. This is just
+ // encouraging flakiness if there is any.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(run_time1.is_null());
+ EXPECT_FALSE(run_time2.is_null());
+}
+
+#if defined(OS_WIN)
+
+void SubPumpFunc() {
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ MessageLoop::current()->QuitWhenIdle();
+}
+
+void RunTest_PostDelayedTask_SharedTimer_SubPump() {
+ MessageLoop loop(MessageLoop::TYPE_UI);
+
+ // Test that the interval of the timer, used to run the next delayed task, is
+ // set to a value corresponding to when the next delayed task should run.
+
+ // By setting num_tasks to 1, we ensure that the first task to run causes the
+ // run loop to exit.
+ int num_tasks = 1;
+ Time run_time;
+
+ loop.PostTask(FROM_HERE, Bind(&SubPumpFunc));
+
+ // This very delayed task should never run.
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&RecordRunTimeFunc, &run_time, &num_tasks),
+ TimeDelta::FromSeconds(1000));
+
+ // This slightly delayed task should run from within SubPumpFunc).
+ loop.PostDelayedTask(
+ FROM_HERE,
+ Bind(&PostQuitMessage, 0),
+ TimeDelta::FromMilliseconds(10));
+
+ Time start_time = Time::Now();
+
+ loop.Run();
+ EXPECT_EQ(1, num_tasks);
+
+ // Ensure that we ran in far less time than the slower timer.
+ TimeDelta total_time = Time::Now() - start_time;
+ EXPECT_GT(5000, total_time.InMilliseconds());
+
+ // In case both timers somehow run at nearly the same time, sleep a little
+ // and then run all pending to force them both to have run. This is just
+ // encouraging flakiness if there is any.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(run_time.is_null());
+}
+
+#endif // defined(OS_WIN)
+
+// This is used to inject a test point for recording the destructor calls for
+// Closure objects send to MessageLoop::PostTask(). It is awkward usage since we
+// are trying to hook the actual destruction, which is not a common operation.
+class RecordDeletionProbe : public RefCounted<RecordDeletionProbe> {
+ public:
+ RecordDeletionProbe(RecordDeletionProbe* post_on_delete, bool* was_deleted)
+ : post_on_delete_(post_on_delete), was_deleted_(was_deleted) {
+ }
+ void Run() {}
+
+ private:
+ friend class RefCounted<RecordDeletionProbe>;
+
+ ~RecordDeletionProbe() {
+ *was_deleted_ = true;
+ if (post_on_delete_.get())
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&RecordDeletionProbe::Run, post_on_delete_.get()));
+ }
+
+ scoped_refptr<RecordDeletionProbe> post_on_delete_;
+ bool* was_deleted_;
+};
+
+void RunTest_EnsureDeletion(MessageLoop::Type message_loop_type) {
+ bool a_was_deleted = false;
+ bool b_was_deleted = false;
+ {
+ MessageLoop loop(message_loop_type);
+ loop.PostTask(
+ FROM_HERE, Bind(&RecordDeletionProbe::Run,
+ new RecordDeletionProbe(NULL, &a_was_deleted)));
+ // TODO(ajwong): Do we really need 1000ms here?
+ loop.PostDelayedTask(
+ FROM_HERE, Bind(&RecordDeletionProbe::Run,
+ new RecordDeletionProbe(NULL, &b_was_deleted)),
+ TimeDelta::FromMilliseconds(1000));
+ }
+ EXPECT_TRUE(a_was_deleted);
+ EXPECT_TRUE(b_was_deleted);
+}
+
+void RunTest_EnsureDeletion_Chain(MessageLoop::Type message_loop_type) {
+ bool a_was_deleted = false;
+ bool b_was_deleted = false;
+ bool c_was_deleted = false;
+ {
+ MessageLoop loop(message_loop_type);
+ // The scoped_refptr for each of the below is held either by the chained
+ // RecordDeletionProbe, or the bound RecordDeletionProbe::Run() callback.
+ RecordDeletionProbe* a = new RecordDeletionProbe(NULL, &a_was_deleted);
+ RecordDeletionProbe* b = new RecordDeletionProbe(a, &b_was_deleted);
+ RecordDeletionProbe* c = new RecordDeletionProbe(b, &c_was_deleted);
+ loop.PostTask(FROM_HERE, Bind(&RecordDeletionProbe::Run, c));
+ }
+ EXPECT_TRUE(a_was_deleted);
+ EXPECT_TRUE(b_was_deleted);
+ EXPECT_TRUE(c_was_deleted);
+}
+
+void NestingFunc(int* depth) {
+ if (*depth > 0) {
+ *depth -= 1;
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&NestingFunc, depth));
+
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoop::current()->Run();
+ }
+ MessageLoop::current()->QuitWhenIdle();
+}
+
+#if defined(OS_WIN)
+
+LONG WINAPI BadExceptionHandler(EXCEPTION_POINTERS *ex_info) {
+ ADD_FAILURE() << "bad exception handler";
+ ::ExitProcess(ex_info->ExceptionRecord->ExceptionCode);
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+// This task throws an SEH exception: initially write to an invalid address.
+// If the right SEH filter is installed, it will fix the error.
+class Crasher : public RefCounted<Crasher> {
+ public:
+ // Ctor. If trash_SEH_handler is true, the task will override the unhandled
+ // exception handler with one sure to crash this test.
+ explicit Crasher(bool trash_SEH_handler)
+ : trash_SEH_handler_(trash_SEH_handler) {
+ }
+
+ void Run() {
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+ if (trash_SEH_handler_)
+ ::SetUnhandledExceptionFilter(&BadExceptionHandler);
+ // Generate a SEH fault. We do it in asm to make sure we know how to undo
+ // the damage.
+
+#if defined(_M_IX86)
+
+ __asm {
+ mov eax, dword ptr [Crasher::bad_array_]
+ mov byte ptr [eax], 66
+ }
+
+#elif defined(_M_X64)
+
+ bad_array_[0] = 66;
+
+#else
+#error "needs architecture support"
+#endif
+
+ MessageLoop::current()->QuitWhenIdle();
+ }
+ // Points the bad array to a valid memory location.
+ static void FixError() {
+ bad_array_ = &valid_store_;
+ }
+
+ private:
+ bool trash_SEH_handler_;
+ static volatile char* bad_array_;
+ static char valid_store_;
+};
+
+volatile char* Crasher::bad_array_ = 0;
+char Crasher::valid_store_ = 0;
+
+// This SEH filter fixes the problem and retries execution. Fixing requires
+// that the last instruction: mov eax, [Crasher::bad_array_] to be retried
+// so we move the instruction pointer 5 bytes back.
+LONG WINAPI HandleCrasherException(EXCEPTION_POINTERS *ex_info) {
+ if (ex_info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
+ return EXCEPTION_EXECUTE_HANDLER;
+
+ Crasher::FixError();
+
+#if defined(_M_IX86)
+
+ ex_info->ContextRecord->Eip -= 5;
+
+#elif defined(_M_X64)
+
+ ex_info->ContextRecord->Rip -= 5;
+
+#endif
+
+ return EXCEPTION_CONTINUE_EXECUTION;
+}
+
+void RunTest_Crasher(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ if (::IsDebuggerPresent())
+ return;
+
+ LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter =
+ ::SetUnhandledExceptionFilter(&HandleCrasherException);
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&Crasher::Run, new Crasher(false)));
+ MessageLoop::current()->set_exception_restoration(true);
+ MessageLoop::current()->Run();
+ MessageLoop::current()->set_exception_restoration(false);
+
+ ::SetUnhandledExceptionFilter(old_SEH_filter);
+}
+
+void RunTest_CrasherNasty(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ if (::IsDebuggerPresent())
+ return;
+
+ LPTOP_LEVEL_EXCEPTION_FILTER old_SEH_filter =
+ ::SetUnhandledExceptionFilter(&HandleCrasherException);
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&Crasher::Run, new Crasher(true)));
+ MessageLoop::current()->set_exception_restoration(true);
+ MessageLoop::current()->Run();
+ MessageLoop::current()->set_exception_restoration(false);
+
+ ::SetUnhandledExceptionFilter(old_SEH_filter);
+}
+
+#endif // defined(OS_WIN)
+
+void RunTest_Nesting(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ int depth = 100;
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&NestingFunc, &depth));
+ MessageLoop::current()->Run();
+ EXPECT_EQ(depth, 0);
+}
+
+const wchar_t* const kMessageBoxTitle = L"MessageLoop Unit Test";
+
+enum TaskType {
+ MESSAGEBOX,
+ ENDDIALOG,
+ RECURSIVE,
+ TIMEDMESSAGELOOP,
+ QUITMESSAGELOOP,
+ ORDERED,
+ PUMPS,
+ SLEEP,
+ RUNS,
+};
+
+// Saves the order in which the tasks executed.
+struct TaskItem {
+ TaskItem(TaskType t, int c, bool s)
+ : type(t),
+ cookie(c),
+ start(s) {
+ }
+
+ TaskType type;
+ int cookie;
+ bool start;
+
+ bool operator == (const TaskItem& other) const {
+ return type == other.type && cookie == other.cookie && start == other.start;
+ }
+};
+
+std::ostream& operator <<(std::ostream& os, TaskType type) {
+ switch (type) {
+ case MESSAGEBOX: os << "MESSAGEBOX"; break;
+ case ENDDIALOG: os << "ENDDIALOG"; break;
+ case RECURSIVE: os << "RECURSIVE"; break;
+ case TIMEDMESSAGELOOP: os << "TIMEDMESSAGELOOP"; break;
+ case QUITMESSAGELOOP: os << "QUITMESSAGELOOP"; break;
+ case ORDERED: os << "ORDERED"; break;
+ case PUMPS: os << "PUMPS"; break;
+ case SLEEP: os << "SLEEP"; break;
+ default:
+ NOTREACHED();
+ os << "Unknown TaskType";
+ break;
+ }
+ return os;
+}
+
+std::ostream& operator <<(std::ostream& os, const TaskItem& item) {
+ if (item.start)
+ return os << item.type << " " << item.cookie << " starts";
+ else
+ return os << item.type << " " << item.cookie << " ends";
+}
+
+class TaskList {
+ public:
+ void RecordStart(TaskType type, int cookie) {
+ TaskItem item(type, cookie, true);
+ DVLOG(1) << item;
+ task_list_.push_back(item);
+ }
+
+ void RecordEnd(TaskType type, int cookie) {
+ TaskItem item(type, cookie, false);
+ DVLOG(1) << item;
+ task_list_.push_back(item);
+ }
+
+ size_t Size() {
+ return task_list_.size();
+ }
+
+ TaskItem Get(int n) {
+ return task_list_[n];
+ }
+
+ private:
+ std::vector<TaskItem> task_list_;
+};
+
+// Saves the order the tasks ran.
+void OrderedFunc(TaskList* order, int cookie) {
+ order->RecordStart(ORDERED, cookie);
+ order->RecordEnd(ORDERED, cookie);
+}
+
+#if defined(OS_WIN)
+
+// MessageLoop implicitly start a "modal message loop". Modal dialog boxes,
+// common controls (like OpenFile) and StartDoc printing function can cause
+// implicit message loops.
+void MessageBoxFunc(TaskList* order, int cookie, bool is_reentrant) {
+ order->RecordStart(MESSAGEBOX, cookie);
+ if (is_reentrant)
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK);
+ order->RecordEnd(MESSAGEBOX, cookie);
+}
+
+// Will end the MessageBox.
+void EndDialogFunc(TaskList* order, int cookie) {
+ order->RecordStart(ENDDIALOG, cookie);
+ HWND window = GetActiveWindow();
+ if (window != NULL) {
+ EXPECT_NE(EndDialog(window, IDCONTINUE), 0);
+ // Cheap way to signal that the window wasn't found if RunEnd() isn't
+ // called.
+ order->RecordEnd(ENDDIALOG, cookie);
+ }
+}
+
+#endif // defined(OS_WIN)
+
+void RecursiveFunc(TaskList* order, int cookie, int depth,
+ bool is_reentrant) {
+ order->RecordStart(RECURSIVE, cookie);
+ if (depth > 0) {
+ if (is_reentrant)
+ MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant));
+ }
+ order->RecordEnd(RECURSIVE, cookie);
+}
+
+void RecursiveSlowFunc(TaskList* order, int cookie, int depth,
+ bool is_reentrant) {
+ RecursiveFunc(order, cookie, depth, is_reentrant);
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+}
+
+void QuitFunc(TaskList* order, int cookie) {
+ order->RecordStart(QUITMESSAGELOOP, cookie);
+ MessageLoop::current()->QuitWhenIdle();
+ order->RecordEnd(QUITMESSAGELOOP, cookie);
+}
+
+void SleepFunc(TaskList* order, int cookie, TimeDelta delay) {
+ order->RecordStart(SLEEP, cookie);
+ PlatformThread::Sleep(delay);
+ order->RecordEnd(SLEEP, cookie);
+}
+
+#if defined(OS_WIN)
+void RecursiveFuncWin(MessageLoop* target,
+ HANDLE event,
+ bool expect_window,
+ TaskList* order,
+ bool is_reentrant) {
+ target->PostTask(FROM_HERE,
+ Bind(&RecursiveFunc, order, 1, 2, is_reentrant));
+ target->PostTask(FROM_HERE,
+ Bind(&MessageBoxFunc, order, 2, is_reentrant));
+ target->PostTask(FROM_HERE,
+ Bind(&RecursiveFunc, order, 3, 2, is_reentrant));
+ // The trick here is that for recursive task processing, this task will be
+ // ran _inside_ the MessageBox message loop, dismissing the MessageBox
+ // without a chance.
+ // For non-recursive task processing, this will be executed _after_ the
+ // MessageBox will have been dismissed by the code below, where
+ // expect_window_ is true.
+ target->PostTask(FROM_HERE,
+ Bind(&EndDialogFunc, order, 4));
+ target->PostTask(FROM_HERE,
+ Bind(&QuitFunc, order, 5));
+
+ // Enforce that every tasks are sent before starting to run the main thread
+ // message loop.
+ ASSERT_TRUE(SetEvent(event));
+
+ // Poll for the MessageBox. Don't do this at home! At the speed we do it,
+ // you will never realize one MessageBox was shown.
+ for (; expect_window;) {
+ HWND window = FindWindow(L"#32770", kMessageBoxTitle);
+ if (window) {
+ // Dismiss it.
+ for (;;) {
+ HWND button = FindWindowEx(window, NULL, L"Button", NULL);
+ if (button != NULL) {
+ EXPECT_EQ(0, SendMessage(button, WM_LBUTTONDOWN, 0, 0));
+ EXPECT_EQ(0, SendMessage(button, WM_LBUTTONUP, 0, 0));
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+
+#endif // defined(OS_WIN)
+
+void RunTest_RecursiveDenial1(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed());
+ TaskList order;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&RecursiveFunc, &order, 1, 2, false));
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&RecursiveFunc, &order, 2, 2, false));
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&QuitFunc, &order, 3));
+
+ MessageLoop::current()->Run();
+
+ // FIFO order.
+ ASSERT_EQ(14U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
+ EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false));
+}
+
+void RunTest_RecursiveDenial3(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed());
+ TaskList order;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&RecursiveSlowFunc, &order, 1, 2, false));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&RecursiveSlowFunc, &order, 2, 2, false));
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&OrderedFunc, &order, 3),
+ TimeDelta::FromMilliseconds(5));
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&QuitFunc, &order, 4),
+ TimeDelta::FromMilliseconds(5));
+
+ MessageLoop::current()->Run();
+
+ // FIFO order.
+ ASSERT_EQ(16U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(7), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 4, true));
+ EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 4, false));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 2, false));
+}
+
+void RunTest_RecursiveSupport1(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&RecursiveFunc, &order, 1, 2, true));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&RecursiveFunc, &order, 2, 2, true));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&QuitFunc, &order, 3));
+
+ MessageLoop::current()->Run();
+
+ // FIFO order.
+ ASSERT_EQ(14U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
+ EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false));
+}
+
+#if defined(OS_WIN)
+// TODO(darin): These tests need to be ported since they test critical
+// message loop functionality.
+
+// A side effect of this test is the generation a beep. Sorry.
+void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ Thread worker("RecursiveDenial2_worker");
+ Thread::Options options;
+ options.message_loop_type = message_loop_type;
+ ASSERT_EQ(true, worker.StartWithOptions(options));
+ TaskList order;
+ win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
+ worker.message_loop()->PostTask(FROM_HERE,
+ Bind(&RecursiveFuncWin,
+ MessageLoop::current(),
+ event.Get(),
+ true,
+ &order,
+ false));
+ // Let the other thread execute.
+ WaitForSingleObject(event, INFINITE);
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(order.Size(), 17);
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(MESSAGEBOX, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 3, false));
+ // When EndDialogFunc is processed, the window is already dismissed, hence no
+ // "end" entry.
+ EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, true));
+ EXPECT_EQ(order.Get(7), TaskItem(QUITMESSAGELOOP, 5, true));
+ EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, false));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 3, true));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, false));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 3, true));
+ EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, false));
+}
+
+// A side effect of this test is the generation a beep. Sorry. This test also
+// needs to process windows messages on the current thread.
+void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ Thread worker("RecursiveSupport2_worker");
+ Thread::Options options;
+ options.message_loop_type = message_loop_type;
+ ASSERT_EQ(true, worker.StartWithOptions(options));
+ TaskList order;
+ win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
+ worker.message_loop()->PostTask(FROM_HERE,
+ Bind(&RecursiveFuncWin,
+ MessageLoop::current(),
+ event.Get(),
+ false,
+ &order,
+ true));
+ // Let the other thread execute.
+ WaitForSingleObject(event, INFINITE);
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(order.Size(), 18);
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(MESSAGEBOX, 2, true));
+ // Note that this executes in the MessageBox modal loop.
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 3, true));
+ EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 3, false));
+ EXPECT_EQ(order.Get(5), TaskItem(ENDDIALOG, 4, true));
+ EXPECT_EQ(order.Get(6), TaskItem(ENDDIALOG, 4, false));
+ EXPECT_EQ(order.Get(7), TaskItem(MESSAGEBOX, 2, false));
+ /* The order can subtly change here. The reason is that when RecursiveFunc(1)
+ is called in the main thread, if it is faster than getting to the
+ PostTask(FROM_HERE, Bind(&QuitFunc) execution, the order of task
+ execution can change. We don't care anyway that the order isn't correct.
+ EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, true));
+ EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 5, false));
+ EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
+ */
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 3, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 3, false));
+ EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(16), TaskItem(RECURSIVE, 3, true));
+ EXPECT_EQ(order.Get(17), TaskItem(RECURSIVE, 3, false));
+}
+
+#endif // defined(OS_WIN)
+
+void FuncThatPumps(TaskList* order, int cookie) {
+ order->RecordStart(PUMPS, cookie);
+ {
+ MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
+ RunLoop().RunUntilIdle();
+ }
+ order->RecordEnd(PUMPS, cookie);
+}
+
+void FuncThatRuns(TaskList* order, int cookie, RunLoop* run_loop) {
+ order->RecordStart(RUNS, cookie);
+ {
+ MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
+ run_loop->Run();
+ }
+ order->RecordEnd(RUNS, cookie);
+}
+
+void FuncThatQuitsNow() {
+ MessageLoop::current()->QuitNow();
+}
+
+// Tests that non nestable tasks run in FIFO if there are no nested loops.
+void RunTest_NonNestableWithNoNesting(
+ MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ MessageLoop::current()->PostNonNestableTask(
+ FROM_HERE,
+ Bind(&OrderedFunc, &order, 1));
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&OrderedFunc, &order, 2));
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&QuitFunc, &order, 3));
+ MessageLoop::current()->Run();
+
+ // FIFO order.
+ ASSERT_EQ(6U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(ORDERED, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
+}
+
+// Tests that non nestable tasks don't run when there's code in the call stack.
+void RunTest_NonNestableInNestedLoop(MessageLoop::Type message_loop_type,
+ bool use_delayed) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&FuncThatPumps, &order, 1));
+ if (use_delayed) {
+ MessageLoop::current()->PostNonNestableDelayedTask(
+ FROM_HERE,
+ Bind(&OrderedFunc, &order, 2),
+ TimeDelta::FromMilliseconds(1));
+ } else {
+ MessageLoop::current()->PostNonNestableTask(
+ FROM_HERE,
+ Bind(&OrderedFunc, &order, 2));
+ }
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&OrderedFunc, &order, 3));
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50)));
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&OrderedFunc, &order, 5));
+ if (use_delayed) {
+ MessageLoop::current()->PostNonNestableDelayedTask(
+ FROM_HERE,
+ Bind(&QuitFunc, &order, 6),
+ TimeDelta::FromMilliseconds(2));
+ } else {
+ MessageLoop::current()->PostNonNestableTask(
+ FROM_HERE,
+ Bind(&QuitFunc, &order, 6));
+ }
+
+ MessageLoop::current()->Run();
+
+ // FIFO order.
+ ASSERT_EQ(12U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(PUMPS, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(order.Get(3), TaskItem(SLEEP, 4, true));
+ EXPECT_EQ(order.Get(4), TaskItem(SLEEP, 4, false));
+ EXPECT_EQ(order.Get(5), TaskItem(ORDERED, 5, true));
+ EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 5, false));
+ EXPECT_EQ(order.Get(7), TaskItem(PUMPS, 1, false));
+ EXPECT_EQ(order.Get(8), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 6, true));
+ EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 6, false));
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+void RunTest_QuitNow(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 2));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&FuncThatQuitsNow));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 3));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&FuncThatQuitsNow));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 4)); // never runs
+
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(6U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit works before RunWithID.
+void RunTest_RunLoopQuitOrderBefore(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ run_loop.Quit();
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 1)); // never runs
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs
+
+ run_loop.Run();
+
+ ASSERT_EQ(0U, order.Size());
+}
+
+// Tests RunLoopQuit works during RunWithID.
+void RunTest_RunLoopQuitOrderDuring(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 1));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, run_loop.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 2)); // never runs
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs
+
+ run_loop.Run();
+
+ ASSERT_EQ(2U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit works after RunWithID.
+void RunTest_RunLoopQuitOrderAfter(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 2));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&FuncThatQuitsNow));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 3));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, run_loop.QuitClosure()); // has no affect
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 4));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&FuncThatQuitsNow));
+
+ RunLoop outer_run_loop;
+ outer_run_loop.Run();
+
+ ASSERT_EQ(8U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+void RunTest_RunLoopQuitTop(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, outer_run_loop.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 2));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_run_loop.QuitClosure());
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(4U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+void RunTest_RunLoopQuitNested(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_run_loop.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 2));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, outer_run_loop.QuitClosure());
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(4U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+void RunTest_RunLoopQuitBogus(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
+ RunLoop bogus_run_loop;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, bogus_run_loop.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 2));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, outer_run_loop.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_run_loop.QuitClosure());
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(4U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+void RunTest_RunLoopQuitDeep(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_loop1;
+ RunLoop nested_loop2;
+ RunLoop nested_loop3;
+ RunLoop nested_loop4;
+
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 1, Unretained(&nested_loop1)));
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 2, Unretained(&nested_loop2)));
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 3, Unretained(&nested_loop3)));
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&FuncThatRuns, &order, 4, Unretained(&nested_loop4)));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 5));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, outer_run_loop.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 6));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_loop1.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 7));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_loop2.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 8));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_loop3.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 9));
+ MessageLoop::current()->PostTask(
+ FROM_HERE, nested_loop4.QuitClosure());
+ MessageLoop::current()->PostTask(
+ FROM_HERE, Bind(&OrderedFunc, &order, 10));
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(18U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+void PostNTasksThenQuit(int posts_remaining) {
+ if (posts_remaining > 1) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind(&PostNTasksThenQuit, posts_remaining - 1));
+ } else {
+ MessageLoop::current()->QuitWhenIdle();
+ }
+}
+
+void RunTest_RecursivePosts(MessageLoop::Type message_loop_type,
+ int num_times) {
+ MessageLoop loop(message_loop_type);
+ loop.PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, num_times));
+ loop.Run();
+}
+
+#if defined(OS_WIN)
+
+class DispatcherImpl : public base::MessageLoopForUI::Dispatcher {
+ public:
+ DispatcherImpl() : dispatch_count_(0) {}
+
+ virtual bool Dispatch(const NativeEvent& msg) OVERRIDE {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ // Do not count WM_TIMER since it is not what we post and it will cause
+ // flakiness.
+ if (msg.message != WM_TIMER)
+ ++dispatch_count_;
+ // We treat WM_LBUTTONUP as the last message.
+ return msg.message != WM_LBUTTONUP;
+ }
+
+ int dispatch_count_;
+};
+
+void MouseDownUp() {
+ PostMessage(NULL, WM_LBUTTONDOWN, 0, 0);
+ PostMessage(NULL, WM_LBUTTONUP, 'A', 0);
+}
+
+void RunTest_Dispatcher(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&MouseDownUp),
+ TimeDelta::FromMilliseconds(100));
+ DispatcherImpl dispatcher;
+ RunLoop run_loop(&dispatcher);
+ run_loop.Run();
+ ASSERT_EQ(2, dispatcher.dispatch_count_);
+}
+
+LRESULT CALLBACK MsgFilterProc(int code, WPARAM wparam, LPARAM lparam) {
+ if (code == MessagePumpForUI::kMessageFilterCode) {
+ MSG* msg = reinterpret_cast<MSG*>(lparam);
+ if (msg->message == WM_LBUTTONDOWN)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void RunTest_DispatcherWithMessageHook(MessageLoop::Type message_loop_type) {
+ MessageLoop loop(message_loop_type);
+
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&MouseDownUp),
+ TimeDelta::FromMilliseconds(100));
+ HHOOK msg_hook = SetWindowsHookEx(WH_MSGFILTER,
+ MsgFilterProc,
+ NULL,
+ GetCurrentThreadId());
+ DispatcherImpl dispatcher;
+ RunLoop run_loop(&dispatcher);
+ run_loop.Run();
+ ASSERT_EQ(1, dispatcher.dispatch_count_);
+ UnhookWindowsHookEx(msg_hook);
+}
+
+class TestIOHandler : public MessageLoopForIO::IOHandler {
+ public:
+ TestIOHandler(const wchar_t* name, HANDLE signal, bool wait);
+
+ virtual void OnIOCompleted(MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered, DWORD error);
+
+ void Init();
+ void WaitForIO();
+ OVERLAPPED* context() { return &context_.overlapped; }
+ DWORD size() { return sizeof(buffer_); }
+
+ private:
+ char buffer_[48];
+ MessageLoopForIO::IOContext context_;
+ HANDLE signal_;
+ win::ScopedHandle file_;
+ bool wait_;
+};
+
+TestIOHandler::TestIOHandler(const wchar_t* name, HANDLE signal, bool wait)
+ : signal_(signal), wait_(wait) {
+ memset(buffer_, 0, sizeof(buffer_));
+ memset(&context_, 0, sizeof(context_));
+ context_.handler = this;
+
+ file_.Set(CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL));
+ EXPECT_TRUE(file_.IsValid());
+}
+
+void TestIOHandler::Init() {
+ MessageLoopForIO::current()->RegisterIOHandler(file_, this);
+
+ DWORD read;
+ EXPECT_FALSE(ReadFile(file_, buffer_, size(), &read, context()));
+ EXPECT_EQ(ERROR_IO_PENDING, GetLastError());
+ if (wait_)
+ WaitForIO();
+}
+
+void TestIOHandler::OnIOCompleted(MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered, DWORD error) {
+ ASSERT_TRUE(context == &context_);
+ ASSERT_TRUE(SetEvent(signal_));
+}
+
+void TestIOHandler::WaitForIO() {
+ EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(300, this));
+ EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(400, this));
+}
+
+void RunTest_IOHandler() {
+ win::ScopedHandle callback_called(CreateEvent(NULL, TRUE, FALSE, NULL));
+ ASSERT_TRUE(callback_called.IsValid());
+
+ const wchar_t* kPipeName = L"\\\\.\\pipe\\iohandler_pipe";
+ win::ScopedHandle server(
+ CreateNamedPipe(kPipeName, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL));
+ ASSERT_TRUE(server.IsValid());
+
+ Thread thread("IOHandler test");
+ Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_IO;
+ ASSERT_TRUE(thread.StartWithOptions(options));
+
+ MessageLoop* thread_loop = thread.message_loop();
+ ASSERT_TRUE(NULL != thread_loop);
+
+ TestIOHandler handler(kPipeName, callback_called, false);
+ thread_loop->PostTask(FROM_HERE, Bind(&TestIOHandler::Init,
+ Unretained(&handler)));
+ // Make sure the thread runs and sleeps for lack of work.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+
+ const char buffer[] = "Hello there!";
+ DWORD written;
+ EXPECT_TRUE(WriteFile(server, buffer, sizeof(buffer), &written, NULL));
+
+ DWORD result = WaitForSingleObject(callback_called, 1000);
+ EXPECT_EQ(WAIT_OBJECT_0, result);
+
+ thread.Stop();
+}
+
+void RunTest_WaitForIO() {
+ win::ScopedHandle callback1_called(
+ CreateEvent(NULL, TRUE, FALSE, NULL));
+ win::ScopedHandle callback2_called(
+ CreateEvent(NULL, TRUE, FALSE, NULL));
+ ASSERT_TRUE(callback1_called.IsValid());
+ ASSERT_TRUE(callback2_called.IsValid());
+
+ const wchar_t* kPipeName1 = L"\\\\.\\pipe\\iohandler_pipe1";
+ const wchar_t* kPipeName2 = L"\\\\.\\pipe\\iohandler_pipe2";
+ win::ScopedHandle server1(
+ CreateNamedPipe(kPipeName1, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL));
+ win::ScopedHandle server2(
+ CreateNamedPipe(kPipeName2, PIPE_ACCESS_OUTBOUND, 0, 1, 0, 0, 0, NULL));
+ ASSERT_TRUE(server1.IsValid());
+ ASSERT_TRUE(server2.IsValid());
+
+ Thread thread("IOHandler test");
+ Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_IO;
+ ASSERT_TRUE(thread.StartWithOptions(options));
+
+ MessageLoop* thread_loop = thread.message_loop();
+ ASSERT_TRUE(NULL != thread_loop);
+
+ TestIOHandler handler1(kPipeName1, callback1_called, false);
+ TestIOHandler handler2(kPipeName2, callback2_called, true);
+ thread_loop->PostTask(FROM_HERE, Bind(&TestIOHandler::Init,
+ Unretained(&handler1)));
+ // TODO(ajwong): Do we really need such long Sleeps in ths function?
+ // Make sure the thread runs and sleeps for lack of work.
+ TimeDelta delay = TimeDelta::FromMilliseconds(100);
+ PlatformThread::Sleep(delay);
+ thread_loop->PostTask(FROM_HERE, Bind(&TestIOHandler::Init,
+ Unretained(&handler2)));
+ PlatformThread::Sleep(delay);
+
+ // At this time handler1 is waiting to be called, and the thread is waiting
+ // on the Init method of handler2, filtering only handler2 callbacks.
+
+ const char buffer[] = "Hello there!";
+ DWORD written;
+ EXPECT_TRUE(WriteFile(server1, buffer, sizeof(buffer), &written, NULL));
+ PlatformThread::Sleep(2 * delay);
+ EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(callback1_called, 0)) <<
+ "handler1 has not been called";
+
+ EXPECT_TRUE(WriteFile(server2, buffer, sizeof(buffer), &written, NULL));
+
+ HANDLE objects[2] = { callback1_called.Get(), callback2_called.Get() };
+ DWORD result = WaitForMultipleObjects(2, objects, TRUE, 1000);
+ EXPECT_EQ(WAIT_OBJECT_0, result);
+
+ thread.Stop();
+}
+
+#endif // defined(OS_WIN)
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+// Each test is run against each type of MessageLoop. That way we are sure
+// that message loops work properly in all configurations. Of course, in some
+// cases, a unit test may only be for a particular type of loop.
+
+TEST(MessageLoopTest, PostTask) {
+ RunTest_PostTask(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostTask(MessageLoop::TYPE_UI);
+ RunTest_PostTask(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostTask_SEH) {
+ RunTest_PostTask_SEH(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostTask_SEH(MessageLoop::TYPE_UI);
+ RunTest_PostTask_SEH(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostDelayedTask_Basic) {
+ RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_UI);
+ RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostDelayedTask_InDelayOrder) {
+ RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_UI);
+ RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostDelayedTask_InPostOrder) {
+ RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_UI);
+ RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostDelayedTask_InPostOrder_2) {
+ RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_UI);
+ RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostDelayedTask_InPostOrder_3) {
+ RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_UI);
+ RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, PostDelayedTask_SharedTimer) {
+ RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_DEFAULT);
+ RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_UI);
+ RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_IO);
+}
+
+#if defined(OS_WIN)
+TEST(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) {
+ RunTest_PostDelayedTask_SharedTimer_SubPump();
+}
+#endif
+
+// TODO(darin): MessageLoop does not support deleting all tasks in the
+// destructor.
+// Fails, http://crbug.com/50272.
+TEST(MessageLoopTest, DISABLED_EnsureDeletion) {
+ RunTest_EnsureDeletion(MessageLoop::TYPE_DEFAULT);
+ RunTest_EnsureDeletion(MessageLoop::TYPE_UI);
+ RunTest_EnsureDeletion(MessageLoop::TYPE_IO);
+}
+
+// TODO(darin): MessageLoop does not support deleting all tasks in the
+// destructor.
+// Fails, http://crbug.com/50272.
+TEST(MessageLoopTest, DISABLED_EnsureDeletion_Chain) {
+ RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_DEFAULT);
+ RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_UI);
+ RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_IO);
+}
+
+#if defined(OS_WIN)
+TEST(MessageLoopTest, Crasher) {
+ RunTest_Crasher(MessageLoop::TYPE_DEFAULT);
+ RunTest_Crasher(MessageLoop::TYPE_UI);
+ RunTest_Crasher(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, CrasherNasty) {
+ RunTest_CrasherNasty(MessageLoop::TYPE_DEFAULT);
+ RunTest_CrasherNasty(MessageLoop::TYPE_UI);
+ RunTest_CrasherNasty(MessageLoop::TYPE_IO);
+}
+#endif // defined(OS_WIN)
+
+TEST(MessageLoopTest, Nesting) {
+ RunTest_Nesting(MessageLoop::TYPE_DEFAULT);
+ RunTest_Nesting(MessageLoop::TYPE_UI);
+ RunTest_Nesting(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RecursiveDenial1) {
+ RunTest_RecursiveDenial1(MessageLoop::TYPE_DEFAULT);
+ RunTest_RecursiveDenial1(MessageLoop::TYPE_UI);
+ RunTest_RecursiveDenial1(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RecursiveDenial3) {
+ RunTest_RecursiveDenial3(MessageLoop::TYPE_DEFAULT);
+ RunTest_RecursiveDenial3(MessageLoop::TYPE_UI);
+ RunTest_RecursiveDenial3(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RecursiveSupport1) {
+ RunTest_RecursiveSupport1(MessageLoop::TYPE_DEFAULT);
+ RunTest_RecursiveSupport1(MessageLoop::TYPE_UI);
+ RunTest_RecursiveSupport1(MessageLoop::TYPE_IO);
+}
+
+#if defined(OS_WIN)
+// This test occasionally hangs http://crbug.com/44567
+TEST(MessageLoopTest, DISABLED_RecursiveDenial2) {
+ RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT);
+ RunTest_RecursiveDenial2(MessageLoop::TYPE_UI);
+ RunTest_RecursiveDenial2(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RecursiveSupport2) {
+ // This test requires a UI loop
+ RunTest_RecursiveSupport2(MessageLoop::TYPE_UI);
+}
+#endif // defined(OS_WIN)
+
+TEST(MessageLoopTest, NonNestableWithNoNesting) {
+ RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_DEFAULT);
+ RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_UI);
+ RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, NonNestableInNestedLoop) {
+ RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, false);
+ RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, false);
+ RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, false);
+}
+
+TEST(MessageLoopTest, NonNestableDelayedInNestedLoop) {
+ RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, true);
+ RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, true);
+ RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, true);
+}
+
+TEST(MessageLoopTest, QuitNow) {
+ RunTest_QuitNow(MessageLoop::TYPE_DEFAULT);
+ RunTest_QuitNow(MessageLoop::TYPE_UI);
+ RunTest_QuitNow(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitTop) {
+ RunTest_RunLoopQuitTop(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitTop(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitTop(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitNested) {
+ RunTest_RunLoopQuitNested(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitNested(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitNested(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitBogus) {
+ RunTest_RunLoopQuitBogus(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitBogus(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitBogus(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitDeep) {
+ RunTest_RunLoopQuitDeep(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitDeep(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitDeep(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitOrderBefore) {
+ RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitOrderDuring) {
+ RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_IO);
+}
+
+TEST(MessageLoopTest, RunLoopQuitOrderAfter) {
+ RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_DEFAULT);
+ RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_UI);
+ RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_IO);
+}
+
+class DummyTaskObserver : public MessageLoop::TaskObserver {
+ public:
+ explicit DummyTaskObserver(int num_tasks)
+ : num_tasks_started_(0),
+ num_tasks_processed_(0),
+ num_tasks_(num_tasks) {}
+
+ virtual ~DummyTaskObserver() {}
+
+ virtual void WillProcessTask(const PendingTask& pending_task) OVERRIDE {
+ num_tasks_started_++;
+ EXPECT_TRUE(pending_task.time_posted != TimeTicks());
+ EXPECT_LE(num_tasks_started_, num_tasks_);
+ EXPECT_EQ(num_tasks_started_, num_tasks_processed_ + 1);
+ }
+
+ virtual void DidProcessTask(const PendingTask& pending_task) OVERRIDE {
+ num_tasks_processed_++;
+ EXPECT_TRUE(pending_task.time_posted != TimeTicks());
+ EXPECT_LE(num_tasks_started_, num_tasks_);
+ EXPECT_EQ(num_tasks_started_, num_tasks_processed_);
+ }
+
+ int num_tasks_started() const { return num_tasks_started_; }
+ int num_tasks_processed() const { return num_tasks_processed_; }
+
+ private:
+ int num_tasks_started_;
+ int num_tasks_processed_;
+ const int num_tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyTaskObserver);
+};
+
+TEST(MessageLoopTest, TaskObserver) {
+ const int kNumPosts = 6;
+ DummyTaskObserver observer(kNumPosts);
+
+ MessageLoop loop;
+ loop.AddTaskObserver(&observer);
+ loop.PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, kNumPosts));
+ loop.Run();
+ loop.RemoveTaskObserver(&observer);
+
+ EXPECT_EQ(kNumPosts, observer.num_tasks_started());
+ EXPECT_EQ(kNumPosts, observer.num_tasks_processed());
+}
+
+#if defined(OS_WIN)
+TEST(MessageLoopTest, Dispatcher) {
+ // This test requires a UI loop
+ RunTest_Dispatcher(MessageLoop::TYPE_UI);
+}
+
+TEST(MessageLoopTest, DispatcherWithMessageHook) {
+ // This test requires a UI loop
+ RunTest_DispatcherWithMessageHook(MessageLoop::TYPE_UI);
+}
+
+TEST(MessageLoopTest, IOHandler) {
+ RunTest_IOHandler();
+}
+
+TEST(MessageLoopTest, WaitForIO) {
+ RunTest_WaitForIO();
+}
+
+TEST(MessageLoopTest, HighResolutionTimer) {
+ MessageLoop loop;
+
+ const TimeDelta kFastTimer = TimeDelta::FromMilliseconds(5);
+ const TimeDelta kSlowTimer = TimeDelta::FromMilliseconds(100);
+
+ EXPECT_FALSE(loop.high_resolution_timers_enabled());
+
+ // Post a fast task to enable the high resolution timers.
+ loop.PostDelayedTask(FROM_HERE, Bind(&PostNTasksThenQuit, 1),
+ kFastTimer);
+ loop.Run();
+ EXPECT_TRUE(loop.high_resolution_timers_enabled());
+
+ // Post a slow task and verify high resolution timers
+ // are still enabled.
+ loop.PostDelayedTask(FROM_HERE, Bind(&PostNTasksThenQuit, 1),
+ kSlowTimer);
+ loop.Run();
+ EXPECT_TRUE(loop.high_resolution_timers_enabled());
+
+ // Wait for a while so that high-resolution mode elapses.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(
+ MessageLoop::kHighResolutionTimerModeLeaseTimeMs));
+
+ // Post a slow task to disable the high resolution timers.
+ loop.PostDelayedTask(FROM_HERE, Bind(&PostNTasksThenQuit, 1),
+ kSlowTimer);
+ loop.Run();
+ EXPECT_FALSE(loop.high_resolution_timers_enabled());
+}
+
+#endif // defined(OS_WIN)
+
+#if defined(OS_POSIX) && !defined(OS_NACL)
+
+namespace {
+
+class QuitDelegate : public MessageLoopForIO::Watcher {
+ public:
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {
+ MessageLoop::current()->QuitWhenIdle();
+ }
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
+ MessageLoop::current()->QuitWhenIdle();
+ }
+};
+
+TEST(MessageLoopTest, FileDescriptorWatcherOutlivesMessageLoop) {
+ // Simulate a MessageLoop that dies before an FileDescriptorWatcher.
+ // This could happen when people use the Singleton pattern or atexit.
+
+ // Create a file descriptor. Doesn't need to be readable or writable,
+ // as we don't need to actually get any notifications.
+ // pipe() is just the easiest way to do it.
+ int pipefds[2];
+ int err = pipe(pipefds);
+ ASSERT_EQ(0, err);
+ int fd = pipefds[1];
+ {
+ // Arrange for controller to live longer than message loop.
+ MessageLoopForIO::FileDescriptorWatcher controller;
+ {
+ MessageLoopForIO message_loop;
+
+ QuitDelegate delegate;
+ message_loop.WatchFileDescriptor(fd,
+ true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate);
+ // and don't run the message loop, just destroy it.
+ }
+ }
+ if (HANDLE_EINTR(close(pipefds[0])) < 0)
+ PLOG(ERROR) << "close";
+ if (HANDLE_EINTR(close(pipefds[1])) < 0)
+ PLOG(ERROR) << "close";
+}
+
+TEST(MessageLoopTest, FileDescriptorWatcherDoubleStop) {
+ // Verify that it's ok to call StopWatchingFileDescriptor().
+ // (Errors only showed up in valgrind.)
+ int pipefds[2];
+ int err = pipe(pipefds);
+ ASSERT_EQ(0, err);
+ int fd = pipefds[1];
+ {
+ // Arrange for message loop to live longer than controller.
+ MessageLoopForIO message_loop;
+ {
+ MessageLoopForIO::FileDescriptorWatcher controller;
+
+ QuitDelegate delegate;
+ message_loop.WatchFileDescriptor(fd,
+ true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate);
+ controller.StopWatchingFileDescriptor();
+ }
+ }
+ if (HANDLE_EINTR(close(pipefds[0])) < 0)
+ PLOG(ERROR) << "close";
+ if (HANDLE_EINTR(close(pipefds[1])) < 0)
+ PLOG(ERROR) << "close";
+}
+
+} // namespace
+
+#endif // defined(OS_POSIX) && !defined(OS_NACL)
+
+namespace {
+// Inject a test point for recording the destructor calls for Closure objects
+// send to MessageLoop::PostTask(). It is awkward usage since we are trying to
+// hook the actual destruction, which is not a common operation.
+class DestructionObserverProbe :
+ public RefCounted<DestructionObserverProbe> {
+ public:
+ DestructionObserverProbe(bool* task_destroyed,
+ bool* destruction_observer_called)
+ : task_destroyed_(task_destroyed),
+ destruction_observer_called_(destruction_observer_called) {
+ }
+ virtual void Run() {
+ // This task should never run.
+ ADD_FAILURE();
+ }
+ private:
+ friend class RefCounted<DestructionObserverProbe>;
+
+ virtual ~DestructionObserverProbe() {
+ EXPECT_FALSE(*destruction_observer_called_);
+ *task_destroyed_ = true;
+ }
+
+ bool* task_destroyed_;
+ bool* destruction_observer_called_;
+};
+
+class MLDestructionObserver : public MessageLoop::DestructionObserver {
+ public:
+ MLDestructionObserver(bool* task_destroyed, bool* destruction_observer_called)
+ : task_destroyed_(task_destroyed),
+ destruction_observer_called_(destruction_observer_called),
+ task_destroyed_before_message_loop_(false) {
+ }
+ virtual void WillDestroyCurrentMessageLoop() OVERRIDE {
+ task_destroyed_before_message_loop_ = *task_destroyed_;
+ *destruction_observer_called_ = true;
+ }
+ bool task_destroyed_before_message_loop() const {
+ return task_destroyed_before_message_loop_;
+ }
+ private:
+ bool* task_destroyed_;
+ bool* destruction_observer_called_;
+ bool task_destroyed_before_message_loop_;
+};
+
+} // namespace
+
+TEST(MessageLoopTest, DestructionObserverTest) {
+ // Verify that the destruction observer gets called at the very end (after
+ // all the pending tasks have been destroyed).
+ MessageLoop* loop = new MessageLoop;
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ bool task_destroyed = false;
+ bool destruction_observer_called = false;
+
+ MLDestructionObserver observer(&task_destroyed, &destruction_observer_called);
+ loop->AddDestructionObserver(&observer);
+ loop->PostDelayedTask(
+ FROM_HERE,
+ Bind(&DestructionObserverProbe::Run,
+ new DestructionObserverProbe(&task_destroyed,
+ &destruction_observer_called)),
+ kDelay);
+ delete loop;
+ EXPECT_TRUE(observer.task_destroyed_before_message_loop());
+ // The task should have been destroyed when we deleted the loop.
+ EXPECT_TRUE(task_destroyed);
+ EXPECT_TRUE(destruction_observer_called);
+}
+
+
+// Verify that MessageLoop sets ThreadMainTaskRunner::current() and it
+// posts tasks on that message loop.
+TEST(MessageLoopTest, ThreadMainTaskRunner) {
+ MessageLoop loop;
+
+ scoped_refptr<Foo> foo(new Foo());
+ std::string a("a");
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(
+ &Foo::Test1ConstRef, foo.get(), a));
+
+ // Post quit task;
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &MessageLoop::Quit, Unretained(MessageLoop::current())));
+
+ // Now kick things off
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(foo->test_count(), 1);
+ EXPECT_EQ(foo->result(), "a");
+}
+
+TEST(MessageLoopTest, IsType) {
+ MessageLoop loop(MessageLoop::TYPE_UI);
+ EXPECT_TRUE(loop.IsType(MessageLoop::TYPE_UI));
+ EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_IO));
+ EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_DEFAULT));
+}
+
+TEST(MessageLoopTest, RecursivePosts) {
+ // There was a bug in the MessagePumpGLib where posting tasks recursively
+ // caused the message loop to hang, due to the buffer of the internal pipe
+ // becoming full. Test all MessageLoop types to ensure this issue does not
+ // exist in other MessagePumps.
+
+ // On Linux, the pipe buffer size is 64KiB by default. The bug caused one
+ // byte accumulated in the pipe per two posts, so we should repeat 128K
+ // times to reproduce the bug.
+ const int kNumTimes = 1 << 17;
+ RunTest_RecursivePosts(MessageLoop::TYPE_DEFAULT, kNumTimes);
+ RunTest_RecursivePosts(MessageLoop::TYPE_UI, kNumTimes);
+ RunTest_RecursivePosts(MessageLoop::TYPE_IO, kNumTimes);
+}
+
+} // namespace base