| Index: base/threading/thread_unittest.cc
|
| diff --git a/base/threading/thread_unittest.cc b/base/threading/thread_unittest.cc
|
| index 81fc4e964c3e6a544950cc9032495ee998f24cc4..e183cfc2307045fce92b0883dce3581c68a580c9 100644
|
| --- a/base/threading/thread_unittest.cc
|
| +++ b/base/threading/thread_unittest.cc
|
| @@ -9,10 +9,15 @@
|
| #include <vector>
|
|
|
| #include "base/bind.h"
|
| -#include "base/location.h"
|
| +#include "base/debug/leak_annotations.h"
|
| +#include "base/macros.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/run_loop.h"
|
| #include "base/single_thread_task_runner.h"
|
| #include "base/synchronization/waitable_event.h"
|
| +#include "base/test/gtest_util.h"
|
| #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
|
| +#include "base/threading/platform_thread.h"
|
| #include "build/build_config.h"
|
| #include "testing/gtest/include/gtest/gtest.h"
|
| #include "testing/platform_test.h"
|
| @@ -43,8 +48,11 @@ class SleepInsideInitThread : public Thread {
|
| init_called_ = true;
|
| }
|
| bool InitCalled() { return init_called_; }
|
| +
|
| private:
|
| bool init_called_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SleepInsideInitThread);
|
| };
|
|
|
| enum ThreadEvent {
|
| @@ -81,6 +89,8 @@ class CaptureToEventList : public Thread {
|
|
|
| private:
|
| EventList* event_list_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CaptureToEventList);
|
| };
|
|
|
| // Observer that writes a value into |event_list| when a message loop has been
|
| @@ -101,6 +111,8 @@ class CapturingDestructionObserver
|
|
|
| private:
|
| EventList* event_list_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CapturingDestructionObserver);
|
| };
|
|
|
| // Task that adds a destruction observer to the current message loop.
|
| @@ -142,10 +154,50 @@ TEST_F(ThreadTest, StartWithOptions_StackSize) {
|
| event.Wait();
|
| }
|
|
|
| -TEST_F(ThreadTest, TwoTasks) {
|
| +TEST_F(ThreadTest, StartWithOptions_NonJoinable) {
|
| + Thread* a = new Thread("StartNonJoinable");
|
| + // Non-joinable threads have to be leaked for now (see
|
| + // Thread::Options::joinable for details).
|
| + ANNOTATE_LEAKING_OBJECT_PTR(a);
|
| +
|
| + Thread::Options options;
|
| + options.joinable = false;
|
| + EXPECT_TRUE(a->StartWithOptions(options));
|
| + EXPECT_TRUE(a->message_loop());
|
| + EXPECT_TRUE(a->IsRunning());
|
| +
|
| + // Without this call this test is racy. The above IsRunning() succeeds because
|
| + // of an early-return condition while between Start() and StopSoon(), after
|
| + // invoking StopSoon() below this early-return condition is no longer
|
| + // satisfied and the real |is_running_| bit has to be checked. It could still
|
| + // be false if the message loop hasn't started for real in practice. This is
|
| + // only a requirement for this test because the non-joinable property forces
|
| + // it to use StopSoon() and not wait for a complete Stop().
|
| + EXPECT_TRUE(a->WaitUntilThreadStarted());
|
| +
|
| + // Make the thread block until |block_event| is signaled.
|
| + base::WaitableEvent block_event(
|
| + base::WaitableEvent::ResetPolicy::AUTOMATIC,
|
| + base::WaitableEvent::InitialState::NOT_SIGNALED);
|
| + a->task_runner()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&base::WaitableEvent::Wait, base::Unretained(&block_event)));
|
| +
|
| + a->StopSoon();
|
| + EXPECT_TRUE(a->IsRunning());
|
| +
|
| + // Unblock the task and give a bit of extra time to unwind QuitWhenIdle().
|
| + block_event.Signal();
|
| + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
|
| +
|
| + // The thread should now have stopped on its own.
|
| + EXPECT_FALSE(a->IsRunning());
|
| +}
|
| +
|
| +TEST_F(ThreadTest, TwoTasksOnJoinableThread) {
|
| bool was_invoked = false;
|
| {
|
| - Thread a("TwoTasks");
|
| + Thread a("TwoTasksOnJoinableThread");
|
| EXPECT_TRUE(a.Start());
|
| EXPECT_TRUE(a.message_loop());
|
|
|
| @@ -162,6 +214,28 @@ TEST_F(ThreadTest, TwoTasks) {
|
| EXPECT_TRUE(was_invoked);
|
| }
|
|
|
| +TEST_F(ThreadTest, DestroyWhileRunningIsSafe) {
|
| + Thread a("DestroyWhileRunningIsSafe");
|
| + EXPECT_TRUE(a.Start());
|
| + EXPECT_TRUE(a.WaitUntilThreadStarted());
|
| +}
|
| +
|
| +// TODO(gab): Enable this test when destroying a non-joinable Thread instance
|
| +// is supported (proposal @ https://crbug.com/629139#c14).
|
| +// TEST_F(ThreadTest, DestroyWhileRunningNonJoinableIsSafe) {
|
| +// {
|
| +// Thread a("DestroyWhileRunningNonJoinableIsSafe");
|
| +// Thread::Options options;
|
| +// options.joinable = false;
|
| +// EXPECT_TRUE(a.StartWithOptions(options));
|
| +// EXPECT_TRUE(a.WaitUntilThreadStarted());
|
| +// }
|
| +//
|
| +// // Attempt to catch use-after-frees from the non-joinable thread in the
|
| +// // scope of this test if any.
|
| +// base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
|
| +// }
|
| +
|
| TEST_F(ThreadTest, StopSoon) {
|
| Thread a("StopSoon");
|
| EXPECT_TRUE(a.Start());
|
| @@ -214,6 +288,42 @@ TEST_F(ThreadTest, StartTwice) {
|
| EXPECT_FALSE(a.IsRunning());
|
| }
|
|
|
| +TEST_F(ThreadTest, StartTwiceNonJoinableNotAllowed) {
|
| + Thread* a = new Thread("StartTwiceNonJoinable");
|
| + // Non-joinable threads have to be leaked for now (see
|
| + // Thread::Options::joinable for details).
|
| + ANNOTATE_LEAKING_OBJECT_PTR(a);
|
| +
|
| + Thread::Options options;
|
| + options.joinable = false;
|
| + EXPECT_TRUE(a->StartWithOptions(options));
|
| + EXPECT_TRUE(a->message_loop());
|
| + EXPECT_TRUE(a->IsRunning());
|
| +
|
| + // Signaled when last task on |a| is processed.
|
| + base::WaitableEvent last_task_event(
|
| + base::WaitableEvent::ResetPolicy::AUTOMATIC,
|
| + base::WaitableEvent::InitialState::NOT_SIGNALED);
|
| + a->task_runner()->PostTask(FROM_HERE,
|
| + base::Bind(&base::WaitableEvent::Signal,
|
| + base::Unretained(&last_task_event)));
|
| +
|
| + // StopSoon() is non-blocking, Yield() to |a|, wait for last task to be
|
| + // processed and a little more for QuitWhenIdle() to unwind before considering
|
| + // the thread "stopped".
|
| + a->StopSoon();
|
| + base::PlatformThread::YieldCurrentThread();
|
| + last_task_event.Wait();
|
| + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
|
| +
|
| + // This test assumes that the above was sufficient to let the thread fully
|
| + // stop.
|
| + ASSERT_FALSE(a->IsRunning());
|
| +
|
| + // Restarting it should not be allowed.
|
| + EXPECT_DCHECK_DEATH(a->Start(), "");
|
| +}
|
| +
|
| TEST_F(ThreadTest, ThreadName) {
|
| Thread a("ThreadName");
|
| EXPECT_TRUE(a.Start());
|
| @@ -315,3 +425,49 @@ TEST_F(ThreadTest, MultipleWaitUntilThreadStarted) {
|
| EXPECT_TRUE(a.WaitUntilThreadStarted());
|
| EXPECT_TRUE(a.WaitUntilThreadStarted());
|
| }
|
| +
|
| +namespace {
|
| +
|
| +// A Thread which uses a MessageLoop on the stack. It won't start a real
|
| +// underlying thread (instead its messages can be processed by a RunLoop on the
|
| +// stack).
|
| +class ExternalMessageLoopThread : public Thread {
|
| + public:
|
| + ExternalMessageLoopThread() : Thread("ExternalMessageLoopThread") {}
|
| +
|
| + ~ExternalMessageLoopThread() override { Stop(); }
|
| +
|
| + void InstallMessageLoop() { SetMessageLoop(&external_message_loop_); }
|
| +
|
| + private:
|
| + base::MessageLoop external_message_loop_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ExternalMessageLoopThread);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +TEST_F(ThreadTest, ExternalMessageLoop) {
|
| + ExternalMessageLoopThread a;
|
| + EXPECT_FALSE(a.message_loop());
|
| + EXPECT_FALSE(a.IsRunning());
|
| +
|
| + a.InstallMessageLoop();
|
| + EXPECT_TRUE(a.message_loop());
|
| + EXPECT_TRUE(a.IsRunning());
|
| +
|
| + bool ran = false;
|
| + a.task_runner()->PostTask(
|
| + FROM_HERE, base::Bind([](bool* toggled) { *toggled = true; }, &ran));
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_TRUE(ran);
|
| +
|
| + a.Stop();
|
| + EXPECT_FALSE(a.message_loop());
|
| + EXPECT_FALSE(a.IsRunning());
|
| +
|
| + // Confirm that running any remaining tasks posted from Stop() goes smoothly
|
| + // (e.g. https://codereview.chromium.org/2135413003/#ps300001 crashed if
|
| + // StopSoon() posted Thread::ThreadQuitHelper() while |run_loop_| was null).
|
| + base::RunLoop().RunUntilIdle();
|
| +}
|
|
|