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