| // 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); | |
| } | |
| // Flaky, see https://crbug.com/856900 for details. | |
| TEST_F(MessageLoopTaskRunnerTest, | |
| DISABLED_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 |