blob: 8bb84ee8e1eece83307f870582ceddf2c136d936 [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/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_worker_pool.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "net/disk_cache/simple/simple_backend_impl.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_DBThread", // DB
"Chrome_FileThread", // FILE
"Chrome_FileUserBlockingThread", // FILE_USER_BLOCKING
"Chrome_ProcessLauncherThread", // PROCESS_LAUNCHER
"Chrome_CacheThread", // CACHE
"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 tracked_objects::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
return BrowserThread::PostDelayedTask(id_, from_here, std::move(task),
delay);
}
bool PostNonNestableDelayedTask(const tracked_objects::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 {
BrowserThreadGlobals()
: blocking_pool(
new base::SequencedWorkerPool(3,
"BrowserBlocking",
base::TaskPriority::USER_VISIBLE)) {}
// 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 RedirectThreadIDToTaskRunner() for threads that are being
// redirected. 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;
const scoped_refptr<base::SequencedWorkerPool> blocking_pool;
};
base::LazyInstance<BrowserThreadGlobals>::Leaky
g_globals = LAZY_INSTANCE_INITIALIZER;
} // 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;
}
// static
void BrowserThreadImpl::ShutdownThreadPool() {
// The goal is to make it impossible for chrome to 'infinite loop' during
// shutdown, but to reasonably expect that all BLOCKING_SHUTDOWN tasks queued
// during shutdown get run. There's nothing particularly scientific about the
// number chosen.
const int kMaxNewShutdownBlockingTasks = 1000;
BrowserThreadGlobals& globals = g_globals.Get();
globals.blocking_pool->Shutdown(kMaxNewShutdownBlockingTasks);
}
// static
void BrowserThreadImpl::FlushThreadPoolHelperForTesting() {
// We don't want to create a pool if none exists.
if (g_globals == nullptr)
return;
g_globals.Get().blocking_pool->FlushForTesting();
disk_cache::SimpleBackendImpl::FlushWorkerPoolForTesting();
}
void BrowserThreadImpl::Init() {
BrowserThreadGlobals& globals = g_globals.Get();
#if DCHECK_IS_ON()
{
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()
if (identifier_ == BrowserThread::DB ||
identifier_ == BrowserThread::FILE ||
identifier_ == BrowserThread::FILE_USER_BLOCKING ||
identifier_ == BrowserThread::PROCESS_LAUNCHER ||
identifier_ == BrowserThread::CACHE) {
// Nesting and task observers are not allowed on redirected threads.
base::RunLoop::DisallowNestingOnCurrentThread();
message_loop()->DisallowTaskObservers();
}
if (identifier_ == BrowserThread::IO) {
BrowserThreadDelegateAtomicPtr delegate =
base::subtle::NoBarrier_Load(&globals.io_thread_delegate);
if (delegate)
reinterpret_cast<BrowserThreadDelegate*>(delegate)->Init();
}
}
// 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::DBThreadRun(base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
NOINLINE void BrowserThreadImpl::FileThreadRun(base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
NOINLINE void BrowserThreadImpl::FileUserBlockingThreadRun(
base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
NOINLINE void BrowserThreadImpl::ProcessLauncherThreadRun(
base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
NOINLINE void BrowserThreadImpl::CacheThreadRun(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::DB:
return DBThreadRun(run_loop);
case BrowserThread::FILE:
return FileThreadRun(run_loop);
case BrowserThread::FILE_USER_BLOCKING:
return FileUserBlockingThreadRun(run_loop);
case BrowserThread::PROCESS_LAUNCHER:
return ProcessLauncherThreadRun(run_loop);
case BrowserThread::CACHE:
return CacheThreadRun(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) {
BrowserThreadDelegateAtomicPtr delegate =
base::subtle::NoBarrier_Load(&globals.io_thread_delegate);
if (delegate)
reinterpret_cast<BrowserThreadDelegate*>(delegate)->CleanUp();
}
// 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);
}
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
void BrowserThreadImpl::RedirectThreadIDToTaskRunner(
BrowserThread::ID identifier,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(task_runner);
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK(!globals.task_runners[identifier]);
DCHECK_EQ(globals.states[identifier], BrowserThreadState::UNINITIALIZED);
globals.task_runners[identifier] = std::move(task_runner);
globals.states[identifier] = BrowserThreadState::RUNNING;
}
// static
void BrowserThreadImpl::StopRedirectionOfThreadID(
BrowserThread::ID identifier) {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock auto_lock(globals.lock);
DCHECK(globals.task_runners[identifier]);
// Change the state to SHUTDOWN to stop accepting new tasks. Note: this is
// different from non-redirected threads which continue accepting tasks while
// being joined and only quit when idle. However, any tasks for which this
// difference matters was already racy as any thread posting a task after the
// Signal task below can't be synchronized with the joining thread. Therefore,
// that task could already come in before or after the join had completed in
// the non-redirection world. Entering SHUTDOWN early merely skews this race
// towards making it less likely such a task is accepted by the joined thread
// which is fine.
DCHECK_EQ(globals.states[identifier], BrowserThreadState::RUNNING);
globals.states[identifier] = BrowserThreadState::SHUTDOWN;
// Wait for all pending tasks to complete.
base::WaitableEvent flushed(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
globals.task_runners[identifier]->PostTask(
FROM_HERE,
base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&flushed)));
{
base::AutoUnlock auto_lock(globals.lock);
flushed.Wait();
}
// Only reset the task runner after running pending tasks so that
// BrowserThread::CurrentlyOn() works in their scope.
globals.task_runners[identifier] = nullptr;
// Note: it's still possible for tasks to be posted to that task runner after
// this point (e.g. through a previously obtained ThreadTaskRunnerHandle or by
// one of the last tasks re-posting to its ThreadTaskRunnerHandle) but the
// BrowserThread API itself won't accept tasks. Such tasks are ultimately
// guaranteed to run before TaskScheduler::Shutdown() returns but may break
// the assumption in PostTaskHelper that BrowserThread::ID A > B will always
// succeed to post to B. This is pretty much the only observable difference
// between a redirected thread and a real one and is one we're willing to live
// with for this experiment. TODO(gab): fix this before enabling the
// experiment by default on trunk, http://crbug.com/653916.
}
// static
bool BrowserThreadImpl::PostTaskHelper(
BrowserThread::ID identifier,
const tracked_objects::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();
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
bool BrowserThread::PostBlockingPoolSequencedTask(
const std::string& sequence_token_name,
const tracked_objects::Location& from_here,
base::OnceClosure task) {
return g_globals.Get().blocking_pool->PostNamedSequencedWorkerTask(
sequence_token_name, from_here, std::move(task));
}
// static
void BrowserThread::PostAfterStartupTask(
const tracked_objects::Location& from_here,
const scoped_refptr<base::TaskRunner>& task_runner,
base::OnceClosure task) {
GetContentClient()->browser()->PostAfterStartupTask(from_here, task_runner,
std::move(task));
}
// static
base::SequencedWorkerPool* BrowserThread::GetBlockingPool() {
return g_globals.Get().blocking_pool.get();
}
// static
bool BrowserThread::IsThreadInitialized(ID identifier) {
if (g_globals == nullptr)
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::INITIALIZED ||
globals.states[identifier] == BrowserThreadState::RUNNING;
}
// 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 == nullptr)
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 tracked_objects::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 tracked_objects::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 tracked_objects::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 tracked_objects::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 tracked_objects::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 == nullptr)
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