blob: b8b6b2077cc22206d4fd976904e94e72fc5b6a82 [file] [log] [blame]
// Copyright (c) 2012 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 "content/browser/browser_thread_impl.h"
#include <string>
#include <utility>
#include "base/atomicops.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread_delegate.h"
#include "content/public/browser/content_browser_client.h"
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#endif
namespace content {
namespace {
// Friendly names for the well-known threads.
static const char* const g_browser_thread_names[BrowserThread::ID_COUNT] = {
"", // UI (name assembled in browser_main.cc).
"Chrome_IOThread", // IO
};
static const char* GetThreadName(BrowserThread::ID thread) {
if (BrowserThread::UI < thread && thread < BrowserThread::ID_COUNT)
return g_browser_thread_names[thread];
if (thread == BrowserThread::UI)
return "Chrome_UIThread";
return "Unknown Thread";
}
// An implementation of SingleThreadTaskRunner to be used in conjunction
// with BrowserThread.
// TODO(gab): Consider replacing this with |g_globals->task_runners| -- only
// works if none are requested before starting the threads.
class BrowserThreadTaskRunner : public base::SingleThreadTaskRunner {
public:
explicit BrowserThreadTaskRunner(BrowserThread::ID identifier)
: id_(identifier) {}
// SingleThreadTaskRunner implementation.
bool PostDelayedTask(const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
return BrowserThread::PostDelayedTask(id_, from_here, std::move(task),
delay);
}
bool PostNonNestableDelayedTask(const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
return BrowserThread::PostNonNestableDelayedTask(id_, from_here,
std::move(task), delay);
}
bool RunsTasksInCurrentSequence() const override {
return BrowserThread::CurrentlyOn(id_);
}
protected:
~BrowserThreadTaskRunner() override {}
private:
BrowserThread::ID id_;
DISALLOW_COPY_AND_ASSIGN(BrowserThreadTaskRunner);
};
// A separate helper is used just for the task runners, in order to avoid
// needing to initialize the globals to create a task runner.
struct BrowserThreadTaskRunners {
BrowserThreadTaskRunners() {
for (int i = 0; i < BrowserThread::ID_COUNT; ++i) {
proxies[i] =
new BrowserThreadTaskRunner(static_cast<BrowserThread::ID>(i));
}
}
scoped_refptr<base::SingleThreadTaskRunner> proxies[BrowserThread::ID_COUNT];
};
base::LazyInstance<BrowserThreadTaskRunners>::Leaky g_task_runners =
LAZY_INSTANCE_INITIALIZER;
// State of a given BrowserThread::ID in chronological order throughout the
// browser process' lifetime.
enum BrowserThreadState {
// BrowserThread::ID isn't associated with anything yet.
UNINITIALIZED = 0,
// BrowserThread::ID is associated with a BrowserThreadImpl instance but the
// underlying thread hasn't started yet.
INITIALIZED,
// BrowserThread::ID is associated to a TaskRunner and is accepting tasks.
RUNNING,
// BrowserThread::ID no longer accepts tasks.
SHUTDOWN
};
using BrowserThreadDelegateAtomicPtr = base::subtle::AtomicWord;
struct BrowserThreadGlobals {
// This lock protects |task_runners| and |states|. Do not read or modify those
// arrays without holding this lock. Do not block while holding this lock.
base::Lock lock;
// This array is filled either as the underlying threads start and invoke
// Init() or in BrowserThreadImpl() when a MessageLoop* is provided at
// construction. It is not emptied during shutdown in order to support
// RunsTasksInCurrentSequence() until the very end.
scoped_refptr<base::SingleThreadTaskRunner>
task_runners[BrowserThread::ID_COUNT];
// Holds the state of each BrowserThread::ID.
BrowserThreadState states[BrowserThread::ID_COUNT] = {};
// Only atomic operations are used on this pointer. The delegate isn't owned
// by BrowserThreadGlobals, rather by whoever calls
// BrowserThread::SetIOThreadDelegate.
BrowserThreadDelegateAtomicPtr io_thread_delegate = 0;
// This locks protects |is_io_thread_initialized|. Do not read or modify this
// variable without holding this lock.
base::Lock io_thread_lock;
// A flag indicates whether the BrowserThreadDelegate of the BrowserThread::IO
// thread has been initialized.
bool is_io_thread_initialized = false;
};
base::LazyInstance<BrowserThreadGlobals>::Leaky
g_globals = LAZY_INSTANCE_INITIALIZER;
void InitIOThreadDelegateOnIOThread() {
BrowserThreadDelegateAtomicPtr delegate =
base::subtle::NoBarrier_Load(&g_globals.Get().io_thread_delegate);
if (delegate)
reinterpret_cast<BrowserThreadDelegate*>(delegate)->Init();
}
bool IsIOThreadInitialized() {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.io_thread_lock);
return globals.is_io_thread_initialized;
}
void SetIsIOThreadInitialized(bool is_io_thread_initialized) {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.io_thread_lock);
globals.is_io_thread_initialized = is_io_thread_initialized;
}
} // namespace
BrowserThreadImpl::BrowserThreadImpl(ID identifier)
: Thread(GetThreadName(identifier)), identifier_(identifier) {
Initialize();
}
BrowserThreadImpl::BrowserThreadImpl(ID identifier,
base::MessageLoop* message_loop)
: Thread(GetThreadName(identifier)), identifier_(identifier) {
SetMessageLoop(message_loop);
Initialize();
// If constructed with an explicit message loop, this is a fake
// BrowserThread which runs on the current thread.
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK(!globals.task_runners[identifier_]);
globals.task_runners[identifier_] = task_runner();
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::INITIALIZED);
globals.states[identifier_] = BrowserThreadState::RUNNING;
}
void BrowserThreadImpl::Init() {
#if DCHECK_IS_ON()
{
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
// |globals| should already have been initialized for |identifier_| in
// BrowserThreadImpl::StartWithOptions(). If this isn't the case it's likely
// because this BrowserThreadImpl's owner incorrectly used Thread::Start.*()
// instead of BrowserThreadImpl::Start.*().
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
DCHECK(globals.task_runners[identifier_]);
DCHECK(globals.task_runners[identifier_]->RunsTasksInCurrentSequence());
}
#endif // DCHECK_IS_ON()
}
// We disable optimizations for this block of functions so the compiler doesn't
// merge them all together.
MSVC_DISABLE_OPTIMIZE()
MSVC_PUSH_DISABLE_WARNING(4748)
NOINLINE void BrowserThreadImpl::UIThreadRun(base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
NOINLINE void BrowserThreadImpl::IOThreadRun(base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
MSVC_POP_WARNING()
MSVC_ENABLE_OPTIMIZE();
void BrowserThreadImpl::Run(base::RunLoop* run_loop) {
#if defined(OS_ANDROID)
// Not to reset thread name to "Thread-???" by VM, attach VM with thread name.
// Though it may create unnecessary VM thread objects, keeping thread name
// gives more benefit in debugging in the platform.
if (!thread_name().empty()) {
base::android::AttachCurrentThreadWithName(thread_name());
}
#endif
BrowserThread::ID thread_id = ID_COUNT;
CHECK(GetCurrentThreadIdentifier(&thread_id));
CHECK_EQ(identifier_, thread_id);
switch (identifier_) {
case BrowserThread::UI:
return UIThreadRun(run_loop);
case BrowserThread::IO:
return IOThreadRun(run_loop);
case BrowserThread::ID_COUNT:
CHECK(false); // This shouldn't actually be reached!
break;
}
// |identifier_| must be set to a valid enum value in the constructor, so it
// should be impossible to reach here.
CHECK(false);
}
void BrowserThreadImpl::CleanUp() {
BrowserThreadGlobals& globals = g_globals.Get();
if (identifier_ == BrowserThread::IO && IsIOThreadInitialized()) {
BrowserThreadDelegateAtomicPtr delegate =
base::subtle::NoBarrier_Load(&globals.io_thread_delegate);
if (delegate)
reinterpret_cast<BrowserThreadDelegate*>(delegate)->CleanUp();
SetIsIOThreadInitialized(false);
}
// Change the state to SHUTDOWN so that PostTaskHelper stops accepting tasks
// for this thread. Do not clear globals.task_runners[identifier_] so that
// BrowserThread::CurrentlyOn() works from the MessageLoop's
// DestructionObservers.
base::AutoLock lock(globals.lock);
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
globals.states[identifier_] = BrowserThreadState::SHUTDOWN;
}
void BrowserThreadImpl::Initialize() {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK_GE(identifier_, 0);
DCHECK_LT(identifier_, ID_COUNT);
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::UNINITIALIZED);
globals.states[identifier_] = BrowserThreadState::INITIALIZED;
}
// static
void BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::ID identifier) {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK_EQ(globals.states[identifier], BrowserThreadState::SHUTDOWN);
globals.states[identifier] = BrowserThreadState::UNINITIALIZED;
globals.task_runners[identifier] = nullptr;
if (identifier == BrowserThread::IO)
SetIOThreadDelegate(nullptr);
}
void BrowserThreadImpl::InitIOThreadDelegate() {
DCHECK(!IsIOThreadInitialized());
SetIsIOThreadInitialized(true);
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::BindOnce(&InitIOThreadDelegateOnIOThread));
}
BrowserThreadImpl::~BrowserThreadImpl() {
// All Thread subclasses must call Stop() in the destructor. This is
// doubly important here as various bits of code check they are on
// the right BrowserThread.
Stop();
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
// This thread should have gone through Cleanup() as part of Stop() and be in
// the SHUTDOWN state already (unless it uses an externally provided
// MessageLoop instead of a real underlying thread and thus doesn't go through
// Cleanup()).
if (using_external_message_loop()) {
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
globals.states[identifier_] = BrowserThreadState::SHUTDOWN;
} else {
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::SHUTDOWN);
}
#if DCHECK_IS_ON()
// Double check that the threads are ordered correctly in the enumeration.
for (int i = identifier_ + 1; i < ID_COUNT; ++i) {
DCHECK(globals.states[i] == BrowserThreadState::SHUTDOWN ||
globals.states[i] == BrowserThreadState::UNINITIALIZED)
<< "Threads must be listed in the reverse order that they die";
}
#endif
}
bool BrowserThreadImpl::Start() {
return StartWithOptions(base::Thread::Options());
}
bool BrowserThreadImpl::StartWithOptions(const Options& options) {
BrowserThreadGlobals& globals = g_globals.Get();
// Holding the lock is necessary when kicking off the thread to ensure
// |states| and |task_runners| are updated before it gets to query them.
base::AutoLock lock(globals.lock);
bool result = Thread::StartWithOptions(options);
// Although the thread is starting asynchronously, the MessageLoop is already
// ready to accept tasks and as such this BrowserThreadImpl is considered as
// "running".
DCHECK(!globals.task_runners[identifier_]);
globals.task_runners[identifier_] = task_runner();
DCHECK(globals.task_runners[identifier_]);
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::INITIALIZED);
globals.states[identifier_] = BrowserThreadState::RUNNING;
return result;
}
bool BrowserThreadImpl::StartAndWaitForTesting() {
if (!Start())
return false;
WaitUntilThreadStarted();
return true;
}
// static
bool BrowserThreadImpl::PostTaskHelper(BrowserThread::ID identifier,
const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay,
bool nestable) {
DCHECK_GE(identifier, 0);
DCHECK_LT(identifier, ID_COUNT);
// Optimization: to avoid unnecessary locks, we listed the ID enumeration in
// order of lifetime. So no need to lock if we know that the target thread
// outlives current thread as that implies the current thread only ever sees
// the target thread in its RUNNING state.
// Note: since the array is so small, ok to loop instead of creating a map,
// which would require a lock because std::map isn't thread safe, defeating
// the whole purpose of this optimization.
BrowserThread::ID current_thread = ID_COUNT;
bool target_thread_outlives_current =
GetCurrentThreadIdentifier(&current_thread) &&
current_thread >= identifier;
BrowserThreadGlobals& globals = g_globals.Get();
if (!target_thread_outlives_current)
globals.lock.Acquire();
// Posting tasks before BrowserThreads are initialized is incorrect as it
// would silently no-op. If you need to support posting early, gate it on
// BrowserThread::IsThreadInitialized(). If you hit this in unittests, you
// most likely posted a task outside the scope of a TestBrowserThreadBundle.
DCHECK_GE(globals.states[identifier], BrowserThreadState::RUNNING);
const bool accepting_tasks =
globals.states[identifier] == BrowserThreadState::RUNNING;
if (accepting_tasks) {
base::SingleThreadTaskRunner* task_runner =
globals.task_runners[identifier].get();
DCHECK(task_runner);
if (nestable) {
task_runner->PostDelayedTask(from_here, std::move(task), delay);
} else {
task_runner->PostNonNestableDelayedTask(from_here, std::move(task),
delay);
}
}
if (!target_thread_outlives_current)
globals.lock.Release();
return accepting_tasks;
}
// static
void BrowserThread::PostAfterStartupTask(
const base::Location& from_here,
const scoped_refptr<base::TaskRunner>& task_runner,
base::OnceClosure task) {
GetContentClient()->browser()->PostAfterStartupTask(from_here, task_runner,
std::move(task));
}
// static
bool BrowserThread::IsThreadInitialized(ID identifier) {
if (!g_globals.IsCreated())
return false;
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK_GE(identifier, 0);
DCHECK_LT(identifier, ID_COUNT);
bool running =
globals.states[identifier] == BrowserThreadState::INITIALIZED ||
globals.states[identifier] == BrowserThreadState::RUNNING;
if (identifier != BrowserThread::IO)
return running;
return running && IsIOThreadInitialized();
}
// static
bool BrowserThread::CurrentlyOn(ID identifier) {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK_GE(identifier, 0);
DCHECK_LT(identifier, ID_COUNT);
return globals.task_runners[identifier] &&
globals.task_runners[identifier]->RunsTasksInCurrentSequence();
}
// static
std::string BrowserThread::GetDCheckCurrentlyOnErrorMessage(ID expected) {
std::string actual_name = base::PlatformThread::GetName();
if (actual_name.empty())
actual_name = "Unknown Thread";
std::string result = "Must be called on ";
result += GetThreadName(expected);
result += "; actually called on ";
result += actual_name;
result += ".";
return result;
}
// static
bool BrowserThread::IsMessageLoopValid(ID identifier) {
if (!g_globals.IsCreated())
return false;
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK_GE(identifier, 0);
DCHECK_LT(identifier, ID_COUNT);
return globals.states[identifier] == BrowserThreadState::RUNNING;
}
// static
bool BrowserThread::PostTask(ID identifier,
const base::Location& from_here,
base::OnceClosure task) {
return BrowserThreadImpl::PostTaskHelper(
identifier, from_here, std::move(task), base::TimeDelta(), true);
}
// static
bool BrowserThread::PostDelayedTask(ID identifier,
const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) {
return BrowserThreadImpl::PostTaskHelper(identifier, from_here,
std::move(task), delay, true);
}
// static
bool BrowserThread::PostNonNestableTask(ID identifier,
const base::Location& from_here,
base::OnceClosure task) {
return BrowserThreadImpl::PostTaskHelper(
identifier, from_here, std::move(task), base::TimeDelta(), false);
}
// static
bool BrowserThread::PostNonNestableDelayedTask(ID identifier,
const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) {
return BrowserThreadImpl::PostTaskHelper(identifier, from_here,
std::move(task), delay, false);
}
// static
bool BrowserThread::PostTaskAndReply(ID identifier,
const base::Location& from_here,
base::OnceClosure task,
base::OnceClosure reply) {
return GetTaskRunnerForThread(identifier)
->PostTaskAndReply(from_here, std::move(task), std::move(reply));
}
// static
bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier) {
if (!g_globals.IsCreated())
return false;
BrowserThreadGlobals& globals = g_globals.Get();
// Profiler to track potential contention on |globals.lock|. This only does
// real work on canary and local dev builds, so the cost of having this here
// should be minimal.
base::AutoLock lock(globals.lock);
for (int i = 0; i < ID_COUNT; ++i) {
if (globals.task_runners[i] &&
globals.task_runners[i]->RunsTasksInCurrentSequence()) {
*identifier = static_cast<ID>(i);
return true;
}
}
return false;
}
// static
scoped_refptr<base::SingleThreadTaskRunner>
BrowserThread::GetTaskRunnerForThread(ID identifier) {
return g_task_runners.Get().proxies[identifier];
}
// static
void BrowserThread::SetIOThreadDelegate(BrowserThreadDelegate* delegate) {
BrowserThreadGlobals& globals = g_globals.Get();
BrowserThreadDelegateAtomicPtr old_delegate =
base::subtle::NoBarrier_AtomicExchange(
&globals.io_thread_delegate,
reinterpret_cast<BrowserThreadDelegateAtomicPtr>(delegate));
// This catches registration when previously registered.
DCHECK(!delegate || !old_delegate);
}
} // namespace content