| // 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 |