1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
4 // Use of this source code is governed by a BSD-style license that can be
5 // found in the LICENSE file.
6
7 #include "base/thread.h"
8
9 #include "base/string_util.h"
10 #include "base/thread_local.h"
11 #include "base/waitable_event.h"
12 #include "GeckoProfiler.h"
13 #include "mozilla/EventQueue.h"
14 #include "mozilla/IOInterposer.h"
15 #include "mozilla/ThreadEventQueue.h"
16 #include "nsThreadUtils.h"
17 #include "nsThreadManager.h"
18
19 namespace base {
20
21 // This task is used to trigger the message loop to exit.
22 class ThreadQuitTask : public mozilla::Runnable {
23 public:
ThreadQuitTask()24 ThreadQuitTask() : mozilla::Runnable("ThreadQuitTask") {}
Run()25 NS_IMETHOD Run() override {
26 MessageLoop::current()->Quit();
27 Thread::SetThreadWasQuitProperly(true);
28 return NS_OK;
29 }
30 };
31
32 // Used to pass data to ThreadMain. This structure is allocated on the stack
33 // from within StartWithOptions.
34 struct Thread::StartupData {
35 // We get away with a const reference here because of how we are allocated.
36 const Thread::Options& options;
37
38 // Used to synchronize thread startup.
39 WaitableEvent event;
40
StartupDatabase::Thread::StartupData41 explicit StartupData(const Options& opt)
42 : options(opt), event(false, false) {}
43 };
44
Thread(const char * name)45 Thread::Thread(const char* name)
46 : startup_data_(NULL),
47 thread_(0),
48 message_loop_(NULL),
49 thread_id_(0),
50 name_(name) {
51 MOZ_COUNT_CTOR(base::Thread);
52 }
53
~Thread()54 Thread::~Thread() {
55 MOZ_COUNT_DTOR(base::Thread);
56 Stop();
57 }
58
59 namespace {
60
61 // We use this thread-local variable to record whether or not a thread exited
62 // because its Stop method was called. This allows us to catch cases where
63 // MessageLoop::Quit() is called directly, which is unexpected when using a
64 // Thread to setup and run a MessageLoop.
65
get_tls_bool()66 static base::ThreadLocalBoolean& get_tls_bool() {
67 static base::ThreadLocalBoolean tls_ptr;
68 return tls_ptr;
69 }
70
71 } // namespace
72
SetThreadWasQuitProperly(bool flag)73 void Thread::SetThreadWasQuitProperly(bool flag) { get_tls_bool().Set(flag); }
74
GetThreadWasQuitProperly()75 bool Thread::GetThreadWasQuitProperly() {
76 bool quit_properly = true;
77 #ifndef NDEBUG
78 quit_properly = get_tls_bool().Get();
79 #endif
80 return quit_properly;
81 }
82
Start()83 bool Thread::Start() { return StartWithOptions(Options()); }
84
StartWithOptions(const Options & options)85 bool Thread::StartWithOptions(const Options& options) {
86 DCHECK(!message_loop_);
87
88 SetThreadWasQuitProperly(false);
89
90 StartupData startup_data(options);
91 startup_data_ = &startup_data;
92
93 if (!PlatformThread::Create(options.stack_size, this, &thread_)) {
94 DLOG(ERROR) << "failed to create thread";
95 startup_data_ = NULL; // Record that we failed to start.
96 return false;
97 }
98
99 // Wait for the thread to start and initialize message_loop_
100 startup_data.event.Wait();
101
102 DCHECK(message_loop_);
103 return true;
104 }
105
Stop()106 void Thread::Stop() {
107 if (!thread_was_started()) return;
108
109 // We should only be called on the same thread that started us.
110 DCHECK_NE(thread_id_, PlatformThread::CurrentId());
111
112 // StopSoon may have already been called.
113 if (message_loop_) {
114 RefPtr<ThreadQuitTask> task = new ThreadQuitTask();
115 message_loop_->PostTask(task.forget());
116 }
117
118 // Wait for the thread to exit. It should already have terminated but make
119 // sure this assumption is valid.
120 //
121 // TODO(darin): Unfortunately, we need to keep message_loop_ around until
122 // the thread exits. Some consumers are abusing the API. Make them stop.
123 //
124 PlatformThread::Join(thread_);
125
126 // The thread can't receive messages anymore.
127 message_loop_ = NULL;
128
129 // The thread no longer needs to be joined.
130 startup_data_ = NULL;
131 }
132
StopSoon()133 void Thread::StopSoon() {
134 if (!message_loop_) return;
135
136 // We should only be called on the same thread that started us.
137 DCHECK_NE(thread_id_, PlatformThread::CurrentId());
138
139 // We had better have a message loop at this point! If we do not, then it
140 // most likely means that the thread terminated unexpectedly, probably due
141 // to someone calling Quit() on our message loop directly.
142 DCHECK(message_loop_);
143
144 RefPtr<ThreadQuitTask> task = new ThreadQuitTask();
145 message_loop_->PostTask(task.forget());
146 }
147
ThreadMain()148 void Thread::ThreadMain() {
149 nsCOMPtr<nsIThread> xpcomThread;
150 auto loopType = startup_data_->options.message_loop_type;
151 if (loopType == MessageLoop::TYPE_MOZILLA_NONMAINTHREAD ||
152 loopType == MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD) {
153 auto queue = mozilla::MakeRefPtr<mozilla::ThreadEventQueue>(
154 mozilla::MakeUnique<mozilla::EventQueue>());
155 xpcomThread = nsThreadManager::get().CreateCurrentThread(
156 queue, nsThread::NOT_MAIN_THREAD);
157 } else {
158 xpcomThread = NS_GetCurrentThread();
159 }
160
161 AUTO_PROFILER_REGISTER_THREAD(name_.c_str());
162 mozilla::IOInterposer::RegisterCurrentThread();
163
164 // The message loop for this thread.
165 MessageLoop message_loop(startup_data_->options.message_loop_type,
166 xpcomThread);
167
168 xpcomThread = nullptr;
169
170 // Complete the initialization of our Thread object.
171 thread_id_ = PlatformThread::CurrentId();
172 PlatformThread::SetName(name_.c_str());
173 NS_SetCurrentThreadName(name_.c_str());
174 message_loop.set_thread_name(name_);
175 message_loop.set_hang_timeouts(startup_data_->options.transient_hang_timeout,
176 startup_data_->options.permanent_hang_timeout);
177 message_loop_ = &message_loop;
178
179 // Let the thread do extra initialization.
180 // Let's do this before signaling we are started.
181 Init();
182
183 startup_data_->event.Signal();
184 // startup_data_ can't be touched anymore since the starting thread is now
185 // unlocked.
186
187 message_loop.Run();
188
189 // Let the thread do extra cleanup.
190 CleanUp();
191
192 // Assert that MessageLoop::Quit was called by ThreadQuitTask.
193 DCHECK(GetThreadWasQuitProperly());
194
195 mozilla::IOInterposer::UnregisterCurrentThread();
196
197 // We can't receive messages anymore.
198 message_loop_ = NULL;
199 thread_id_ = 0;
200 }
201
202 } // namespace base
203