// 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 "base/atomic_sequence_num.h" #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_task_runner.h" #include "base/synchronization/waitable_event.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread.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_(true, false) {} 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_.message_loop()->PostTask( FROM_HERE, Bind(&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(); MessageLoop::current()->QuitWhenIdle(); } void UnblockTaskThread() { thread_sync_.Signal(); } void BlockTaskThreadHelper() { thread_sync_.Wait(); } static StaticAtomicSequenceNumber g_order; scoped_ptr<MessageLoop> current_loop_; Thread task_thread_; private: base::WaitableEvent thread_sync_; }; StaticAtomicSequenceNumber MessageLoopTaskRunnerTest::g_order; TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_Basic) { MessageLoop* task_run_on = NULL; MessageLoop* task_deleted_on = NULL; int task_delete_order = -1; MessageLoop* reply_run_on = NULL; MessageLoop* reply_deleted_on = NULL; int reply_delete_order = -1; scoped_refptr<LoopRecorder> task_recoder = new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); scoped_refptr<LoopRecorder> reply_recoder = new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); ASSERT_TRUE(task_thread_.task_runner()->PostTaskAndReply( FROM_HERE, Bind(&RecordLoop, task_recoder), Bind(&RecordLoopAndQuit, reply_recoder))); // Die if base::Bind doesn't retain a reference to the recorders. task_recoder = NULL; reply_recoder = NULL; ASSERT_FALSE(task_deleted_on); ASSERT_FALSE(reply_deleted_on); UnblockTaskThread(); current_loop_->Run(); EXPECT_EQ(task_thread_.message_loop(), 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, PostTaskAndReplyOnDeletedThreadDoesNotLeak) { MessageLoop* task_run_on = NULL; MessageLoop* task_deleted_on = NULL; int task_delete_order = -1; MessageLoop* reply_run_on = NULL; MessageLoop* reply_deleted_on = NULL; int reply_delete_order = -1; scoped_refptr<LoopRecorder> task_recoder = new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); scoped_refptr<LoopRecorder> reply_recoder = 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, Bind(&RecordLoop, task_recoder), Bind(&RecordLoopAndQuit, reply_recoder))); // 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_recoder->HasOneRef()); ASSERT_TRUE(reply_recoder->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 = NULL; MessageLoop* task_deleted_on = NULL; int task_delete_order = -1; MessageLoop* reply_run_on = NULL; MessageLoop* reply_deleted_on = NULL; int reply_delete_order = -1; scoped_refptr<LoopRecorder> task_recoder = new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); scoped_refptr<LoopRecorder> reply_recoder = new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); // Enqueue the relay. ASSERT_TRUE(current_loop_->task_runner()->PostTaskAndReply( FROM_HERE, Bind(&RecordLoop, task_recoder), Bind(&RecordLoopAndQuit, reply_recoder))); // Die if base::Bind doesn't retain a reference to the recorders. task_recoder = NULL; reply_recoder = NULL; ASSERT_FALSE(task_deleted_on); ASSERT_FALSE(reply_deleted_on); current_loop_->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_DeadReplyLoopDoesNotDelete) { MessageLoop* task_run_on = NULL; MessageLoop* task_deleted_on = NULL; int task_delete_order = -1; MessageLoop* reply_run_on = NULL; MessageLoop* reply_deleted_on = NULL; int reply_delete_order = -1; scoped_refptr<LoopRecorder> task_recoder = new LoopRecorder(&task_run_on, &task_deleted_on, &task_delete_order); scoped_refptr<LoopRecorder> reply_recoder = new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order); // Enqueue the relay. task_thread_.task_runner()->PostTaskAndReply( FROM_HERE, Bind(&RecordLoop, task_recoder), Bind(&RecordLoopAndQuit, reply_recoder)); // Die if base::Bind doesn't retain a reference to the recorders. task_recoder = NULL; reply_recoder = NULL; 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(); EXPECT_EQ(task_loop, task_run_on); ASSERT_FALSE(task_deleted_on); EXPECT_FALSE(reply_run_on); ASSERT_FALSE(reply_deleted_on); EXPECT_EQ(task_delete_order, reply_delete_order); // 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_.PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure()); } 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_; }; scoped_ptr<Thread> io_thread_; scoped_ptr<Thread> file_thread_; private: mutable MessageLoop loop_; }; TEST_F(MessageLoopTaskRunnerThreadingTest, Release) { EXPECT_TRUE(io_thread_->task_runner()->ReleaseSoon(FROM_HERE, this)); MessageLoop::current()->Run(); } TEST_F(MessageLoopTaskRunnerThreadingTest, Delete) { DeletedOnFile* deleted_on_file = new DeletedOnFile(this); EXPECT_TRUE( file_thread_->task_runner()->DeleteSoon(FROM_HERE, deleted_on_file)); MessageLoop::current()->Run(); } TEST_F(MessageLoopTaskRunnerThreadingTest, PostTask) { EXPECT_TRUE(file_thread_->task_runner()->PostTask( FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::BasicFunction, Unretained(this)))); MessageLoop::current()->Run(); } TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadExits) { scoped_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, Bind(&MessageLoopTaskRunnerThreadingTest::AssertNotRun)); EXPECT_FALSE(ret); } TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadIsDeleted) { scoped_refptr<SingleThreadTaskRunner> task_runner; { scoped_ptr<Thread> test_thread( new Thread("MessageLoopTaskRunnerThreadingTest_Dummy")); test_thread->Start(); task_runner = test_thread->task_runner(); } bool ret = task_runner->PostTask( FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::AssertNotRun)); EXPECT_FALSE(ret); } } // namespace base