michael@0: // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/thread.h" michael@0: michael@0: #include "base/lazy_instance.h" michael@0: #include "base/string_util.h" michael@0: #include "base/thread_local.h" michael@0: #include "base/waitable_event.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "mozilla/IOInterposer.h" michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: #include "GeckoTaskTracer.h" michael@0: #endif michael@0: michael@0: namespace base { michael@0: michael@0: // This task is used to trigger the message loop to exit. michael@0: class ThreadQuitTask : public Task { michael@0: public: michael@0: virtual void Run() { michael@0: MessageLoop::current()->Quit(); michael@0: Thread::SetThreadWasQuitProperly(true); michael@0: } michael@0: }; michael@0: michael@0: // Used to pass data to ThreadMain. This structure is allocated on the stack michael@0: // from within StartWithOptions. michael@0: struct Thread::StartupData { michael@0: // We get away with a const reference here because of how we are allocated. michael@0: const Thread::Options& options; michael@0: michael@0: // Used to synchronize thread startup. michael@0: WaitableEvent event; michael@0: michael@0: explicit StartupData(const Options& opt) michael@0: : options(opt), michael@0: event(false, false) {} michael@0: }; michael@0: michael@0: Thread::Thread(const char *name) michael@0: : startup_data_(NULL), michael@0: thread_(0), michael@0: message_loop_(NULL), michael@0: thread_id_(0), michael@0: name_(name) { michael@0: } michael@0: michael@0: Thread::~Thread() { michael@0: Stop(); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: // We use this thread-local variable to record whether or not a thread exited michael@0: // because its Stop method was called. This allows us to catch cases where michael@0: // MessageLoop::Quit() is called directly, which is unexpected when using a michael@0: // Thread to setup and run a MessageLoop. michael@0: base::LazyInstance lazy_tls_bool( michael@0: base::LINKER_INITIALIZED); michael@0: michael@0: } // namespace michael@0: michael@0: void Thread::SetThreadWasQuitProperly(bool flag) { michael@0: lazy_tls_bool.Pointer()->Set(flag); michael@0: } michael@0: michael@0: bool Thread::GetThreadWasQuitProperly() { michael@0: bool quit_properly = true; michael@0: #ifndef NDEBUG michael@0: quit_properly = lazy_tls_bool.Pointer()->Get(); michael@0: #endif michael@0: return quit_properly; michael@0: } michael@0: michael@0: bool Thread::Start() { michael@0: return StartWithOptions(Options()); michael@0: } michael@0: michael@0: bool Thread::StartWithOptions(const Options& options) { michael@0: DCHECK(!message_loop_); michael@0: michael@0: SetThreadWasQuitProperly(false); michael@0: michael@0: StartupData startup_data(options); michael@0: startup_data_ = &startup_data; michael@0: michael@0: if (!PlatformThread::Create(options.stack_size, this, &thread_)) { michael@0: DLOG(ERROR) << "failed to create thread"; michael@0: startup_data_ = NULL; // Record that we failed to start. michael@0: return false; michael@0: } michael@0: michael@0: // Wait for the thread to start and initialize message_loop_ michael@0: startup_data.event.Wait(); michael@0: michael@0: DCHECK(message_loop_); michael@0: return true; michael@0: } michael@0: michael@0: void Thread::Stop() { michael@0: if (!thread_was_started()) michael@0: return; michael@0: michael@0: // We should only be called on the same thread that started us. michael@0: DCHECK_NE(thread_id_, PlatformThread::CurrentId()); michael@0: michael@0: // StopSoon may have already been called. michael@0: if (message_loop_) michael@0: message_loop_->PostTask(FROM_HERE, new ThreadQuitTask()); michael@0: michael@0: // Wait for the thread to exit. It should already have terminated but make michael@0: // sure this assumption is valid. michael@0: // michael@0: // TODO(darin): Unfortunately, we need to keep message_loop_ around until michael@0: // the thread exits. Some consumers are abusing the API. Make them stop. michael@0: // michael@0: PlatformThread::Join(thread_); michael@0: michael@0: // The thread can't receive messages anymore. michael@0: message_loop_ = NULL; michael@0: michael@0: // The thread no longer needs to be joined. michael@0: startup_data_ = NULL; michael@0: } michael@0: michael@0: void Thread::StopSoon() { michael@0: if (!message_loop_) michael@0: return; michael@0: michael@0: // We should only be called on the same thread that started us. michael@0: DCHECK_NE(thread_id_, PlatformThread::CurrentId()); michael@0: michael@0: // We had better have a message loop at this point! If we do not, then it michael@0: // most likely means that the thread terminated unexpectedly, probably due michael@0: // to someone calling Quit() on our message loop directly. michael@0: DCHECK(message_loop_); michael@0: michael@0: message_loop_->PostTask(FROM_HERE, new ThreadQuitTask()); michael@0: } michael@0: michael@0: void Thread::ThreadMain() { michael@0: char aLocal; michael@0: profiler_register_thread(name_.c_str(), &aLocal); michael@0: mozilla::IOInterposer::RegisterCurrentThread(); michael@0: michael@0: // The message loop for this thread. michael@0: MessageLoop message_loop(startup_data_->options.message_loop_type); michael@0: michael@0: // Complete the initialization of our Thread object. michael@0: thread_id_ = PlatformThread::CurrentId(); michael@0: PlatformThread::SetName(name_.c_str()); michael@0: message_loop.set_thread_name(name_); michael@0: message_loop.set_hang_timeouts(startup_data_->options.transient_hang_timeout, michael@0: startup_data_->options.permanent_hang_timeout); michael@0: message_loop_ = &message_loop; michael@0: michael@0: // Let the thread do extra initialization. michael@0: // Let's do this before signaling we are started. michael@0: Init(); michael@0: michael@0: startup_data_->event.Signal(); michael@0: // startup_data_ can't be touched anymore since the starting thread is now michael@0: // unlocked. michael@0: michael@0: message_loop.Run(); michael@0: michael@0: // Let the thread do extra cleanup. michael@0: CleanUp(); michael@0: michael@0: // Assert that MessageLoop::Quit was called by ThreadQuitTask. michael@0: DCHECK(GetThreadWasQuitProperly()); michael@0: michael@0: mozilla::IOInterposer::UnregisterCurrentThread(); michael@0: profiler_unregister_thread(); michael@0: michael@0: #ifdef MOZ_TASK_TRACER michael@0: mozilla::tasktracer::FreeTraceInfo(); michael@0: #endif michael@0: michael@0: // We can't receive messages anymore. michael@0: message_loop_ = NULL; michael@0: thread_id_ = 0; michael@0: } michael@0: michael@0: } // namespace base