Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2006-2009 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/thread.h"
#include "base/string_util.h"
#include "base/thread_local.h"
#include "base/waitable_event.h"
#include "GeckoProfiler.h"
#include "mozilla/EventQueue.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/ThreadEventQueue.h"
#include "nsThreadUtils.h"
#include "nsThreadManager.h"
namespace base {
// This task is used to trigger the message loop to exit.
class ThreadQuitTask : public mozilla::Runnable {
public:
ThreadQuitTask() : mozilla::Runnable("ThreadQuitTask") {}
NS_IMETHOD Run() override {
MessageLoop::current()->Quit();
Thread::SetThreadWasQuitProperly(true);
return NS_OK;
}
};
// Used to pass data to ThreadMain. This structure is allocated on the stack
// from within StartWithOptions.
struct Thread::StartupData {
// We get away with a const reference here because of how we are allocated.
const Thread::Options& options;
// Used to synchronize thread startup.
WaitableEvent event;
explicit StartupData(const Options& opt)
: options(opt), event(false, false) {}
};
Thread::Thread(const char* name)
: startup_data_(NULL),
thread_(0),
message_loop_(NULL),
thread_id_(0),
name_(name) {
MOZ_COUNT_CTOR(base::Thread);
}
Thread::~Thread() {
MOZ_COUNT_DTOR(base::Thread);
Stop();
}
namespace {
// We use this thread-local variable to record whether or not a thread exited
// because its Stop method was called. This allows us to catch cases where
// MessageLoop::Quit() is called directly, which is unexpected when using a
// Thread to setup and run a MessageLoop.
static base::ThreadLocalBoolean& get_tls_bool() {
static base::ThreadLocalBoolean tls_ptr;
return tls_ptr;
}
} // namespace
void Thread::SetThreadWasQuitProperly(bool flag) { get_tls_bool().Set(flag); }
bool Thread::GetThreadWasQuitProperly() {
bool quit_properly = true;
#ifndef NDEBUG
quit_properly = get_tls_bool().Get();
#endif
return quit_properly;
}
bool Thread::Start() { return StartWithOptions(Options()); }
bool Thread::StartWithOptions(const Options& options) {
DCHECK(!message_loop_);
SetThreadWasQuitProperly(false);
StartupData startup_data(options);
startup_data_ = &startup_data;
if (!PlatformThread::Create(options.stack_size, this, &thread_)) {
DLOG(ERROR) << "failed to create thread";
startup_data_ = NULL; // Record that we failed to start.
return false;
}
// Wait for the thread to start and initialize message_loop_
startup_data.event.Wait();
DCHECK(message_loop_);
return true;
}
void Thread::Stop() {
if (!thread_was_started()) return;
// We should only be called on the same thread that started us.
DCHECK_NE(thread_id_, PlatformThread::CurrentId());
// StopSoon may have already been called.
if (message_loop_) {
RefPtr<ThreadQuitTask> task = new ThreadQuitTask();
message_loop_->PostTask(task.forget());
}
// Wait for the thread to exit. It should already have terminated but make
// sure this assumption is valid.
//
// TODO(darin): Unfortunately, we need to keep message_loop_ around until
// the thread exits. Some consumers are abusing the API. Make them stop.
//
PlatformThread::Join(thread_);
// The thread can't receive messages anymore.
message_loop_ = NULL;
// The thread no longer needs to be joined.
startup_data_ = NULL;
}
void Thread::StopSoon() {
if (!message_loop_) return;
// We should only be called on the same thread that started us.
DCHECK_NE(thread_id_, PlatformThread::CurrentId());
// We had better have a message loop at this point! If we do not, then it
// most likely means that the thread terminated unexpectedly, probably due
// to someone calling Quit() on our message loop directly.
DCHECK(message_loop_);
RefPtr<ThreadQuitTask> task = new ThreadQuitTask();
message_loop_->PostTask(task.forget());
}
void Thread::ThreadMain() {
nsCOMPtr<nsIThread> xpcomThread;
auto loopType = startup_data_->options.message_loop_type;
if (loopType == MessageLoop::TYPE_MOZILLA_NONMAINTHREAD ||
loopType == MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD) {
auto queue = mozilla::MakeRefPtr<mozilla::ThreadEventQueue>(
mozilla::MakeUnique<mozilla::EventQueue>());
xpcomThread = nsThreadManager::get().CreateCurrentThread(queue);
} else {
xpcomThread = NS_GetCurrentThread();
}
AUTO_PROFILER_REGISTER_THREAD(name_.c_str());
mozilla::IOInterposer::RegisterCurrentThread();
// The message loop for this thread.
MessageLoop message_loop(startup_data_->options.message_loop_type,
xpcomThread);
xpcomThread = nullptr;
// Complete the initialization of our Thread object.
thread_id_ = PlatformThread::CurrentId();
PlatformThread::SetName(name_.c_str());
NS_SetCurrentThreadName(name_.c_str());
message_loop.set_thread_name(name_);
message_loop.set_hang_timeouts(startup_data_->options.transient_hang_timeout,
startup_data_->options.permanent_hang_timeout);
message_loop_ = &message_loop;
// Let the thread do extra initialization.
// Let's do this before signaling we are started.
Init();
startup_data_->event.Signal();
// startup_data_ can't be touched anymore since the starting thread is now
// unlocked.
message_loop.Run();
// Let the thread do extra cleanup.
CleanUp();
// Assert that MessageLoop::Quit was called by ThreadQuitTask.
DCHECK(GetThreadWasQuitProperly());
mozilla::IOInterposer::UnregisterCurrentThread();
// We can't receive messages anymore.
message_loop_ = NULL;
thread_id_ = 0;
}
} // namespace base