Index: content/browser/browser_thread_impl.cc |
diff --git a/content/browser/browser_thread_impl.cc b/content/browser/browser_thread_impl.cc |
index 72345852c0f5e7e64f5c3d501839dda16748c5e5..459fc3cbc4dc939a6d887cedfc73dee28d3e45d0 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/synchronization/waitable_event.h" |
#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,6 +101,20 @@ 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 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 { |
@@ -106,24 +122,21 @@ struct 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])); |
- } |
+ 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. |
+ BrowserThreadState states[BrowserThread::ID_COUNT] = {}; |
// Only atomic operations are used on this pointer. The delegate isn't owned |
// by BrowserThreadGlobals, rather by whoever calls |
@@ -141,13 +154,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, |
@@ -156,11 +162,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 |
@@ -186,14 +197,27 @@ void BrowserThreadImpl::FlushThreadPoolHelperForTesting() { |
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_]->RunsTasksOnCurrentThread()); |
+ } |
+#endif // DCHECK_IS_ON() |
+ |
if (identifier_ == BrowserThread::DB || |
identifier_ == BrowserThread::FILE || |
identifier_ == BrowserThread::FILE_USER_BLOCKING || |
identifier_ == BrowserThread::PROCESS_LAUNCHER || |
identifier_ == 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(); |
} |
if (identifier_ == BrowserThread::IO) { |
@@ -305,12 +329,13 @@ void BrowserThreadImpl::CleanUp() { |
reinterpret_cast<BrowserThreadDelegate*>(delegate)->CleanUp(); |
} |
- // PostTaskHelper() accesses the message loop while holding this lock. |
- // However, the message loop will soon be destructed without any locking. So |
- // to prevent a race with accessing the message loop in PostTaskHelper(), |
- // remove this thread from the global array now. |
+ // 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); |
- globals.threads[identifier_] = nullptr; |
+ DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING); |
+ globals.states[identifier_] = BrowserThreadState::SHUTDOWN; |
} |
void BrowserThreadImpl::Initialize() { |
@@ -319,8 +344,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; |
+} |
+ |
+// 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; |
+ SetIOThreadDelegate(nullptr); |
} |
BrowserThreadImpl::~BrowserThreadImpl() { |
@@ -331,12 +367,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 |
} |
@@ -346,14 +392,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). |
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); |
- DCHECK_EQ(globals.thread_ids[identifier_], base::kInvalidThreadId); |
+ |
bool result = Thread::StartWithOptions(options); |
- globals.thread_ids[identifier_] = GetThreadId(); |
+ |
+ // 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; |
} |
@@ -363,6 +419,70 @@ bool BrowserThreadImpl::StartAndWaitForTesting() { |
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::Bind(&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, |
@@ -374,7 +494,8 @@ bool BrowserThreadImpl::PostTaskHelper( |
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. |
+ // 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. |
@@ -387,22 +508,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 |
@@ -453,7 +575,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 |
@@ -462,7 +585,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 |
@@ -488,8 +612,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 |
@@ -543,7 +666,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 |
@@ -551,9 +673,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; |
} |
} |