[MessageLoop] Clear all pending tasks in TriageQueue::Clear

This is a precursor to moving the |incoming_queue_| multi-threaded part
of IncomingTaskQueue to MessageLoopTaskRunner to allow mocking the
source of incoming tasks (i.e. for ScopedTaskEnvironment::MOCK_TIME on
top of MLForUI/etc.).

Such an interface will not have a notion of pre-triaged versus
still-on-multi-threaded-queue tasks. What matters here is that
TriageQueue::Clear() doesn't get stuck in a loop, the new logic clears
all pending tasks while still backing out if destructors post more
tasks.

R=danakj@chromium.org, kylechar@chromium.org

Bug: 708584
Change-Id: I8cd4f244718e4f5f74d5cb84138083ee37833238
Reviewed-on: https://chromium-review.googlesource.com/1078932
Commit-Queue: Gabriel Charette <gab@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: kylechar <kylechar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#563846}
diff --git a/base/message_loop/incoming_task_queue.cc b/base/message_loop/incoming_task_queue.cc
index c495f14..d6f4d64 100644
--- a/base/message_loop/incoming_task_queue.cc
+++ b/base/message_loop/incoming_task_queue.cc
@@ -7,6 +7,8 @@
 #include <limits>
 #include <utility>
 
+#include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/location.h"
 #include "base/message_loop/message_loop.h"
 #include "base/synchronization/waitable_event.h"
@@ -157,18 +159,31 @@
 
 void IncomingTaskQueue::TriageQueue::Clear() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
-  // Previously, MessageLoop would delete all tasks including delayed and
-  // deferred tasks in a single round before attempting to reload from the
-  // incoming queue to see if more tasks remained. This gave it a chance to
-  // assess whether or not clearing should continue. As a result, while
-  // reloading is automatic for getting and seeing if tasks exist, it is not
-  // automatic for Clear().
-  while (!queue_.empty()) {
-    PendingTask pending_task = std::move(queue_.front());
-    queue_.pop();
 
-    if (pending_task.is_high_res)
-      --outer_->pending_high_res_tasks_;
+  // Clear() should be invoked before WillDestroyCurrentMessageLoop().
+  DCHECK(outer_->accept_new_tasks_);
+
+  // Delete all currently pending tasks but not tasks potentially posted from
+  // their destructors. See ~MessageLoop() for the full logic mitigating against
+  // infite loops when clearing pending tasks. The ScopedClosureRunner below
+  // will be bound to a task posted at the end of the queue. After it is posted,
+  // tasks will be deleted one by one, when the bound ScopedClosureRunner is
+  // deleted and sets |deleted_all_originally_pending|, we know we've deleted
+  // all originally pending tasks.
+  bool deleted_all_originally_pending = false;
+  ScopedClosureRunner capture_deleted_all_originally_pending(BindOnce(
+      [](bool* deleted_all_originally_pending) {
+        *deleted_all_originally_pending = true;
+      },
+      Unretained(&deleted_all_originally_pending)));
+  outer_->AddToIncomingQueue(
+      FROM_HERE,
+      BindOnce([](ScopedClosureRunner) {},
+               std::move(capture_deleted_all_originally_pending)),
+      TimeDelta(), Nestable::kNestable);
+
+  while (!deleted_all_originally_pending) {
+    PendingTask pending_task = Pop();
 
     if (!pending_task.delayed_run_time.is_null()) {
       outer_->delayed_tasks().Push(std::move(pending_task));
diff --git a/base/message_loop/incoming_task_queue.h b/base/message_loop/incoming_task_queue.h
index f158d2a..9810cfa7 100644
--- a/base/message_loop/incoming_task_queue.h
+++ b/base/message_loop/incoming_task_queue.h
@@ -129,9 +129,9 @@
     ~TriageQueue() override;
 
     // ReadAndRemoveOnlyQueue:
-    // In general, the methods below will attempt to reload from the incoming
-    // queue if the queue itself is empty except for Clear(). See Clear() for
-    // why it doesn't reload.
+    // The methods below will attempt to reload from the incoming queue if the
+    // queue itself is empty (Clear() has special logic to reload only once
+    // should destructors post more tasks).
     const PendingTask& Peek() override;
     PendingTask Pop() override;
     // Whether this queue has tasks after reloading from the incoming queue.
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
index 8525366..3ddce801 100644
--- a/base/message_loop/message_loop_unittest.cc
+++ b/base/message_loop/message_loop_unittest.cc
@@ -23,6 +23,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task_scheduler/task_scheduler.h"
+#include "base/test/gtest_util.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/platform_thread.h"
@@ -2174,6 +2175,51 @@
   EXPECT_NE(slot.Get(), 11);
 }
 
+namespace {
+
+class PostTaskOnDestroy {
+ public:
+  PostTaskOnDestroy(int times) : times_remaining_(times) {}
+  ~PostTaskOnDestroy() { PostTaskWithPostingDestructor(times_remaining_); }
+
+  // Post a task that will repost itself on destruction |times| times.
+  static void PostTaskWithPostingDestructor(int times) {
+    if (times > 0) {
+      ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, BindOnce([](std::unique_ptr<PostTaskOnDestroy>) {},
+                              std::make_unique<PostTaskOnDestroy>(times - 1)));
+    }
+  }
+
+ private:
+  const int times_remaining_;
+
+  DISALLOW_COPY_AND_ASSIGN(PostTaskOnDestroy);
+};
+
+}  // namespace
+
+// Test that MessageLoop destruction handles a task's destructor posting another
+// task by:
+//  1) Not getting stuck clearing its task queue.
+//  2) DCHECKing when clearing pending tasks many times still doesn't yield an
+//     empty queue.
+TEST_P(MessageLoopTest, ExpectDeathWithStubbornPostTaskOnDestroy) {
+  std::unique_ptr<MessageLoop> loop = std::make_unique<MessageLoop>();
+
+  EXPECT_DCHECK_DEATH({
+    PostTaskOnDestroy::PostTaskWithPostingDestructor(1000);
+    loop.reset();
+  });
+}
+
+TEST_P(MessageLoopTest, DestroysFineWithReasonablePostTaskOnDestroy) {
+  std::unique_ptr<MessageLoop> loop = std::make_unique<MessageLoop>();
+
+  PostTaskOnDestroy::PostTaskWithPostingDestructor(10);
+  loop.reset();
+}
+
 INSTANTIATE_TEST_CASE_P(
     ,
     MessageLoopTest,