[scheduler] Prevent deadlock when posting a task from task destructor.

When TaskQueueManager was shutdown, posted closure will be destroyed
synchronously. When a closure owns an object, the destructor of this object
may try to post a task. If the task is destroyed while holding scheduler locks,
it may result in deadlocks.

R=alexclarke@chromium.org, skyostil@chromium.org
TBR=altimin@chromium.org

(cherry picked from commit 021a4bf395642491d7734138f06326b178a11e9a)

Bug: 814031
Change-Id: I7550b767d094fbd99543dd6b7827d58bf1c2bee9
Reviewed-on: https://chromium-review.googlesource.com/928705
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Commit-Queue: Alexander Timin <altimin@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#538147}
Reviewed-on: https://chromium-review.googlesource.com/937466
Reviewed-by: Alexander Timin <altimin@chromium.org>
Cr-Commit-Position: refs/branch-heads/3325@{#595}
Cr-Branched-From: bc084a8b5afa3744a74927344e304c02ae54189f-refs/heads/master@{#530369}
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue.cc b/third_party/WebKit/Source/platform/scheduler/base/task_queue.cc
index f92b6884..16a8012 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/task_queue.cc
+++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue.cc
@@ -69,28 +69,40 @@
 bool TaskQueue::PostDelayedTask(const base::Location& from_here,
                                 base::OnceClosure task,
                                 base::TimeDelta delay) {
-  auto lock = AcquireImplReadLockIfNeeded();
-  if (!impl_)
-    return false;
-  return impl_->PostDelayedTask(
-      PostedTask(std::move(task), from_here, delay, base::Nestable::kNestable));
+  internal::TaskQueueImpl::PostTaskResult result;
+  {
+    auto lock = AcquireImplReadLockIfNeeded();
+    if (!impl_)
+      return false;
+    result = impl_->PostDelayedTask(PostedTask(
+        std::move(task), from_here, delay, base::Nestable::kNestable));
+  }
+  return result.success;
 }
 
 bool TaskQueue::PostNonNestableDelayedTask(const base::Location& from_here,
                                            base::OnceClosure task,
                                            base::TimeDelta delay) {
-  auto lock = AcquireImplReadLockIfNeeded();
-  if (!impl_)
-    return false;
-  return impl_->PostDelayedTask(PostedTask(std::move(task), from_here, delay,
-                                           base::Nestable::kNonNestable));
+  internal::TaskQueueImpl::PostTaskResult result;
+  {
+    auto lock = AcquireImplReadLockIfNeeded();
+    if (!impl_)
+      return false;
+    result = impl_->PostDelayedTask(PostedTask(
+        std::move(task), from_here, delay, base::Nestable::kNonNestable));
+  }
+  return result.success;
 }
 
 bool TaskQueue::PostTaskWithMetadata(PostedTask task) {
-  auto lock = AcquireImplReadLockIfNeeded();
-  if (!impl_)
-    return false;
-  return impl_->PostDelayedTask(std::move(task));
+  internal::TaskQueueImpl::PostTaskResult result;
+  {
+    auto lock = AcquireImplReadLockIfNeeded();
+    if (!impl_)
+      return false;
+    result = impl_->PostDelayedTask(std::move(task));
+  }
+  return result.success;
 }
 
 std::unique_ptr<TaskQueue::QueueEnabledVoter>
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.cc b/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.cc
index 3bcb9ea3..3fada72 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.cc
+++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.cc
@@ -64,6 +64,23 @@
 #endif
 }
 
+TaskQueueImpl::PostTaskResult::PostTaskResult()
+    : task(base::OnceClosure(), base::Location()) {}
+
+TaskQueueImpl::PostTaskResult::PostTaskResult(bool success,
+                                              TaskQueue::PostedTask task)
+    : success(success), task(std::move(task)) {}
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostTaskResult::Success() {
+  return PostTaskResult(
+      true, TaskQueue::PostedTask(base::OnceClosure(), base::Location()));
+}
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostTaskResult::Fail(
+    TaskQueue::PostedTask task) {
+  return PostTaskResult(false, std::move(task));
+}
+
 TaskQueueImpl::Task::Task(TaskQueue::PostedTask task,
                           base::TimeTicks desired_run_time,
                           EnqueueOrder sequence_number)
@@ -169,20 +186,22 @@
   return base::PlatformThread::CurrentId() == thread_id_;
 }
 
-bool TaskQueueImpl::PostDelayedTask(TaskQueue::PostedTask task) {
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostDelayedTask(
+    TaskQueue::PostedTask task) {
   if (task.delay.is_zero())
     return PostImmediateTaskImpl(std::move(task));
 
   return PostDelayedTaskImpl(std::move(task));
 }
 
-bool TaskQueueImpl::PostImmediateTaskImpl(TaskQueue::PostedTask task) {
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostImmediateTaskImpl(
+    TaskQueue::PostedTask task) {
   // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
   // for details.
   CHECK(task.callback);
   base::AutoLock lock(any_thread_lock_);
   if (!any_thread().task_queue_manager)
-    return false;
+    return PostTaskResult::Fail(std::move(task));
 
   EnqueueOrder sequence_number =
       any_thread().task_queue_manager->GetNextSequenceNumber();
@@ -190,10 +209,11 @@
   PushOntoImmediateIncomingQueueLocked(Task(std::move(task),
                                             any_thread().time_domain->Now(),
                                             sequence_number, sequence_number));
-  return true;
+  return PostTaskResult::Success();
 }
 
-bool TaskQueueImpl::PostDelayedTaskImpl(TaskQueue::PostedTask task) {
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostDelayedTaskImpl(
+    TaskQueue::PostedTask task) {
   // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
   // for details.
   CHECK(task.callback);
@@ -201,7 +221,7 @@
   if (base::PlatformThread::CurrentId() == thread_id_) {
     // Lock-free fast path for delayed tasks posted from the main thread.
     if (!main_thread_only().task_queue_manager)
-      return false;
+      return PostTaskResult::Fail(std::move(task));
 
     EnqueueOrder sequence_number =
         main_thread_only().task_queue_manager->GetNextSequenceNumber();
@@ -218,7 +238,7 @@
     // assumption prove to be false in future, we may need to revisit this.
     base::AutoLock lock(any_thread_lock_);
     if (!any_thread().task_queue_manager)
-      return false;
+      return PostTaskResult::Fail(std::move(task));
 
     EnqueueOrder sequence_number =
         any_thread().task_queue_manager->GetNextSequenceNumber();
@@ -228,7 +248,7 @@
     PushOntoDelayedIncomingQueueLocked(
         Task(std::move(task), time_domain_delayed_run_time, sequence_number));
   }
-  return true;
+  return PostTaskResult::Success();
 }
 
 void TaskQueueImpl::PushOntoDelayedIncomingQueueFromMainThread(
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.h b/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.h
index ad628ff..970b0d25 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.h
+++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue_impl.h
@@ -131,6 +131,19 @@
     EnqueueOrder enqueue_order_;
   };
 
+  // A result retuned by PostDelayedTask. When scheduler failed to post a task
+  // due to being shutdown a task is returned to be destroyed outside the lock.
+  struct PostTaskResult {
+    PostTaskResult();
+    PostTaskResult(bool success, TaskQueue::PostedTask task);
+
+    static PostTaskResult Success();
+    static PostTaskResult Fail(TaskQueue::PostedTask task);
+
+    bool success = false;
+    TaskQueue::PostedTask task;
+  };
+
   using OnNextWakeUpChangedCallback = base::Callback<void(base::TimeTicks)>;
   using OnTaskStartedHandler =
       base::RepeatingCallback<void(const TaskQueue::Task&, base::TimeTicks)>;
@@ -143,7 +156,7 @@
   // TaskQueue implementation.
   const char* GetName() const;
   bool RunsTasksInCurrentSequence() const;
-  bool PostDelayedTask(TaskQueue::PostedTask task);
+  PostTaskResult PostDelayedTask(TaskQueue::PostedTask task);
   // Require a reference to enclosing task queue for lifetime control.
   std::unique_ptr<TaskQueue::QueueEnabledVoter> CreateQueueEnabledVoter(
       scoped_refptr<TaskQueue> owning_task_queue);
@@ -335,8 +348,8 @@
     bool is_enabled_for_test;
   };
 
-  bool PostImmediateTaskImpl(TaskQueue::PostedTask task);
-  bool PostDelayedTaskImpl(TaskQueue::PostedTask task);
+  PostTaskResult PostImmediateTaskImpl(TaskQueue::PostedTask task);
+  PostTaskResult PostDelayedTaskImpl(TaskQueue::PostedTask task);
 
   // Push the task onto the |delayed_incoming_queue|. Lock-free main thread
   // only fast path.
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager_unittest.cc b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager_unittest.cc
index 0bd15ed..a5d1018 100644
--- a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager_unittest.cc
+++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager_unittest.cc
@@ -3533,6 +3533,61 @@
   DCHECK_EQ(original_task_runner, message_loop.task_runner());
 }
 
+namespace {
+
+void DoNothing() {}
+
+class PostTaskInDestructor {
+ public:
+  explicit PostTaskInDestructor(scoped_refptr<TaskQueue> task_queue)
+      : task_queue_(task_queue) {}
+
+  ~PostTaskInDestructor() {
+    task_queue_->PostTask(FROM_HERE, base::BindOnce(&DoNothing));
+  }
+
+  void Do() {}
+
+ private:
+  scoped_refptr<TaskQueue> task_queue_;
+};
+
+}  // namespace
+
+TEST_F(TaskQueueManagerTest, TaskQueueUsedInTaskDestructorAfterShutdown) {
+  // This test checks that when a task is posted to a shutdown queue and
+  // destroyed, it can try to post a task to the same queue without deadlocks.
+  Initialize(0u);
+  test_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
+
+  scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
+
+  base::WaitableEvent test_executed(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  std::unique_ptr<base::Thread> thread =
+      std::make_unique<base::Thread>("test thread");
+  thread->StartAndWaitForTesting();
+
+  manager_.reset();
+
+  thread->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](scoped_refptr<base::SingleThreadTaskRunner> task_queue,
+             std::unique_ptr<PostTaskInDestructor> test_object,
+             base::WaitableEvent* test_executed) {
+            task_queue->PostTask(
+                FROM_HERE,
+                base::BindOnce(&PostTaskInDestructor::Do,
+                               base::Passed(std::move(test_object))));
+            test_executed->Signal();
+          },
+          main_tq, std::make_unique<PostTaskInDestructor>(main_tq),
+          &test_executed));
+  test_executed.Wait();
+}
+
 }  // namespace task_queue_manager_unittest
 }  // namespace scheduler
 }  // namespace blink