Chromium Code Reviews| Index: content/browser/browser_thread_impl.cc |
| diff --git a/content/browser/browser_thread_impl.cc b/content/browser/browser_thread_impl.cc |
| index 18eff7c8672e37ecf83381b566474c4d75fc9af3..30efdf539f6dbfd2577822523208ba44bcc9564d 100644 |
| --- a/content/browser/browser_thread_impl.cc |
| +++ b/content/browser/browser_thread_impl.cc |
| @@ -4,18 +4,18 @@ |
| #include "content/browser/browser_thread_impl.h" |
| -#include <string.h> |
| - |
| #include <string> |
| #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/profiler/scoped_tracker.h" |
| #include "base/run_loop.h" |
| -#include "base/single_thread_task_runner.h" |
| +#include "base/task_scheduler/task_scheduler.h" |
|
fdoray
2016/12/01 15:15:21
Not needed.
gab
2016/12/07 19:15:27
Done.
|
| #include "base/threading/platform_thread.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "build/build_config.h" |
| @@ -52,6 +52,8 @@ static const char* GetThreadName(BrowserThread::ID 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) |
| @@ -99,35 +101,45 @@ struct BrowserThreadTaskRunners { |
| 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 but the underlying thread hasn't run yet. |
|
fdoray
2016/12/01 15:15:22
The INITIALIZED state is useless. See comment in B
gab
2016/12/07 19:15:27
It is useful, replied there.
|
| + INITIALIZED, |
| + // BrowserThread::ID is associated to a live thread. |
|
fdoray
2016/12/01 15:15:22
... is associated to TaskRunner and is accepting t
gab
2016/12/07 19:15:27
Done.
|
| + RUNNING, |
| + // BrowserThread::ID is associated to a thread having been or being shutdown. |
|
fdoray
2016/12/01 15:15:21
... no longer accepts tasks. If it was backed by a
gab
2016/12/07 19:15:27
Done (without second sentence).
|
| + SHUTDOWN |
| +}; |
| + |
| struct BrowserThreadGlobals { |
| BrowserThreadGlobals() |
| : blocking_pool( |
| new base::SequencedWorkerPool(3, |
| "BrowserBlocking", |
| - base::TaskPriority::USER_VISIBLE)) { |
| - memset(threads, 0, BrowserThread::ID_COUNT * sizeof(threads[0])); |
| - memset(thread_ids, 0, BrowserThread::ID_COUNT * sizeof(thread_ids[0])); |
| - memset(thread_delegates, 0, |
| - BrowserThread::ID_COUNT * sizeof(thread_delegates[0])); |
| - } |
| + base::TaskPriority::USER_VISIBLE)) {} |
| - // This lock protects |threads| and |thread_ids|. Do not read or modify those |
| + // 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 protected by |lock|. IDs in this array are populated as soon |
| - // as their respective thread is started and are never reset. |
| - base::PlatformThreadId thread_ids[BrowserThread::ID_COUNT]; |
| + // 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 |
| + // RunsTasksOnCurrentThread() until the very end. |
| + scoped_refptr<base::SingleThreadTaskRunner> |
| + task_runners[BrowserThread::ID_COUNT]; |
| - // This array is protected by |lock|. The threads are not owned by this |
| - // array. Typically, the threads are owned on the UI thread by |
| - // BrowserMainLoop. BrowserThreadImpl objects remove themselves from this |
| - // array upon destruction. |
| - BrowserThreadImpl* threads[BrowserThread::ID_COUNT]; |
| + // Holds the state of each BrowserThread::ID. This needs to be a separate bit |
| + // because |threads| isn't a good signal per redirected IDs remaining null and |
|
fdoray
2016/12/01 15:15:22
|threads| doesn't exist
gab
2016/12/07 19:15:27
Done.
|
| + // |task_runners| isn't either per not being cleaned up on shutdown. |
| + BrowserThreadState states[BrowserThread::ID_COUNT] = {}; |
| // Only atomic operations are used on this array. The delegates are not owned |
| // by this array, rather by whoever calls BrowserThread::SetDelegate. |
| - BrowserThreadDelegate* thread_delegates[BrowserThread::ID_COUNT]; |
| + BrowserThreadDelegate* thread_delegates[BrowserThread::ID_COUNT] = {}; |
| const scoped_refptr<base::SequencedWorkerPool> blocking_pool; |
| }; |
| @@ -140,13 +152,6 @@ base::LazyInstance<BrowserThreadGlobals>::Leaky |
| BrowserThreadImpl::BrowserThreadImpl(ID identifier) |
| : Thread(GetThreadName(identifier)), identifier_(identifier) { |
| Initialize(); |
| - |
| - // Unit tests may create multiple TestBrowserThreadBundles, causing the same |
| - // BrowserThread ID to be reinitialized. We explicitly clear the thread ID |
| - // here so Start() can sanity check. |
| - BrowserThreadGlobals& globals = g_globals.Get(); |
| - base::AutoLock lock(globals.lock); |
| - globals.thread_ids[identifier] = base::kInvalidThreadId; |
| } |
| BrowserThreadImpl::BrowserThreadImpl(ID identifier, |
| @@ -155,11 +160,16 @@ BrowserThreadImpl::BrowserThreadImpl(ID identifier, |
| SetMessageLoop(message_loop); |
| Initialize(); |
| - // If constructed with an explicit message loop, this is a fake BrowserThread |
| - // which runs on the current thread. |
| + // 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); |
| - globals.thread_ids[identifier] = base::PlatformThread::CurrentId(); |
| + |
| + DCHECK(!globals.task_runners[identifier_]); |
| + globals.task_runners[identifier_] = task_runner(); |
| + |
| + DCHECK_EQ(globals.states[identifier_], BrowserThreadState::INITIALIZED); |
| + globals.states[identifier_] = BrowserThreadState::RUNNING; |
| } |
| // static |
| @@ -185,14 +195,22 @@ void BrowserThreadImpl::FlushThreadPoolHelperForTesting() { |
| void BrowserThreadImpl::Init() { |
| BrowserThreadGlobals& globals = g_globals.Get(); |
| + // |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(globals.task_runners[identifier_]); |
| + DCHECK(globals.task_runners[identifier_]->RunsTasksOnCurrentThread()); |
| + DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING); |
| + |
| if (BrowserThread::CurrentlyOn(BrowserThread::DB) || |
| BrowserThread::CurrentlyOn(BrowserThread::FILE) || |
| BrowserThread::CurrentlyOn(BrowserThread::FILE_USER_BLOCKING) || |
| BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER) || |
| BrowserThread::CurrentlyOn(BrowserThread::CACHE)) { |
| - base::MessageLoop* message_loop = base::MessageLoop::current(); |
| - message_loop->DisallowNesting(); |
| - message_loop->DisallowTaskObservers(); |
| + // Nesting and task observers are not allowed on redirected threads. |
| + message_loop()->DisallowNesting(); |
| + message_loop()->DisallowTaskObservers(); |
| } |
| using base::subtle::AtomicWord; |
| @@ -314,7 +332,8 @@ void BrowserThreadImpl::CleanUp() { |
| // to prevent a race with accessing the message loop in PostTaskHelper(), |
| // remove this thread from the global array now. |
| base::AutoLock lock(globals.lock); |
| - globals.threads[identifier_] = nullptr; |
| + DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING); |
| + globals.states[identifier_] = BrowserThreadState::SHUTDOWN; |
| } |
| void BrowserThreadImpl::Initialize() { |
| @@ -323,8 +342,19 @@ void BrowserThreadImpl::Initialize() { |
| base::AutoLock lock(globals.lock); |
| DCHECK_GE(identifier_, 0); |
| DCHECK_LT(identifier_, ID_COUNT); |
| - DCHECK_EQ(globals.threads[identifier_], nullptr); |
| - globals.threads[identifier_] = this; |
| + DCHECK_EQ(globals.states[identifier_], BrowserThreadState::UNINITIALIZED); |
| + globals.states[identifier_] = BrowserThreadState::INITIALIZED; |
|
fdoray
2016/12/01 15:15:21
You can get rid of the INITIALIZED state. Initiali
gab
2016/12/07 19:15:27
No, INITIALIZED is required to support BrowserThre
|
| +} |
| + |
| +// 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; |
| + SetDelegate(identifier, nullptr); |
| } |
| BrowserThreadImpl::~BrowserThreadImpl() { |
| @@ -335,12 +365,22 @@ BrowserThreadImpl::~BrowserThreadImpl() { |
| BrowserThreadGlobals& globals = g_globals.Get(); |
| base::AutoLock lock(globals.lock); |
| - globals.threads[identifier_] = nullptr; |
| -#ifndef NDEBUG |
| + // 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.threads[i]) << |
| - "Threads must be listed in the reverse order that they die"; |
| + DCHECK(globals.states[i] == BrowserThreadState::SHUTDOWN || |
| + globals.states[i] == BrowserThreadState::UNINITIALIZED) |
| + << "Threads must be listed in the reverse order that they die"; |
| } |
| #endif |
| } |
| @@ -350,14 +390,24 @@ bool BrowserThreadImpl::Start() { |
| } |
| bool BrowserThreadImpl::StartWithOptions(const Options& options) { |
| - // The global thread table needs to be locked while a new thread is |
| - // starting, as the new thread can asynchronously start touching the |
| - // table (and other thread's message_loop). |
| + // The lock doesn't need to be held to start the thread. The thread will |
| + // start asynchronously and eventually invoke BrowserThreadImpl::Init() |
| + // to complete initialization. |
| + bool result = Thread::StartWithOptions(options); |
| + |
| BrowserThreadGlobals& globals = g_globals.Get(); |
| base::AutoLock lock(globals.lock); |
| - DCHECK_EQ(globals.thread_ids[identifier_], base::kInvalidThreadId); |
| - bool result = Thread::StartWithOptions(options); |
| - globals.thread_ids[identifier_] = GetThreadId(); |
| + |
| + // Although the 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; |
| } |
| @@ -367,6 +417,27 @@ bool BrowserThreadImpl::StartAndWaitForTesting() { |
| WaitUntilThreadStarted(); |
| return true; |
| } |
| + |
| +// static |
| +void BrowserThreadImpl::RedirectThreadIDToTaskRunner( |
| + BrowserThread::ID identifier, |
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| + BrowserThreadGlobals& globals = g_globals.Get(); |
| + base::AutoLock lock(globals.lock); |
| + if (task_runner) { |
| + 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; |
| + } else { |
| + // Cancelling redirection should only be done on previously redirected |
| + // threads. |
|
fdoray
2016/12/01 15:15:22
Add:
// Change the state to SHUTDOWN so that Post
gab
2016/12/07 19:15:27
Detailed comments added to BrowserThreadImpl::Stop
|
| + DCHECK_EQ(globals.states[identifier], BrowserThreadState::RUNNING); |
| + globals.states[identifier] = BrowserThreadState::SHUTDOWN; |
| + } |
| +} |
| + |
| // static |
| bool BrowserThreadImpl::PostTaskHelper( |
| BrowserThread::ID identifier, |
| @@ -391,22 +462,23 @@ bool BrowserThreadImpl::PostTaskHelper( |
| if (!target_thread_outlives_current) |
| globals.lock.Acquire(); |
| - base::MessageLoop* message_loop = |
| - globals.threads[identifier] ? globals.threads[identifier]->message_loop() |
| - : nullptr; |
| - if (message_loop) { |
| + 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) { |
| - message_loop->task_runner()->PostDelayedTask(from_here, task, delay); |
| + task_runner->PostDelayedTask(from_here, task, delay); |
| } else { |
| - message_loop->task_runner()->PostNonNestableDelayedTask(from_here, task, |
| - delay); |
| + task_runner->PostNonNestableDelayedTask(from_here, task, delay); |
| } |
| } |
| if (!target_thread_outlives_current) |
| globals.lock.Release(); |
| - return !!message_loop; |
| + return accepting_tasks; |
| } |
| // static |
| @@ -457,7 +529,8 @@ bool BrowserThread::IsThreadInitialized(ID identifier) { |
| base::AutoLock lock(globals.lock); |
| DCHECK_GE(identifier, 0); |
| DCHECK_LT(identifier, ID_COUNT); |
| - return globals.threads[identifier] != nullptr; |
| + return globals.states[identifier] == BrowserThreadState::INITIALIZED || |
| + globals.states[identifier] == BrowserThreadState::RUNNING; |
| } |
| // static |
| @@ -466,7 +539,8 @@ bool BrowserThread::CurrentlyOn(ID identifier) { |
| base::AutoLock lock(globals.lock); |
| DCHECK_GE(identifier, 0); |
| DCHECK_LT(identifier, ID_COUNT); |
| - return base::PlatformThread::CurrentId() == globals.thread_ids[identifier]; |
| + return globals.task_runners[identifier] && |
| + globals.task_runners[identifier]->RunsTasksOnCurrentThread(); |
| } |
| // static |
| @@ -492,8 +566,7 @@ bool BrowserThread::IsMessageLoopValid(ID identifier) { |
| base::AutoLock lock(globals.lock); |
| DCHECK_GE(identifier, 0); |
| DCHECK_LT(identifier, ID_COUNT); |
| - return globals.threads[identifier] && |
| - globals.threads[identifier]->message_loop(); |
| + return globals.states[identifier] == BrowserThreadState::RUNNING; |
| } |
| // static |
| @@ -547,7 +620,6 @@ bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier) { |
| if (g_globals == nullptr) |
| return false; |
| - base::MessageLoop* cur_message_loop = base::MessageLoop::current(); |
| 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 |
| @@ -555,9 +627,9 @@ bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier) { |
| tracked_objects::ScopedTracker tracking_profile(FROM_HERE); |
| base::AutoLock lock(globals.lock); |
| for (int i = 0; i < ID_COUNT; ++i) { |
| - if (globals.threads[i] && |
| - globals.threads[i]->message_loop() == cur_message_loop) { |
| - *identifier = globals.threads[i]->identifier_; |
| + if (globals.task_runners[i] && |
| + globals.task_runners[i]->RunsTasksOnCurrentThread()) { |
| + *identifier = static_cast<ID>(i); |
| return true; |
| } |
| } |
| @@ -571,6 +643,7 @@ BrowserThread::GetTaskRunnerForThread(ID identifier) { |
| return g_task_runners.Get().proxies[identifier]; |
| } |
| +// static |
| void BrowserThread::SetDelegate(ID identifier, |
|
fdoray
2016/12/01 15:15:22
I would change this to SetIOThreadDelegate() and c
gab
2016/12/07 19:15:27
Good call, will do in precursor CL.
gab
2016/12/08 18:49:41
Done in http://crrev.com/437271 :)
|
| BrowserThreadDelegate* delegate) { |
| using base::subtle::AtomicWord; |