|  | // Copyright (c) 2012 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_task_runner.h" | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/atomic_sequence_num.h" | 
|  | #include "base/bind.h" | 
|  | #include "base/debug/leak_annotations.h" | 
|  | #include "base/message_loop/message_loop.h" | 
|  | #include "base/message_loop/message_loop_task_runner.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/threading/thread.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "testing/platform_test.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | class MessageLoopTaskRunnerTest : public testing::Test { | 
|  | public: | 
|  | MessageLoopTaskRunnerTest() | 
|  | : current_loop_(new MessageLoop()), | 
|  | task_thread_("task_thread"), | 
|  | thread_sync_(WaitableEvent::ResetPolicy::MANUAL, | 
|  | WaitableEvent::InitialState::NOT_SIGNALED) {} | 
|  |  | 
|  | void DeleteCurrentMessageLoop() { current_loop_.reset(); } | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | // Use SetUp() instead of the constructor to avoid posting a task to a | 
|  | // partially constructed object. | 
|  | task_thread_.Start(); | 
|  |  | 
|  | // Allow us to pause the |task_thread_|'s MessageLoop. | 
|  | task_thread_.task_runner()->PostTask( | 
|  | FROM_HERE, BindOnce(&MessageLoopTaskRunnerTest::BlockTaskThreadHelper, | 
|  | Unretained(this))); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | // Make sure the |task_thread_| is not blocked, and stop the thread | 
|  | // fully before destruction because its tasks may still depend on the | 
|  | // |thread_sync_| event. | 
|  | thread_sync_.Signal(); | 
|  | task_thread_.Stop(); | 
|  | DeleteCurrentMessageLoop(); | 
|  | } | 
|  |  | 
|  | // Make LoopRecorder threadsafe so that there is defined behavior even if a | 
|  | // threading mistake sneaks into the PostTaskAndReplyRelay implementation. | 
|  | class LoopRecorder : public RefCountedThreadSafe<LoopRecorder> { | 
|  | public: | 
|  | LoopRecorder(MessageLoop** run_on, | 
|  | MessageLoop** deleted_on, | 
|  | int* destruct_order) | 
|  | : run_on_(run_on), | 
|  | deleted_on_(deleted_on), | 
|  | destruct_order_(destruct_order) {} | 
|  |  | 
|  | void RecordRun() { *run_on_ = MessageLoop::current(); } | 
|  |  | 
|  | private: | 
|  | friend class RefCountedThreadSafe<LoopRecorder>; | 
|  | ~LoopRecorder() { | 
|  | *deleted_on_ = MessageLoop::current(); | 
|  | *destruct_order_ = g_order.GetNext(); | 
|  | } | 
|  |  | 
|  | MessageLoop** run_on_; | 
|  | MessageLoop** deleted_on_; | 
|  | int* destruct_order_; | 
|  | }; | 
|  |  | 
|  | static void RecordLoop(scoped_refptr<LoopRecorder> recorder) { | 
|  | recorder->RecordRun(); | 
|  | } | 
|  |  | 
|  | static void RecordLoopAndQuit(scoped_refptr<LoopRecorder> recorder) { | 
|  | recorder->RecordRun(); | 
|  | RunLoop::QuitCurrentWhenIdleDeprecated(); | 
|  | } | 
|  |  | 
|  | void UnblockTaskThread() { thread_sync_.Signal(); } | 
|  |  | 
|  | void BlockTaskThreadHelper() { thread_sync_.Wait(); } | 
|  |  | 
|  | static AtomicSequenceNumber g_order; | 
|  |  | 
|  | std::unique_ptr<MessageLoop> current_loop_; | 
|  | Thread task_thread_; | 
|  |  | 
|  | private: | 
|  | base::WaitableEvent thread_sync_; | 
|  | }; | 
|  |  | 
|  | AtomicSequenceNumber MessageLoopTaskRunnerTest::g_order; | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_Basic) { | 
|  | MessageLoop* task_run_on = nullptr; | 
|  | MessageLoop* task_deleted_on = nullptr; | 
|  | int task_delete_order = -1; | 
|  | MessageLoop* reply_run_on = nullptr; | 
|  | MessageLoop* reply_deleted_on = nullptr; | 
|  | int reply_delete_order = -1; | 
|  |  | 
|  | scoped_refptr<LoopRecorder> task_recorder = | 
|  | new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); | 
|  | scoped_refptr<LoopRecorder> reply_recorder = | 
|  | new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); | 
|  |  | 
|  | ASSERT_TRUE(task_thread_.task_runner()->PostTaskAndReply( | 
|  | FROM_HERE, BindOnce(&RecordLoop, task_recorder), | 
|  | BindOnce(&RecordLoopAndQuit, reply_recorder))); | 
|  |  | 
|  | // Die if base::Bind doesn't retain a reference to the recorders. | 
|  | task_recorder = nullptr; | 
|  | reply_recorder = nullptr; | 
|  | ASSERT_FALSE(task_deleted_on); | 
|  | ASSERT_FALSE(reply_deleted_on); | 
|  |  | 
|  | UnblockTaskThread(); | 
|  | RunLoop().Run(); | 
|  |  | 
|  | EXPECT_EQ(task_thread_.message_loop(), task_run_on); | 
|  | EXPECT_EQ(task_thread_.message_loop(), task_deleted_on); | 
|  | EXPECT_EQ(current_loop_.get(), reply_run_on); | 
|  | EXPECT_EQ(current_loop_.get(), reply_deleted_on); | 
|  | EXPECT_LT(task_delete_order, reply_delete_order); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { | 
|  | MessageLoop* task_run_on = nullptr; | 
|  | MessageLoop* task_deleted_on = nullptr; | 
|  | int task_delete_order = -1; | 
|  | MessageLoop* reply_run_on = nullptr; | 
|  | MessageLoop* reply_deleted_on = nullptr; | 
|  | int reply_delete_order = -1; | 
|  |  | 
|  | scoped_refptr<LoopRecorder> task_recorder = | 
|  | new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); | 
|  | scoped_refptr<LoopRecorder> reply_recorder = | 
|  | new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); | 
|  |  | 
|  | // Grab a task runner to a dead MessageLoop. | 
|  | scoped_refptr<SingleThreadTaskRunner> task_runner = | 
|  | task_thread_.task_runner(); | 
|  | UnblockTaskThread(); | 
|  | task_thread_.Stop(); | 
|  |  | 
|  | ASSERT_FALSE(task_runner->PostTaskAndReply( | 
|  | FROM_HERE, BindOnce(&RecordLoop, task_recorder), | 
|  | BindOnce(&RecordLoopAndQuit, reply_recorder))); | 
|  |  | 
|  | // The relay should have properly deleted its resources leaving us as the only | 
|  | // reference. | 
|  | EXPECT_EQ(task_delete_order, reply_delete_order); | 
|  | ASSERT_TRUE(task_recorder->HasOneRef()); | 
|  | ASSERT_TRUE(reply_recorder->HasOneRef()); | 
|  |  | 
|  | // Nothing should have run though. | 
|  | EXPECT_FALSE(task_run_on); | 
|  | EXPECT_FALSE(reply_run_on); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_SameLoop) { | 
|  | MessageLoop* task_run_on = nullptr; | 
|  | MessageLoop* task_deleted_on = nullptr; | 
|  | int task_delete_order = -1; | 
|  | MessageLoop* reply_run_on = nullptr; | 
|  | MessageLoop* reply_deleted_on = nullptr; | 
|  | int reply_delete_order = -1; | 
|  |  | 
|  | scoped_refptr<LoopRecorder> task_recorder = | 
|  | new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); | 
|  | scoped_refptr<LoopRecorder> reply_recorder = | 
|  | new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); | 
|  |  | 
|  | // Enqueue the relay. | 
|  | ASSERT_TRUE(current_loop_->task_runner()->PostTaskAndReply( | 
|  | FROM_HERE, BindOnce(&RecordLoop, task_recorder), | 
|  | BindOnce(&RecordLoopAndQuit, reply_recorder))); | 
|  |  | 
|  | // Die if base::Bind doesn't retain a reference to the recorders. | 
|  | task_recorder = nullptr; | 
|  | reply_recorder = nullptr; | 
|  | ASSERT_FALSE(task_deleted_on); | 
|  | ASSERT_FALSE(reply_deleted_on); | 
|  |  | 
|  | RunLoop().Run(); | 
|  |  | 
|  | EXPECT_EQ(current_loop_.get(), task_run_on); | 
|  | EXPECT_EQ(current_loop_.get(), task_deleted_on); | 
|  | EXPECT_EQ(current_loop_.get(), reply_run_on); | 
|  | EXPECT_EQ(current_loop_.get(), reply_deleted_on); | 
|  | EXPECT_LT(task_delete_order, reply_delete_order); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerTest, | 
|  | PostTaskAndReply_DeadReplyTaskRunnerBehavior) { | 
|  | // Annotate the scope as having memory leaks to suppress heapchecker reports. | 
|  | ANNOTATE_SCOPED_MEMORY_LEAK; | 
|  | MessageLoop* task_run_on = nullptr; | 
|  | MessageLoop* task_deleted_on = nullptr; | 
|  | int task_delete_order = -1; | 
|  | MessageLoop* reply_run_on = nullptr; | 
|  | MessageLoop* reply_deleted_on = nullptr; | 
|  | int reply_delete_order = -1; | 
|  |  | 
|  | scoped_refptr<LoopRecorder> task_recorder = | 
|  | new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); | 
|  | scoped_refptr<LoopRecorder> reply_recorder = | 
|  | new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); | 
|  |  | 
|  | // Enqueue the relay. | 
|  | task_thread_.task_runner()->PostTaskAndReply( | 
|  | FROM_HERE, BindOnce(&RecordLoop, task_recorder), | 
|  | BindOnce(&RecordLoopAndQuit, reply_recorder)); | 
|  |  | 
|  | // Die if base::Bind doesn't retain a reference to the recorders. | 
|  | task_recorder = nullptr; | 
|  | reply_recorder = nullptr; | 
|  | ASSERT_FALSE(task_deleted_on); | 
|  | ASSERT_FALSE(reply_deleted_on); | 
|  |  | 
|  | UnblockTaskThread(); | 
|  |  | 
|  | // Mercilessly whack the current loop before |reply| gets to run. | 
|  | current_loop_.reset(); | 
|  |  | 
|  | // This should ensure the relay has been run.  We need to record the | 
|  | // MessageLoop pointer before stopping the thread because Thread::Stop() will | 
|  | // NULL out its own pointer. | 
|  | MessageLoop* task_loop = task_thread_.message_loop(); | 
|  | task_thread_.Stop(); | 
|  |  | 
|  | // Even if the reply task runner is already gone, the original task should | 
|  | // already be deleted. However, the reply which hasn't executed yet should | 
|  | // leak to avoid thread-safety issues. | 
|  | EXPECT_EQ(task_loop, task_run_on); | 
|  | EXPECT_EQ(task_loop, task_deleted_on); | 
|  | EXPECT_FALSE(reply_run_on); | 
|  | ASSERT_FALSE(reply_deleted_on); | 
|  |  | 
|  | // The PostTaskAndReplyRelay is leaked here.  Even if we had a reference to | 
|  | // it, we cannot just delete it because PostTaskAndReplyRelay's destructor | 
|  | // checks that MessageLoop::current() is the the same as when the | 
|  | // PostTaskAndReplyRelay object was constructed.  However, this loop must have | 
|  | // already been deleted in order to perform this test.  See | 
|  | // http://crbug.com/86301. | 
|  | } | 
|  |  | 
|  | class MessageLoopTaskRunnerThreadingTest : public testing::Test { | 
|  | public: | 
|  | void Release() const { | 
|  | AssertOnIOThread(); | 
|  | Quit(); | 
|  | } | 
|  |  | 
|  | void Quit() const { | 
|  | loop_.task_runner()->PostTask( | 
|  | FROM_HERE, RunLoop::QuitCurrentWhenIdleClosureDeprecated()); | 
|  | } | 
|  |  | 
|  | void AssertOnIOThread() const { | 
|  | ASSERT_TRUE(io_thread_->task_runner()->BelongsToCurrentThread()); | 
|  | ASSERT_EQ(io_thread_->task_runner(), ThreadTaskRunnerHandle::Get()); | 
|  | } | 
|  |  | 
|  | void AssertOnFileThread() const { | 
|  | ASSERT_TRUE(file_thread_->task_runner()->BelongsToCurrentThread()); | 
|  | ASSERT_EQ(file_thread_->task_runner(), ThreadTaskRunnerHandle::Get()); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | io_thread_.reset(new Thread("MessageLoopTaskRunnerThreadingTest_IO")); | 
|  | file_thread_.reset(new Thread("MessageLoopTaskRunnerThreadingTest_File")); | 
|  | io_thread_->Start(); | 
|  | file_thread_->Start(); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | io_thread_->Stop(); | 
|  | file_thread_->Stop(); | 
|  | } | 
|  |  | 
|  | static void BasicFunction(MessageLoopTaskRunnerThreadingTest* test) { | 
|  | test->AssertOnFileThread(); | 
|  | test->Quit(); | 
|  | } | 
|  |  | 
|  | static void AssertNotRun() { FAIL() << "Callback Should not get executed."; } | 
|  |  | 
|  | class DeletedOnFile { | 
|  | public: | 
|  | explicit DeletedOnFile(MessageLoopTaskRunnerThreadingTest* test) | 
|  | : test_(test) {} | 
|  |  | 
|  | ~DeletedOnFile() { | 
|  | test_->AssertOnFileThread(); | 
|  | test_->Quit(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | MessageLoopTaskRunnerThreadingTest* test_; | 
|  | }; | 
|  |  | 
|  | std::unique_ptr<Thread> io_thread_; | 
|  | std::unique_ptr<Thread> file_thread_; | 
|  |  | 
|  | private: | 
|  | mutable MessageLoop loop_; | 
|  | }; | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerThreadingTest, Release) { | 
|  | EXPECT_TRUE(io_thread_->task_runner()->ReleaseSoon(FROM_HERE, this)); | 
|  | RunLoop().Run(); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerThreadingTest, Delete) { | 
|  | DeletedOnFile* deleted_on_file = new DeletedOnFile(this); | 
|  | EXPECT_TRUE( | 
|  | file_thread_->task_runner()->DeleteSoon(FROM_HERE, deleted_on_file)); | 
|  | RunLoop().Run(); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerThreadingTest, PostTask) { | 
|  | EXPECT_TRUE(file_thread_->task_runner()->PostTask( | 
|  | FROM_HERE, BindOnce(&MessageLoopTaskRunnerThreadingTest::BasicFunction, | 
|  | Unretained(this)))); | 
|  | RunLoop().Run(); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadExits) { | 
|  | std::unique_ptr<Thread> test_thread( | 
|  | new Thread("MessageLoopTaskRunnerThreadingTest_Dummy")); | 
|  | test_thread->Start(); | 
|  | scoped_refptr<SingleThreadTaskRunner> task_runner = | 
|  | test_thread->task_runner(); | 
|  | test_thread->Stop(); | 
|  |  | 
|  | bool ret = task_runner->PostTask( | 
|  | FROM_HERE, BindOnce(&MessageLoopTaskRunnerThreadingTest::AssertNotRun)); | 
|  | EXPECT_FALSE(ret); | 
|  | } | 
|  |  | 
|  | TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadIsDeleted) { | 
|  | scoped_refptr<SingleThreadTaskRunner> task_runner; | 
|  | { | 
|  | std::unique_ptr<Thread> test_thread( | 
|  | new Thread("MessageLoopTaskRunnerThreadingTest_Dummy")); | 
|  | test_thread->Start(); | 
|  | task_runner = test_thread->task_runner(); | 
|  | } | 
|  | bool ret = task_runner->PostTask( | 
|  | FROM_HERE, BindOnce(&MessageLoopTaskRunnerThreadingTest::AssertNotRun)); | 
|  | EXPECT_FALSE(ret); | 
|  | } | 
|  |  | 
|  | }  // namespace base |