|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/task/cancelable_task_tracker.h" | 
|  |  | 
|  | #include <cstddef> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/location.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ref_counted.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/test/gtest_util.h" | 
|  | #include "base/test/test_simple_task_runner.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class CancelableTaskTrackerTest : public testing::Test { | 
|  | protected: | 
|  | ~CancelableTaskTrackerTest() override { RunCurrentLoopUntilIdle(); } | 
|  |  | 
|  | void RunCurrentLoopUntilIdle() { | 
|  | RunLoop run_loop; | 
|  | run_loop.RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | CancelableTaskTracker task_tracker_; | 
|  |  | 
|  | private: | 
|  | // Needed by CancelableTaskTracker methods. | 
|  | MessageLoop message_loop_; | 
|  | }; | 
|  |  | 
|  | void AddFailureAt(const Location& location) { | 
|  | ADD_FAILURE_AT(location.file_name(), location.line_number()); | 
|  | } | 
|  |  | 
|  | // Returns a closure that fails if run. | 
|  | Closure MakeExpectedNotRunClosure(const Location& location) { | 
|  | return Bind(&AddFailureAt, location); | 
|  | } | 
|  |  | 
|  | // A helper class for MakeExpectedRunClosure() that fails if it is | 
|  | // destroyed without Run() having been called.  This class may be used | 
|  | // from multiple threads as long as Run() is called at most once | 
|  | // before destruction. | 
|  | class RunChecker { | 
|  | public: | 
|  | explicit RunChecker(const Location& location) | 
|  | : location_(location), called_(false) {} | 
|  |  | 
|  | ~RunChecker() { | 
|  | if (!called_) { | 
|  | ADD_FAILURE_AT(location_.file_name(), location_.line_number()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Run() { called_ = true; } | 
|  |  | 
|  | private: | 
|  | Location location_; | 
|  | bool called_; | 
|  | }; | 
|  |  | 
|  | // Returns a closure that fails on destruction if it hasn't been run. | 
|  | Closure MakeExpectedRunClosure(const Location& location) { | 
|  | return Bind(&RunChecker::Run, Owned(new RunChecker(location))); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // With the task tracker, post a task, a task with a reply, and get a | 
|  | // new task id without canceling any of them.  The tasks and the reply | 
|  | // should run and the "is canceled" callback should return false. | 
|  | TEST_F(CancelableTaskTrackerTest, NoCancel) { | 
|  | Thread worker_thread("worker thread"); | 
|  | ASSERT_TRUE(worker_thread.Start()); | 
|  |  | 
|  | ignore_result(task_tracker_.PostTask(worker_thread.task_runner().get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedRunClosure(FROM_HERE))); | 
|  |  | 
|  | ignore_result(task_tracker_.PostTaskAndReply( | 
|  | worker_thread.task_runner().get(), FROM_HERE, | 
|  | MakeExpectedRunClosure(FROM_HERE), MakeExpectedRunClosure(FROM_HERE))); | 
|  |  | 
|  | CancelableTaskTracker::IsCanceledCallback is_canceled; | 
|  | ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); | 
|  |  | 
|  | worker_thread.Stop(); | 
|  |  | 
|  | RunCurrentLoopUntilIdle(); | 
|  |  | 
|  | EXPECT_FALSE(is_canceled.Run()); | 
|  | } | 
|  |  | 
|  | // Post a task with the task tracker but cancel it before running the | 
|  | // task runner.  The task should not run. | 
|  | TEST_F(CancelableTaskTrackerTest, CancelPostedTask) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask( | 
|  | test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE)); | 
|  | EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); | 
|  |  | 
|  | EXPECT_EQ(1U, test_task_runner->NumPendingTasks()); | 
|  |  | 
|  | task_tracker_.TryCancel(task_id); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Post a task with reply with the task tracker and cancel it before | 
|  | // running the task runner.  Neither the task nor the reply should | 
|  | // run. | 
|  | TEST_F(CancelableTaskTrackerTest, CancelPostedTaskAndReply) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | CancelableTaskTracker::TaskId task_id = | 
|  | task_tracker_.PostTaskAndReply(test_task_runner.get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedNotRunClosure(FROM_HERE), | 
|  | MakeExpectedNotRunClosure(FROM_HERE)); | 
|  | EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); | 
|  |  | 
|  | task_tracker_.TryCancel(task_id); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Post a task with reply with the task tracker and cancel it after | 
|  | // running the task runner but before running the current message | 
|  | // loop.  The task should run but the reply should not. | 
|  | TEST_F(CancelableTaskTrackerTest, CancelReply) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | CancelableTaskTracker::TaskId task_id = | 
|  | task_tracker_.PostTaskAndReply(test_task_runner.get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedRunClosure(FROM_HERE), | 
|  | MakeExpectedNotRunClosure(FROM_HERE)); | 
|  | EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  |  | 
|  | task_tracker_.TryCancel(task_id); | 
|  | } | 
|  |  | 
|  | // Post a task with reply with the task tracker on a worker thread and | 
|  | // cancel it before running the current message loop.  The task should | 
|  | // run but the reply should not. | 
|  | TEST_F(CancelableTaskTrackerTest, CancelReplyDifferentThread) { | 
|  | Thread worker_thread("worker thread"); | 
|  | ASSERT_TRUE(worker_thread.Start()); | 
|  |  | 
|  | CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply( | 
|  | worker_thread.task_runner().get(), FROM_HERE, DoNothing(), | 
|  | MakeExpectedNotRunClosure(FROM_HERE)); | 
|  | EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); | 
|  |  | 
|  | task_tracker_.TryCancel(task_id); | 
|  |  | 
|  | worker_thread.Stop(); | 
|  | } | 
|  |  | 
|  | void ExpectIsCanceled( | 
|  | const CancelableTaskTracker::IsCanceledCallback& is_canceled, | 
|  | bool expected_is_canceled) { | 
|  | EXPECT_EQ(expected_is_canceled, is_canceled.Run()); | 
|  | } | 
|  |  | 
|  | // Create a new task ID and check its status on a separate thread | 
|  | // before and after canceling.  The is-canceled callback should be | 
|  | // thread-safe (i.e., nothing should blow up). | 
|  | TEST_F(CancelableTaskTrackerTest, NewTrackedTaskIdDifferentThread) { | 
|  | CancelableTaskTracker::IsCanceledCallback is_canceled; | 
|  | CancelableTaskTracker::TaskId task_id = | 
|  | task_tracker_.NewTrackedTaskId(&is_canceled); | 
|  |  | 
|  | EXPECT_FALSE(is_canceled.Run()); | 
|  |  | 
|  | Thread other_thread("other thread"); | 
|  | ASSERT_TRUE(other_thread.Start()); | 
|  | other_thread.task_runner()->PostTask( | 
|  | FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, false)); | 
|  | other_thread.Stop(); | 
|  |  | 
|  | task_tracker_.TryCancel(task_id); | 
|  |  | 
|  | ASSERT_TRUE(other_thread.Start()); | 
|  | other_thread.task_runner()->PostTask( | 
|  | FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, true)); | 
|  | other_thread.Stop(); | 
|  | } | 
|  |  | 
|  | // With the task tracker, post a task, a task with a reply, get a new | 
|  | // task id, and then cancel all of them.  None of the tasks nor the | 
|  | // reply should run and the "is canceled" callback should return | 
|  | // true. | 
|  | TEST_F(CancelableTaskTrackerTest, CancelAll) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | ignore_result(task_tracker_.PostTask( | 
|  | test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE))); | 
|  |  | 
|  | ignore_result( | 
|  | task_tracker_.PostTaskAndReply(test_task_runner.get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedNotRunClosure(FROM_HERE), | 
|  | MakeExpectedNotRunClosure(FROM_HERE))); | 
|  |  | 
|  | CancelableTaskTracker::IsCanceledCallback is_canceled; | 
|  | ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); | 
|  |  | 
|  | task_tracker_.TryCancelAll(); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  |  | 
|  | RunCurrentLoopUntilIdle(); | 
|  |  | 
|  | EXPECT_TRUE(is_canceled.Run()); | 
|  | } | 
|  |  | 
|  | // With the task tracker, post a task, a task with a reply, get a new | 
|  | // task id, and then cancel all of them.  None of the tasks nor the | 
|  | // reply should run and the "is canceled" callback should return | 
|  | // true. | 
|  | TEST_F(CancelableTaskTrackerTest, DestructionCancelsAll) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | CancelableTaskTracker::IsCanceledCallback is_canceled; | 
|  |  | 
|  | { | 
|  | // Create another task tracker with a smaller scope. | 
|  | CancelableTaskTracker task_tracker; | 
|  |  | 
|  | ignore_result(task_tracker.PostTask(test_task_runner.get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedNotRunClosure(FROM_HERE))); | 
|  |  | 
|  | ignore_result( | 
|  | task_tracker.PostTaskAndReply(test_task_runner.get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedNotRunClosure(FROM_HERE), | 
|  | MakeExpectedNotRunClosure(FROM_HERE))); | 
|  |  | 
|  | ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); | 
|  | } | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  |  | 
|  | RunCurrentLoopUntilIdle(); | 
|  |  | 
|  | EXPECT_FALSE(is_canceled.Run()); | 
|  | } | 
|  |  | 
|  | // Post a task and cancel it. HasTrackedTasks() should return false as soon as | 
|  | // TryCancelAll() is called. | 
|  | TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPost) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | EXPECT_FALSE(task_tracker_.HasTrackedTasks()); | 
|  |  | 
|  | ignore_result(task_tracker_.PostTask( | 
|  | test_task_runner.get(), FROM_HERE, MakeExpectedNotRunClosure(FROM_HERE))); | 
|  |  | 
|  | task_tracker_.TryCancelAll(); | 
|  |  | 
|  | EXPECT_FALSE(task_tracker_.HasTrackedTasks()); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  | RunCurrentLoopUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Post a task with a reply and cancel it. HasTrackedTasks() should return false | 
|  | // as soon as TryCancelAll() is called. | 
|  | TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReply) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | EXPECT_FALSE(task_tracker_.HasTrackedTasks()); | 
|  |  | 
|  | ignore_result( | 
|  | task_tracker_.PostTaskAndReply(test_task_runner.get(), | 
|  | FROM_HERE, | 
|  | MakeExpectedNotRunClosure(FROM_HERE), | 
|  | MakeExpectedNotRunClosure(FROM_HERE))); | 
|  |  | 
|  | task_tracker_.TryCancelAll(); | 
|  |  | 
|  | EXPECT_FALSE(task_tracker_.HasTrackedTasks()); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  | RunCurrentLoopUntilIdle(); | 
|  | } | 
|  |  | 
|  | // Create a new tracked task ID. HasTrackedTasks() should return false as soon | 
|  | // as TryCancelAll() is called. | 
|  | TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelled) { | 
|  | EXPECT_FALSE(task_tracker_.HasTrackedTasks()); | 
|  |  | 
|  | CancelableTaskTracker::IsCanceledCallback is_canceled; | 
|  | ignore_result(task_tracker_.NewTrackedTaskId(&is_canceled)); | 
|  |  | 
|  | task_tracker_.TryCancelAll(); | 
|  |  | 
|  | EXPECT_FALSE(task_tracker_.HasTrackedTasks()); | 
|  | } | 
|  |  | 
|  | // The death tests below make sure that calling task tracker member | 
|  | // functions from a thread different from its owner thread DCHECKs in | 
|  | // debug mode. | 
|  |  | 
|  | class CancelableTaskTrackerDeathTest : public CancelableTaskTrackerTest { | 
|  | protected: | 
|  | CancelableTaskTrackerDeathTest() { | 
|  | // The default style "fast" does not support multi-threaded tests. | 
|  | ::testing::FLAGS_gtest_death_test_style = "threadsafe"; | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Runs |fn| with |task_tracker|, expecting it to crash in debug mode. | 
|  | void MaybeRunDeadlyTaskTrackerMemberFunction( | 
|  | CancelableTaskTracker* task_tracker, | 
|  | const Callback<void(CancelableTaskTracker*)>& fn) { | 
|  | EXPECT_DCHECK_DEATH(fn.Run(task_tracker)); | 
|  | } | 
|  |  | 
|  | void PostDoNothingTask(CancelableTaskTracker* task_tracker) { | 
|  | ignore_result(task_tracker->PostTask( | 
|  | scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(), | 
|  | FROM_HERE, DoNothing())); | 
|  | } | 
|  |  | 
|  | TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) { | 
|  | Thread bad_thread("bad thread"); | 
|  | ASSERT_TRUE(bad_thread.Start()); | 
|  |  | 
|  | bad_thread.task_runner()->PostTask( | 
|  | FROM_HERE, | 
|  | BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction, | 
|  | Unretained(&task_tracker_), Bind(&PostDoNothingTask))); | 
|  | } | 
|  |  | 
|  | void TryCancel(CancelableTaskTracker::TaskId task_id, | 
|  | CancelableTaskTracker* task_tracker) { | 
|  | task_tracker->TryCancel(task_id); | 
|  | } | 
|  |  | 
|  | TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | Thread bad_thread("bad thread"); | 
|  | ASSERT_TRUE(bad_thread.Start()); | 
|  |  | 
|  | CancelableTaskTracker::TaskId task_id = | 
|  | task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing()); | 
|  | EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); | 
|  |  | 
|  | bad_thread.task_runner()->PostTask( | 
|  | FROM_HERE, | 
|  | BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction, | 
|  | Unretained(&task_tracker_), Bind(&TryCancel, task_id))); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) { | 
|  | scoped_refptr<TestSimpleTaskRunner> test_task_runner( | 
|  | new TestSimpleTaskRunner()); | 
|  |  | 
|  | Thread bad_thread("bad thread"); | 
|  | ASSERT_TRUE(bad_thread.Start()); | 
|  |  | 
|  | CancelableTaskTracker::TaskId task_id = | 
|  | task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing()); | 
|  | EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id); | 
|  |  | 
|  | bad_thread.task_runner()->PostTask( | 
|  | FROM_HERE, BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction, | 
|  | Unretained(&task_tracker_), | 
|  | Bind(&CancelableTaskTracker::TryCancelAll))); | 
|  |  | 
|  | test_task_runner->RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | }  // namespace base |