Commit fa239c8c authored by Matt Falkenhagen's avatar Matt Falkenhagen Committed by Commit Bot

Revert "Refactor BrowserThreadImpl, BrowserProcessSubThread, and BrowserMainLoop"

This reverts commit d260e9cf.

Reason for revert: In Windows Canary versions since 67.0.3377.0, where this commit landed, IO thread hang reports have spiked dramatically. It is now the #1 browser crash report on Windows Canary at 33% of reports. I'm speculatively reverting this to see if the crash rate heals.

Original change's description:
> Refactor BrowserThreadImpl, BrowserProcessSubThread, and BrowserMainLoop
>
> This brings back the invariant that BrowserThread::IO isn't available
> before BrowserMainLoop::CreateThreads(). This was broken to fix issue
> 729596 to bring up the thread earlier for ServiceManager but it is
> important that code that posts to BrowserThread::IO statically have an
> happens-after relationship to BrowserMainLoop::CreateThreads(). Exposing
> it statically earlier put that invariant at risk.
>
> Thankfully fixing issue 815225 resulted in finally reaching the long
> sought goal of only having BrowserThread::UI/IO. Now that the IO thread
> is also kicked off before it's named statically, BrowserThreadImpl no
> longer needs to be a base::Thread, hence this refactoring.
>
> Before this CL:
>  * BrowserThreadImpl was a base::Thread
>    (could be a fake thread if SetMessageLoop was used)
>  * BrowserProcessSubThread was a BrowserThreadImpl
>    (performed a bit more initialization)
>  * BrowserProcessSubThread was only used in production (in
>    BrowserMainLoop)
>  * BrowserThreadImpl was used for fake threads (BrowserMainLoop for
>    BrowserThread::UI) and for testing (TestBrowserThread(Impl)).
>  * BrowserThreadImpl overrode Init/Run/CleanUp() from base::Thread to
>    perform some sanity checks as well as drive IOThread's Delegate (ref.
>    BrowserThread::SetIOThreadDelegate())
>  * BrowserProcessSubThread re-overrode Init/Run/CleanUp() to perform
>    per-thread //content initialization (tests missed out on that per
>    TestBrowserThread bypassing BrowserProcessSubThread by directly
>    subclassing BrowserThreadImpl).
>
> With this CL:
>  * BrowserThreadImpl is merely a scoped object that binds a provided
>    SingleThreadTaskRunner to a BrowserThread::ID.
>  * BrowserProcessSubThread is a base::Thread and performs all of the
>    initialization and cleanup specific to //content (this means it now
>    also manages BrowserThread::SetIOThreadDelegate())
>  * BrowserProcessSubThread can be brought up early before being bound to
>    a BrowserThread::ID (BrowserMainLoop handles that through
>    BrowserProcessSubThread ::RegisterAsBrowserThread())
>
> Unfortunate exceptions required for this CL:
>  * IOThread::Init() (invoked through BrowserThreadDelegate) perfoms
>    blocking operations this was previously performed before installed
>    the ThreadRestrictions on BrowserThread::IO. But now that //content
>    is initialized after bringing up the thread, a
>    base::ScopedAllowBlocking is required in scope of IOThread::Init().
>  * TestBrowserThread previously bypassing BrowserProcessSubThread by
>    directly subclassing BrowserThreadImpl meant it wasn't subject to
>    ThreadRestrictions (unfortunate becomes it denies allowance
>    verification to product code running in unit tests). Adding it back
>    causes DCHECKs, as such
>    BrowserProcessSubThread::AllowBlockingForTesting was added to allow
>    this CL to pass CQ.
>
> Of note:
>  * BrowserProcessSubThread is still written as though it supports many
>    BrowserThread::IDs but in practice it's mostly always
>    BrowserThread::IO (except in ThreadWatcherTest I think). This change
>    was big enough that I didn't bother also breaking that
>    generalization.
>  * BrowserThreadImpl's constructor was made private to ensure only
>    BrowserProcessSubThread and a few select callers get to drive it (to
>    avoid previous missed initialization issues)
>  * Atomics to manage BrowserThread::SetIOThreadDelegate were removed.
>    Restriction was instead added that this only be called before
>    initialization and after shutdown (this was already the case).
>
> Follow-ups to this CL:
>  * //ios duplicates this logic and will need to undergo the same change
>    as a follow-up
>  * Fixing ios will allow removal of base::Thread::SetMessageLoop hack :)
>  * Removing BrowserThreadGlobals::lock_ to address crbug.com/821034 will
>    be much easier
>  * BrowserThread post APIs should DCHECK rather than no-op if using a
>    BrowserThread::ID before it's registered.
>
> Bug: 815225, 821034, 729596
> Change-Id: If1038f23079df72203b1e95c7d26647f8824a726
> Reviewed-on: https://chromium-review.googlesource.com/969104
> Reviewed-by: John Abd-El-Malek <jam@chromium.org>
> Commit-Queue: Gabriel Charette <gab@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#544440}

TBR=gab@chromium.org,jam@chromium.org
NOPRESUBMIT=true

# Not skipping CQ checks because original CL landed > 1 day ago.
# falken: Skipping presubmit to use deprecated ThreadResrictions::DisallowWaiting().

Bug: 815225, 821034, 729596
Change-Id: I2be97c5d8183497c005ab397c871f625b034d850
Reviewed-on: https://chromium-review.googlesource.com/979752
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#545725}
parent fb48059c
...@@ -271,9 +271,6 @@ class BASE_EXPORT Thread : PlatformThread::Delegate { ...@@ -271,9 +271,6 @@ class BASE_EXPORT Thread : PlatformThread::Delegate {
static bool GetThreadWasQuitProperly(); static bool GetThreadWasQuitProperly();
// Bind this Thread to an existing MessageLoop instead of starting a new one. // Bind this Thread to an existing MessageLoop instead of starting a new one.
// TODO(gab): Remove this after ios/ has undergone the same surgery as
// BrowserThreadImpl (ref.
// https://chromium-review.googlesource.com/c/chromium/src/+/969104).
void SetMessageLoop(MessageLoop* message_loop); void SetMessageLoop(MessageLoop* message_loop);
bool using_external_message_loop() const { bool using_external_message_loop() const {
......
...@@ -38,7 +38,6 @@ namespace content { ...@@ -38,7 +38,6 @@ namespace content {
class BrowserGpuChannelHostFactory; class BrowserGpuChannelHostFactory;
class BrowserGpuMemoryBufferManager; class BrowserGpuMemoryBufferManager;
class BrowserMainLoop; class BrowserMainLoop;
class BrowserProcessSubThread;
class BrowserShutdownProfileDumper; class BrowserShutdownProfileDumper;
class BrowserSurfaceViewManager; class BrowserSurfaceViewManager;
class BrowserTestBase; class BrowserTestBase;
...@@ -212,7 +211,6 @@ class BASE_EXPORT ScopedAllowBlocking { ...@@ -212,7 +211,6 @@ class BASE_EXPORT ScopedAllowBlocking {
// in unit tests to avoid the friend requirement. // in unit tests to avoid the friend requirement.
FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest, ScopedAllowBlocking); FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest, ScopedAllowBlocking);
friend class android_webview::ScopedAllowInitGLBindings; friend class android_webview::ScopedAllowInitGLBindings;
friend class content::BrowserProcessSubThread;
friend class cronet::CronetPrefsManager; friend class cronet::CronetPrefsManager;
friend class cronet::CronetURLRequestContext; friend class cronet::CronetURLRequestContext;
friend class resource_coordinator::TabManagerDelegate; // crbug.com/778703 friend class resource_coordinator::TabManagerDelegate; // crbug.com/778703
......
...@@ -31,9 +31,10 @@ class BrowserProcessImplTest : public ::testing::Test { ...@@ -31,9 +31,10 @@ class BrowserProcessImplTest : public ::testing::Test {
: stashed_browser_process_(g_browser_process), : stashed_browser_process_(g_browser_process),
loop_(base::MessageLoop::TYPE_UI), loop_(base::MessageLoop::TYPE_UI),
ui_thread_(content::BrowserThread::UI, &loop_), ui_thread_(content::BrowserThread::UI, &loop_),
io_thread_(new content::TestBrowserThread(content::BrowserThread::IO)),
command_line_(base::CommandLine::NO_PROGRAM), command_line_(base::CommandLine::NO_PROGRAM),
browser_process_impl_( browser_process_impl_(std::make_unique<BrowserProcessImpl>(
new BrowserProcessImpl(base::ThreadTaskRunnerHandle::Get().get())) { base::ThreadTaskRunnerHandle::Get().get())) {
// Create() and StartWithDefaultParams() TaskScheduler in seperate steps to // Create() and StartWithDefaultParams() TaskScheduler in seperate steps to
// properly simulate the browser process' lifecycle. // properly simulate the browser process' lifecycle.
base::TaskScheduler::Create("BrowserProcessImplTest"); base::TaskScheduler::Create("BrowserProcessImplTest");
...@@ -48,20 +49,17 @@ class BrowserProcessImplTest : public ::testing::Test { ...@@ -48,20 +49,17 @@ class BrowserProcessImplTest : public ::testing::Test {
g_browser_process = stashed_browser_process_; g_browser_process = stashed_browser_process_;
} }
// Creates the IO thread (unbound) and task scheduler threads. The UI thread // Creates the secondary thread (IO thread).
// needs to be alive while BrowserProcessImpl is alive, and is managed // The UI thread needs to be alive while BrowserProcessImpl is alive, and is
// separately. // managed separately.
void StartSecondaryThreads() { void StartSecondaryThreads() {
base::TaskScheduler::GetInstance()->StartWithDefaultParams(); base::TaskScheduler::GetInstance()->StartWithDefaultParams();
io_thread_->StartIOThread();
io_thread_ = std::make_unique<content::TestBrowserThread>(
content::BrowserThread::IO);
io_thread_->StartIOThreadUnregistered();
} }
// Binds the IO thread to BrowserThread::IO and starts the ServiceManager. // Initializes the IO thread delegate and starts the ServiceManager.
void Initialize() { void Initialize() {
io_thread_->RegisterAsBrowserThread(); io_thread_->InitIOThreadDelegate();
// TestServiceManagerContext creation requires the task scheduler to be // TestServiceManagerContext creation requires the task scheduler to be
// started. // started.
......
...@@ -37,7 +37,6 @@ ...@@ -37,7 +37,6 @@
#include "base/synchronization/waitable_event.h" #include "base/synchronization/waitable_event.h"
#include "base/system_monitor/system_monitor.h" #include "base/system_monitor/system_monitor.h"
#include "base/task_scheduler/initialization_util.h" #include "base/task_scheduler/initialization_util.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h" #include "base/time/time.h"
...@@ -55,7 +54,6 @@ ...@@ -55,7 +54,6 @@
#include "components/viz/service/display_embedder/compositing_mode_reporter_impl.h" #include "components/viz/service/display_embedder/compositing_mode_reporter_impl.h"
#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h" #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h" #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "content/browser/browser_process_sub_thread.h"
#include "content/browser/browser_thread_impl.h" #include "content/browser/browser_thread_impl.h"
#include "content/browser/child_process_security_policy_impl.h" #include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/compositor/gpu_process_transport_factory.h" #include "content/browser/compositor/gpu_process_transport_factory.h"
...@@ -373,11 +371,10 @@ void OnStoppedStartupTracing(const base::FilePath& trace_file) { ...@@ -373,11 +371,10 @@ void OnStoppedStartupTracing(const base::FilePath& trace_file) {
MSVC_DISABLE_OPTIMIZE() MSVC_DISABLE_OPTIMIZE()
MSVC_PUSH_DISABLE_WARNING(4748) MSVC_PUSH_DISABLE_WARNING(4748)
NOINLINE void ResetThread_IO( NOINLINE void ResetThread_IO(std::unique_ptr<BrowserProcessSubThread> thread) {
std::unique_ptr<BrowserProcessSubThread> io_thread) { volatile int inhibit_comdat = __LINE__;
volatile int line_number = __LINE__; ALLOW_UNUSED_LOCAL(inhibit_comdat);
io_thread.reset(); thread.reset();
CHECK_GT(line_number, 0);
} }
MSVC_POP_WARNING() MSVC_POP_WARNING()
...@@ -1007,13 +1004,11 @@ int BrowserMainLoop::CreateThreads() { ...@@ -1007,13 +1004,11 @@ int BrowserMainLoop::CreateThreads() {
*task_scheduler_init_params.get()); *task_scheduler_init_params.get());
} }
// The thread used for BrowserThread::IO is created in // |io_thread_| is created by |PostMainMessageLoopStart()|, but its
// |PostMainMessageLoopStart()|, but it's only tagged as BrowserThread::IO // full initialization is deferred until this point because it requires
// here in order to prevent any code from statically posting to it before // several dependencies we don't want to depend on so early in startup.
// CreateThreads() (as such maintaining the invariant that PreCreateThreads()
// et al. "happen-before" BrowserThread::IO is "brought up").
DCHECK(io_thread_); DCHECK(io_thread_);
io_thread_->RegisterAsBrowserThread(); io_thread_->InitIOThreadDelegate();
created_threads_ = true; created_threads_ = true;
return result_code_; return result_code_;
...@@ -1251,12 +1246,9 @@ void BrowserMainLoop::InitializeMainThread() { ...@@ -1251,12 +1246,9 @@ void BrowserMainLoop::InitializeMainThread() {
TRACE_EVENT0("startup", "BrowserMainLoop::InitializeMainThread"); TRACE_EVENT0("startup", "BrowserMainLoop::InitializeMainThread");
base::PlatformThread::SetName("CrBrowserMain"); base::PlatformThread::SetName("CrBrowserMain");
// Register the main thread. The main thread's task runner should already have // Register the main thread by instantiating it, but don't call any methods.
// been initialized in MainMessageLoopStart() (or before if main_thread_.reset(
// MessageLoop::current() was externally provided). new BrowserThreadImpl(BrowserThread::UI, base::MessageLoop::current()));
DCHECK(base::ThreadTaskRunnerHandle::IsSet());
main_thread_.reset(new BrowserThreadImpl(
BrowserThread::UI, base::ThreadTaskRunnerHandle::Get()));
} }
int BrowserMainLoop::BrowserThreadsStarted() { int BrowserMainLoop::BrowserThreadsStarted() {
...@@ -1428,7 +1420,7 @@ int BrowserMainLoop::BrowserThreadsStarted() { ...@@ -1428,7 +1420,7 @@ int BrowserMainLoop::BrowserThreadsStarted() {
"startup", "startup",
"BrowserMainLoop::BrowserThreadsStarted::InitUserInputMonitor"); "BrowserMainLoop::BrowserThreadsStarted::InitUserInputMonitor");
user_input_monitor_ = media::UserInputMonitor::Create( user_input_monitor_ = media::UserInputMonitor::Create(
io_thread_->task_runner(), base::ThreadTaskRunnerHandle::Get()); io_thread_->task_runner(), main_thread_->task_runner());
} }
{ {
...@@ -1578,10 +1570,9 @@ void BrowserMainLoop::InitializeIOThread() { ...@@ -1578,10 +1570,9 @@ void BrowserMainLoop::InitializeIOThread() {
options.priority = base::ThreadPriority::DISPLAY; options.priority = base::ThreadPriority::DISPLAY;
#endif #endif
io_thread_ = std::make_unique<BrowserProcessSubThread>(BrowserThread::IO); io_thread_.reset(new BrowserProcessSubThread(BrowserThread::IO));
if (!io_thread_->StartWithOptions(options)) if (!io_thread_->StartWithOptions(options))
LOG(FATAL) << "Failed to start BrowserThread::IO"; LOG(FATAL) << "Failed to start the browser thread: IO";
} }
void BrowserMainLoop::InitializeMojo() { void BrowserMainLoop::InitializeMojo() {
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/browser/browser_process_sub_thread.h"
#include "content/public/browser/browser_main_runner.h" #include "content/public/browser/browser_main_runner.h"
#include "media/media_buildflags.h" #include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/binding_set.h" #include "mojo/public/cpp/bindings/binding_set.h"
...@@ -91,7 +92,6 @@ class HostFrameSinkManager; ...@@ -91,7 +92,6 @@ class HostFrameSinkManager;
namespace content { namespace content {
class BrowserMainParts; class BrowserMainParts;
class BrowserOnlineStateObserver; class BrowserOnlineStateObserver;
class BrowserProcessSubThread;
class BrowserThreadImpl; class BrowserThreadImpl;
class LoaderDelegateImpl; class LoaderDelegateImpl;
class MediaStreamManager; class MediaStreamManager;
...@@ -243,10 +243,7 @@ class CONTENT_EXPORT BrowserMainLoop { ...@@ -243,10 +243,7 @@ class CONTENT_EXPORT BrowserMainLoop {
void MainMessageLoopRun(); void MainMessageLoopRun();
// Initializes |io_thread_|. It will not be promoted to BrowserThread::IO
// until CreateThreads().
void InitializeIOThread(); void InitializeIOThread();
void InitializeMojo(); void InitializeMojo();
base::FilePath GetStartupTraceFileName( base::FilePath GetStartupTraceFileName(
const base::CommandLine& command_line) const; const base::CommandLine& command_line) const;
...@@ -288,7 +285,6 @@ class CONTENT_EXPORT BrowserMainLoop { ...@@ -288,7 +285,6 @@ class CONTENT_EXPORT BrowserMainLoop {
std::unique_ptr<base::MessageLoop> main_message_loop_; std::unique_ptr<base::MessageLoop> main_message_loop_;
// Members initialized in |PostMainMessageLoopStart()| ----------------------- // Members initialized in |PostMainMessageLoopStart()| -----------------------
std::unique_ptr<BrowserProcessSubThread> io_thread_;
std::unique_ptr<base::SystemMonitor> system_monitor_; std::unique_ptr<base::SystemMonitor> system_monitor_;
std::unique_ptr<base::PowerMonitor> power_monitor_; std::unique_ptr<base::PowerMonitor> power_monitor_;
std::unique_ptr<base::HighResolutionTimerManager> hi_res_timer_manager_; std::unique_ptr<base::HighResolutionTimerManager> hi_res_timer_manager_;
...@@ -339,6 +335,9 @@ class CONTENT_EXPORT BrowserMainLoop { ...@@ -339,6 +335,9 @@ class CONTENT_EXPORT BrowserMainLoop {
gpu_data_manager_visual_proxy_; gpu_data_manager_visual_proxy_;
#endif #endif
// Members initialized in |CreateThreads()| ----------------------------------
std::unique_ptr<BrowserProcessSubThread> io_thread_;
// Members initialized in |BrowserThreadsStarted()| -------------------------- // Members initialized in |BrowserThreadsStarted()| --------------------------
std::unique_ptr<ServiceManagerContext> service_manager_context_; std::unique_ptr<ServiceManagerContext> service_manager_context_;
std::unique_ptr<mojo::edk::ScopedIPCSupport> mojo_ipc_support_; std::unique_ptr<mojo::edk::ScopedIPCSupport> mojo_ipc_support_;
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include "base/sys_info.h" #include "base/sys_info.h"
#include "base/task_scheduler/task_scheduler.h" #include "base/task_scheduler/task_scheduler.h"
#include "base/test/scoped_command_line.h" #include "base/test/scoped_command_line.h"
#include "content/browser/browser_thread_impl.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/public/common/main_function_params.h" #include "content/public/common/main_function_params.h"
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/sampling_heap_profiler/sampling_heap_profiler.h" #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/synchronization/atomic_flag.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/trace_event/heap_profiler_allocation_context_tracker.h" #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
......
...@@ -4,165 +4,66 @@ ...@@ -4,165 +4,66 @@
#include "content/browser/browser_process_sub_thread.h" #include "content/browser/browser_process_sub_thread.h"
#include "base/compiler_specific.h" #include "base/debug/leak_tracker.h"
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "base/trace_event/memory_dump_manager.h" #include "base/trace_event/memory_dump_manager.h"
#include "build/build_config.h"
#include "content/browser/browser_child_process_host_impl.h" #include "content/browser/browser_child_process_host_impl.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/gpu/browser_gpu_memory_buffer_manager.h" #include "content/browser/gpu/browser_gpu_memory_buffer_manager.h"
#include "content/browser/notification_service_impl.h" #include "content/browser/notification_service_impl.h"
#include "content/public/browser/browser_thread_delegate.h"
#include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request.h" #include "net/url_request/url_request.h"
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#endif
#if defined(OS_WIN) #if defined(OS_WIN)
#include "base/win/scoped_com_initializer.h" #include "base/win/scoped_com_initializer.h"
#endif #endif
namespace content { namespace content {
namespace {
BrowserThreadDelegate* g_io_thread_delegate = nullptr;
} // namespace
// static
void BrowserThread::SetIOThreadDelegate(BrowserThreadDelegate* delegate) {
// |delegate| can only be set/unset while BrowserThread::IO isn't up.
DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO));
// and it cannot be set twice.
DCHECK(!g_io_thread_delegate || !delegate);
g_io_thread_delegate = delegate;
}
BrowserProcessSubThread::BrowserProcessSubThread(BrowserThread::ID identifier) BrowserProcessSubThread::BrowserProcessSubThread(BrowserThread::ID identifier)
: base::Thread(BrowserThreadImpl::GetThreadName(identifier)), : BrowserThreadImpl(identifier) {}
identifier_(identifier) {
// Not bound to creation thread. BrowserProcessSubThread::BrowserProcessSubThread(
DETACH_FROM_THREAD(browser_thread_checker_); BrowserThread::ID identifier,
} base::MessageLoop* message_loop)
: BrowserThreadImpl(identifier, message_loop) {}
BrowserProcessSubThread::~BrowserProcessSubThread() { BrowserProcessSubThread::~BrowserProcessSubThread() {
Stop(); Stop();
} }
void BrowserProcessSubThread::RegisterAsBrowserThread() {
DCHECK(IsRunning());
DCHECK(!browser_thread_);
browser_thread_.reset(new BrowserThreadImpl(identifier_, task_runner()));
// Unretained(this) is safe as |this| outlives its underlying thread.
task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&BrowserProcessSubThread::CompleteInitializationOnBrowserThread,
Unretained(this)));
}
void BrowserProcessSubThread::AllowBlockingForTesting() {
DCHECK(!IsRunning());
is_blocking_allowed_for_testing_ = true;
}
void BrowserProcessSubThread::Init() { void BrowserProcessSubThread::Init() {
DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);
#if defined(OS_WIN) #if defined(OS_WIN)
com_initializer_ = std::make_unique<base::win::ScopedCOMInitializer>(); com_initializer_.reset(new base::win::ScopedCOMInitializer());
#endif #endif
if (!is_blocking_allowed_for_testing_) { notification_service_.reset(new NotificationServiceImpl());
base::DisallowBlocking();
base::DisallowBaseSyncPrimitives();
}
}
void BrowserProcessSubThread::Run(base::RunLoop* run_loop) {
DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);
#if defined(OS_ANDROID) BrowserThreadImpl::Init();
// 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
switch (identifier_) { if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
case BrowserThread::UI: // Though this thread is called the "IO" thread, it actually just routes
// The main thread is usually promoted as the UI thread and doesn't go // messages around; it shouldn't be allowed to perform any blocking disk
// through Run() but some tests do run a separate UI thread. // I/O.
UIThreadRun(run_loop); base::ThreadRestrictions::SetIOAllowed(false);
break; base::ThreadRestrictions::DisallowWaiting();
case BrowserThread::IO:
IOThreadRun(run_loop);
return;
case BrowserThread::ID_COUNT:
NOTREACHED();
break;
} }
} }
void BrowserProcessSubThread::CleanUp() { void BrowserProcessSubThread::CleanUp() {
DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);
// Run extra cleanup if this thread represents BrowserThread::IO.
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) if (BrowserThread::CurrentlyOn(BrowserThread::IO))
IOThreadCleanUp(); IOThreadPreCleanUp();
if (identifier_ == BrowserThread::IO && g_io_thread_delegate) BrowserThreadImpl::CleanUp();
g_io_thread_delegate->CleanUp();
notification_service_.reset(); notification_service_.reset();
#if defined(OS_WIN) #if defined(OS_WIN)
com_initializer_.reset(); com_initializer_.reset();
#endif #endif
browser_thread_.reset();
} }
void BrowserProcessSubThread::CompleteInitializationOnBrowserThread() { void BrowserProcessSubThread::IOThreadPreCleanUp() {
DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);
notification_service_ = std::make_unique<NotificationServiceImpl>();
if (identifier_ == BrowserThread::IO && g_io_thread_delegate) {
// Allow blocking calls while initializing the IO thread.
base::ScopedAllowBlocking allow_blocking_for_init;
g_io_thread_delegate->Init();
}
}
// We disable optimizations for Run specifications so the compiler doesn't merge
// them all together.
MSVC_DISABLE_OPTIMIZE()
MSVC_PUSH_DISABLE_WARNING(4748)
void BrowserProcessSubThread::UIThreadRun(base::RunLoop* run_loop) {
volatile int line_number = __LINE__;
Thread::Run(run_loop);
CHECK_GT(line_number, 0);
}
void BrowserProcessSubThread::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 BrowserProcessSubThread::IOThreadCleanUp() {
DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);
// Kill all things that might be holding onto // Kill all things that might be holding onto
// net::URLRequest/net::URLRequestContexts. // net::URLRequest/net::URLRequestContexts.
......
...@@ -8,9 +8,8 @@ ...@@ -8,9 +8,8 @@
#include <memory> #include <memory>
#include "base/macros.h" #include "base/macros.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/browser/browser_thread_impl.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
...@@ -29,57 +28,29 @@ class NotificationService; ...@@ -29,57 +28,29 @@ class NotificationService;
namespace content { namespace content {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// A BrowserProcessSubThread is a physical thread backing a BrowserThread. // BrowserProcessSubThread
//
// This simple thread object is used for the specialized threads that the
// BrowserProcess spins up.
// //
// Applications must initialize the COM library before they can call // Applications must initialize the COM library before they can call
// COM library functions other than CoGetMalloc and memory allocation // COM library functions other than CoGetMalloc and memory allocation
// functions, so this class initializes COM for those users. // functions, so this class initializes COM for those users.
class CONTENT_EXPORT BrowserProcessSubThread : public base::Thread { class CONTENT_EXPORT BrowserProcessSubThread : public BrowserThreadImpl {
public: public:
// Constructs a BrowserProcessSubThread for |identifier|.
explicit BrowserProcessSubThread(BrowserThread::ID identifier); explicit BrowserProcessSubThread(BrowserThread::ID identifier);
BrowserProcessSubThread(BrowserThread::ID identifier,
base::MessageLoop* message_loop);
~BrowserProcessSubThread() override; ~BrowserProcessSubThread() override;
// Registers this thread to represent |identifier_| in the browser_thread.h
// API. This thread must already be running when this is called. This can only
// be called once per BrowserProcessSubThread instance.
void RegisterAsBrowserThread();
// Ideally there wouldn't be a special blanket allowance to block the
// BrowserThreads in tests but TestBrowserThreadImpl previously bypassed
// BrowserProcessSubThread and hence wasn't subject to ThreadRestrictions...
// Flipping that around in favor of explicit scoped allowances would be
// preferable but a non-trivial amount of work. Can only be called before
// starting this BrowserProcessSubThread.
void AllowBlockingForTesting();
protected: protected:
void Init() override; void Init() override;
void Run(base::RunLoop* run_loop) override;
void CleanUp() override; void CleanUp() override;
private: private:
// Second Init() phase that must happen on this thread but can only happen // These methods encapsulate cleanup that needs to happen on the IO thread
// after it's promoted to a BrowserThread in |RegisterAsBrowserThread()|. // before we call the embedder's CleanUp function.
void CompleteInitializationOnBrowserThread(); void IOThreadPreCleanUp();
// These methods merely forwards to Thread::Run() but are useful to identify
// which BrowserThread this represents in stack traces.
void UIThreadRun(base::RunLoop* run_loop);
void IOThreadRun(base::RunLoop* run_loop);
// This method encapsulates cleanup that needs to happen on the IO thread.
void IOThreadCleanUp();
const BrowserThread::ID identifier_;
// BrowserThreads are not allowed to do file I/O nor wait on synchronization
// primivives except when explicitly allowed in tests.
bool is_blocking_allowed_for_testing_ = false;
// The BrowserThread registration for this |identifier_|, initialized in
// RegisterAsBrowserThread().
std::unique_ptr<BrowserThreadImpl> browser_thread_;
#if defined (OS_WIN) #if defined (OS_WIN)
std::unique_ptr<base::win::ScopedCOMInitializer> com_initializer_; std::unique_ptr<base::win::ScopedCOMInitializer> com_initializer_;
...@@ -88,8 +59,6 @@ class CONTENT_EXPORT BrowserProcessSubThread : public base::Thread { ...@@ -88,8 +59,6 @@ class CONTENT_EXPORT BrowserProcessSubThread : public base::Thread {
// Each specialized thread has its own notification service. // Each specialized thread has its own notification service.
std::unique_ptr<NotificationService> notification_service_; std::unique_ptr<NotificationService> notification_service_;
THREAD_CHECKER(browser_thread_checker_);
DISALLOW_COPY_AND_ASSIGN(BrowserProcessSubThread); DISALLOW_COPY_AND_ASSIGN(BrowserProcessSubThread);
}; };
......
...@@ -7,21 +7,41 @@ ...@@ -7,21 +7,41 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "base/atomicops.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/platform_thread.h" #include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/public/browser/browser_thread_delegate.h"
#include "content/public/browser/content_browser_client.h" #include "content/public/browser/content_browser_client.h"
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#endif
namespace content { namespace content {
namespace { 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 // An implementation of SingleThreadTaskRunner to be used in conjunction
// with BrowserThread. // with BrowserThread.
// TODO(gab): Consider replacing this with |g_globals->task_runners| -- only // TODO(gab): Consider replacing this with |g_globals->task_runners| -- only
...@@ -79,12 +99,17 @@ base::LazyInstance<BrowserThreadTaskRunners>::Leaky g_task_runners = ...@@ -79,12 +99,17 @@ base::LazyInstance<BrowserThreadTaskRunners>::Leaky g_task_runners =
enum BrowserThreadState { enum BrowserThreadState {
// BrowserThread::ID isn't associated with anything yet. // BrowserThread::ID isn't associated with anything yet.
UNINITIALIZED = 0, 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. // BrowserThread::ID is associated to a TaskRunner and is accepting tasks.
RUNNING, RUNNING,
// BrowserThread::ID no longer accepts tasks. // BrowserThread::ID no longer accepts tasks.
SHUTDOWN SHUTDOWN
}; };
using BrowserThreadDelegateAtomicPtr = base::subtle::AtomicWord;
struct BrowserThreadGlobals { struct BrowserThreadGlobals {
// This lock protects |task_runners| and |states|. 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. // arrays without holding this lock. Do not block while holding this lock.
...@@ -99,18 +124,252 @@ struct BrowserThreadGlobals { ...@@ -99,18 +124,252 @@ struct BrowserThreadGlobals {
// Holds the state of each BrowserThread::ID. // Holds the state of each BrowserThread::ID.
BrowserThreadState states[BrowserThread::ID_COUNT] = {}; 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 base::LazyInstance<BrowserThreadGlobals>::Leaky
g_globals = LAZY_INSTANCE_INITIALIZER; g_globals = LAZY_INSTANCE_INITIALIZER;
bool PostTaskHelper(BrowserThread::ID identifier, 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, const base::Location& from_here,
base::OnceClosure task, base::OnceClosure task,
base::TimeDelta delay, base::TimeDelta delay,
bool nestable) { bool nestable) {
DCHECK_GE(identifier, 0); DCHECK_GE(identifier, 0);
DCHECK_LT(identifier, BrowserThread::ID_COUNT); DCHECK_LT(identifier, ID_COUNT);
// Optimization: to avoid unnecessary locks, we listed the ID enumeration in // 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 // 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 // outlives current thread as that implies the current thread only ever sees
...@@ -118,9 +377,9 @@ bool PostTaskHelper(BrowserThread::ID identifier, ...@@ -118,9 +377,9 @@ bool PostTaskHelper(BrowserThread::ID identifier,
// Note: since the array is so small, ok to loop instead of creating a map, // 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 // which would require a lock because std::map isn't thread safe, defeating
// the whole purpose of this optimization. // the whole purpose of this optimization.
BrowserThread::ID current_thread = BrowserThread::ID_COUNT; BrowserThread::ID current_thread = ID_COUNT;
bool target_thread_outlives_current = bool target_thread_outlives_current =
BrowserThreadImpl::GetCurrentThreadIdentifier(&current_thread) && GetCurrentThreadIdentifier(&current_thread) &&
current_thread >= identifier; current_thread >= identifier;
BrowserThreadGlobals& globals = g_globals.Get(); BrowserThreadGlobals& globals = g_globals.Get();
...@@ -153,56 +412,6 @@ bool PostTaskHelper(BrowserThread::ID identifier, ...@@ -153,56 +412,6 @@ bool PostTaskHelper(BrowserThread::ID identifier,
return accepting_tasks; return accepting_tasks;
} }
} // namespace
BrowserThreadImpl::BrowserThreadImpl(
ID identifier,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: identifier_(identifier) {
DCHECK(task_runner);
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::RUNNING;
globals.task_runners[identifier_] = std::move(task_runner);
}
BrowserThreadImpl::~BrowserThreadImpl() {
BrowserThreadGlobals& globals = g_globals.Get();
base::AutoLock lock(globals.lock);
DCHECK_EQ(globals.states[identifier_], BrowserThreadState::RUNNING);
globals.states[identifier_] = BrowserThreadState::SHUTDOWN;
}
// 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;
}
// static
const char* BrowserThreadImpl::GetThreadName(BrowserThread::ID thread) {
static const char* const kBrowserThreadNames[BrowserThread::ID_COUNT] = {
"", // UI (name assembled in browser_main_loop.cc).
"Chrome_IOThread", // IO
};
if (BrowserThread::UI < thread && thread < BrowserThread::ID_COUNT)
return kBrowserThreadNames[thread];
if (thread == BrowserThread::UI)
return "Chrome_UIThread";
return "Unknown Thread";
}
// static // static
void BrowserThread::PostAfterStartupTask( void BrowserThread::PostAfterStartupTask(
const base::Location& from_here, const base::Location& from_here,
...@@ -221,7 +430,13 @@ bool BrowserThread::IsThreadInitialized(ID identifier) { ...@@ -221,7 +430,13 @@ bool BrowserThread::IsThreadInitialized(ID identifier) {
base::AutoLock lock(globals.lock); base::AutoLock lock(globals.lock);
DCHECK_GE(identifier, 0); DCHECK_GE(identifier, 0);
DCHECK_LT(identifier, ID_COUNT); DCHECK_LT(identifier, ID_COUNT);
return globals.states[identifier] == BrowserThreadState::RUNNING; bool running =
globals.states[identifier] == BrowserThreadState::INITIALIZED ||
globals.states[identifier] == BrowserThreadState::RUNNING;
if (identifier != BrowserThread::IO)
return running;
return running && IsIOThreadInitialized();
} }
// static // static
...@@ -241,7 +456,7 @@ std::string BrowserThread::GetDCheckCurrentlyOnErrorMessage(ID expected) { ...@@ -241,7 +456,7 @@ std::string BrowserThread::GetDCheckCurrentlyOnErrorMessage(ID expected) {
actual_name = "Unknown Thread"; actual_name = "Unknown Thread";
std::string result = "Must be called on "; std::string result = "Must be called on ";
result += BrowserThreadImpl::GetThreadName(expected); result += GetThreadName(expected);
result += "; actually called on "; result += "; actually called on ";
result += actual_name; result += actual_name;
result += "."; result += ".";
...@@ -264,8 +479,8 @@ bool BrowserThread::IsMessageLoopValid(ID identifier) { ...@@ -264,8 +479,8 @@ bool BrowserThread::IsMessageLoopValid(ID identifier) {
bool BrowserThread::PostTask(ID identifier, bool BrowserThread::PostTask(ID identifier,
const base::Location& from_here, const base::Location& from_here,
base::OnceClosure task) { base::OnceClosure task) {
return PostTaskHelper(identifier, from_here, std::move(task), return BrowserThreadImpl::PostTaskHelper(
base::TimeDelta(), true); identifier, from_here, std::move(task), base::TimeDelta(), true);
} }
// static // static
...@@ -273,15 +488,16 @@ bool BrowserThread::PostDelayedTask(ID identifier, ...@@ -273,15 +488,16 @@ bool BrowserThread::PostDelayedTask(ID identifier,
const base::Location& from_here, const base::Location& from_here,
base::OnceClosure task, base::OnceClosure task,
base::TimeDelta delay) { base::TimeDelta delay) {
return PostTaskHelper(identifier, from_here, std::move(task), delay, true); return BrowserThreadImpl::PostTaskHelper(identifier, from_here,
std::move(task), delay, true);
} }
// static // static
bool BrowserThread::PostNonNestableTask(ID identifier, bool BrowserThread::PostNonNestableTask(ID identifier,
const base::Location& from_here, const base::Location& from_here,
base::OnceClosure task) { base::OnceClosure task) {
return PostTaskHelper(identifier, from_here, std::move(task), return BrowserThreadImpl::PostTaskHelper(
base::TimeDelta(), false); identifier, from_here, std::move(task), base::TimeDelta(), false);
} }
// static // static
...@@ -289,7 +505,8 @@ bool BrowserThread::PostNonNestableDelayedTask(ID identifier, ...@@ -289,7 +505,8 @@ bool BrowserThread::PostNonNestableDelayedTask(ID identifier,
const base::Location& from_here, const base::Location& from_here,
base::OnceClosure task, base::OnceClosure task,
base::TimeDelta delay) { base::TimeDelta delay) {
return PostTaskHelper(identifier, from_here, std::move(task), delay, false); return BrowserThreadImpl::PostTaskHelper(identifier, from_here,
std::move(task), delay, false);
} }
// static // static
...@@ -328,4 +545,16 @@ BrowserThread::GetTaskRunnerForThread(ID identifier) { ...@@ -328,4 +545,16 @@ BrowserThread::GetTaskRunnerForThread(ID identifier) {
return g_task_runners.Get().proxies[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 } // namespace content
...@@ -5,31 +5,47 @@ ...@@ -5,31 +5,47 @@
#ifndef CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_ #ifndef CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_
#define CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_ #define CONTENT_BROWSER_BROWSER_THREAD_IMPL_H_
#include "base/memory/scoped_refptr.h" #include "base/callback.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
namespace content { namespace base {
class MessageLoop;
class RunLoop;
}
namespace base {
class Location;
}
class BrowserMainLoop; namespace content {
class BrowserProcessSubThread;
class TestBrowserThread;
// BrowserThreadImpl is a scoped object which maps a SingleThreadTaskRunner to a
// BrowserThread::ID. On ~BrowserThreadImpl() that ID enters a SHUTDOWN state
// (in which BrowserThread::IsThreadInitialized() returns false) but the mapping
// isn't undone to avoid shutdown races (the task runner is free to stop
// accepting tasks however).
//
// Very few users should use this directly. To mock BrowserThreads, tests should // Very few users should use this directly. To mock BrowserThreads, tests should
// use TestBrowserThreadBundle instead. // use TestBrowserThreadBundle instead.
class CONTENT_EXPORT BrowserThreadImpl : public BrowserThread { class CONTENT_EXPORT BrowserThreadImpl : public BrowserThread,
public base::Thread {
public: public:
~BrowserThreadImpl(); // Construct a BrowserThreadImpl with the supplied identifier. It is an error
// to construct a BrowserThreadImpl that already exists.
explicit BrowserThreadImpl(BrowserThread::ID identifier);
// Returns the thread name for |identifier|. // Special constructor for the main (UI) thread and unittests. If a
static const char* GetThreadName(BrowserThread::ID identifier); // |message_loop| is provied, we use a dummy thread here since the main
// thread already exists.
BrowserThreadImpl(BrowserThread::ID identifier,
base::MessageLoop* message_loop);
~BrowserThreadImpl() override;
bool Start();
bool StartWithOptions(const Options& options);
bool StartAndWaitForTesting();
// Called only by the BrowserThread::IO thread to initialize its
// BrowserThreadDelegate after the thread is created. See
// https://crbug.com/729596.
void InitIOThreadDelegate();
// Resets globals for |identifier|. Used in tests to clear global state that // Resets globals for |identifier|. Used in tests to clear global state that
// would otherwise leak to the next test. Globals are not otherwise fully // would otherwise leak to the next test. Globals are not otherwise fully
...@@ -39,19 +55,35 @@ class CONTENT_EXPORT BrowserThreadImpl : public BrowserThread { ...@@ -39,19 +55,35 @@ class CONTENT_EXPORT BrowserThreadImpl : public BrowserThread {
// |identifier|. // |identifier|.
static void ResetGlobalsForTesting(BrowserThread::ID identifier); static void ResetGlobalsForTesting(BrowserThread::ID identifier);
protected:
void Init() override;
void Run(base::RunLoop* run_loop) override;
void CleanUp() override;
private: private:
// Restrict instantiation to BrowserProcessSubThread as it performs important // We implement all the functionality of the public BrowserThread
// initialization that shouldn't be bypassed (except by BrowserMainLoop for // functions, but state is stored in the BrowserThreadImpl to keep
// the main thread). // the API cleaner. Therefore make BrowserThread a friend class.
friend class BrowserProcessSubThread; friend class BrowserThread;
friend class BrowserMainLoop;
// TestBrowserThread is also allowed to construct this when instantiating fake // The following are unique function names that makes it possible to tell
// threads. // the thread id from the callstack alone in crash dumps.
friend class TestBrowserThread; void UIThreadRun(base::RunLoop* run_loop);
void ProcessLauncherThreadRun(base::RunLoop* run_loop);
// Binds |identifier| to |task_runner| for the browser_thread.h API. void IOThreadRun(base::RunLoop* run_loop);
BrowserThreadImpl(BrowserThread::ID identifier,
scoped_refptr<base::SingleThreadTaskRunner> task_runner); static bool PostTaskHelper(BrowserThread::ID identifier,
const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay,
bool nestable);
// Common initialization code for the constructors.
void Initialize();
// For testing.
friend class ContentTestSuiteBaseListener;
friend class TestBrowserThreadBundle;
// The identifier of this thread. Only one thread can exist with a given // The identifier of this thread. Only one thread can exist with a given
// identifier at a given time. // identifier at a given time.
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include "base/sequenced_task_runner_helpers.h" #include "base/sequenced_task_runner_helpers.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "content/browser/browser_process_sub_thread.h"
#include "content/browser/browser_thread_impl.h" #include "content/browser/browser_thread_impl.h"
#include "content/public/test/test_browser_thread.h" #include "content/public/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -32,22 +31,17 @@ class BrowserThreadTest : public testing::Test { ...@@ -32,22 +31,17 @@ class BrowserThreadTest : public testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
ui_thread_ = std::make_unique<BrowserProcessSubThread>(BrowserThread::UI); ui_thread_.reset(new BrowserThreadImpl(BrowserThread::UI));
io_thread_.reset(new BrowserThreadImpl(BrowserThread::IO));
ui_thread_->Start(); ui_thread_->Start();
io_thread_->Start();
io_thread_ = std::make_unique<BrowserProcessSubThread>(BrowserThread::IO);
base::Thread::Options io_options;
io_options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_->StartWithOptions(io_options);
ui_thread_->RegisterAsBrowserThread();
io_thread_->RegisterAsBrowserThread();
} }
void TearDown() override { void TearDown() override {
io_thread_.reset(); StopUIThread();
ui_thread_.reset(); io_thread_->Stop();
ui_thread_ = nullptr;
io_thread_ = nullptr;
BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::UI); BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::UI);
BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::IO); BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::IO);
} }
...@@ -79,8 +73,8 @@ class BrowserThreadTest : public testing::Test { ...@@ -79,8 +73,8 @@ class BrowserThreadTest : public testing::Test {
}; };
private: private:
std::unique_ptr<BrowserProcessSubThread> ui_thread_; std::unique_ptr<BrowserThreadImpl> ui_thread_;
std::unique_ptr<BrowserProcessSubThread> io_thread_; std::unique_ptr<BrowserThreadImpl> io_thread_;
// It's kind of ugly to make this mutable - solely so we can post the Quit // It's kind of ugly to make this mutable - solely so we can post the Quit
// Task from Release(). This should be fixed. // Task from Release(). This should be fixed.
mutable base::MessageLoop loop_; mutable base::MessageLoop loop_;
......
...@@ -172,11 +172,9 @@ class CONTENT_EXPORT BrowserThread { ...@@ -172,11 +172,9 @@ class CONTENT_EXPORT BrowserThread {
// thread. To DCHECK this, use the DCHECK_CURRENTLY_ON() macro above. // thread. To DCHECK this, use the DCHECK_CURRENTLY_ON() macro above.
static bool CurrentlyOn(ID identifier) WARN_UNUSED_RESULT; static bool CurrentlyOn(ID identifier) WARN_UNUSED_RESULT;
// Deprecated: This is equivalent to IsThreadInitialized().
// Callable on any thread. Returns whether the threads message loop is valid. // Callable on any thread. Returns whether the threads message loop is valid.
// If this returns false it means the thread is in the process of shutting // If this returns false it means the thread is in the process of shutting
// down. // down.
// TODO(gab): Replace callers with IsThreadInitialized().
static bool IsMessageLoopValid(ID identifier) WARN_UNUSED_RESULT; static bool IsMessageLoopValid(ID identifier) WARN_UNUSED_RESULT;
// If the current message loop is one of the known threads, returns true and // If the current message loop is one of the known threads, returns true and
...@@ -190,12 +188,16 @@ class CONTENT_EXPORT BrowserThread { ...@@ -190,12 +188,16 @@ class CONTENT_EXPORT BrowserThread {
// Sets the delegate for BrowserThread::IO. // Sets the delegate for BrowserThread::IO.
// //
// This only supports the IO thread as it doesn't work for potentially
// redirected threads (ref. http://crbug.com/653916) and also doesn't make
// sense for the UI thread.
//
// Only one delegate may be registered at a time. The delegate may be // Only one delegate may be registered at a time. The delegate may be
// unregistered by providing a nullptr pointer. // unregistered by providing a nullptr pointer.
// //
// The delegate can only be registered through this call before // If the caller unregisters the delegate before CleanUp has been called, it
// BrowserThreadImpl(BrowserThread::IO) is created and unregistered after // must perform its own locking to ensure the delegate is not deleted while
// it was destroyed and its underlying thread shutdown. // unregistering.
static void SetIOThreadDelegate(BrowserThreadDelegate* delegate); static void SetIOThreadDelegate(BrowserThreadDelegate* delegate);
// Use these templates in conjunction with RefCountedThreadSafe or scoped_ptr // Use these templates in conjunction with RefCountedThreadSafe or scoped_ptr
......
...@@ -5,20 +5,22 @@ ...@@ -5,20 +5,22 @@
#ifndef CONTENT_PUBLIC_BROWSER_BROWSER_THREAD_DELEGATE_H_ #ifndef CONTENT_PUBLIC_BROWSER_BROWSER_THREAD_DELEGATE_H_
#define CONTENT_PUBLIC_BROWSER_BROWSER_THREAD_DELEGATE_H_ #define CONTENT_PUBLIC_BROWSER_BROWSER_THREAD_DELEGATE_H_
#include "content/common/content_export.h"
namespace content { namespace content {
// A Delegate for content embedders to perform extra initialization/cleanup on // BrowserThread::SetDelegate was deprecated, this is now only used by
// BrowserThread::IO. // BrowserThread::SetIOThreadDelegate.
//
// When registered as such, it will schedule to run Init() before the message
// loop begins and receive a CleanUp call right after the message loop ends (and
// before the BrowserThread has done its own clean-up).
class BrowserThreadDelegate { class BrowserThreadDelegate {
public: public:
virtual ~BrowserThreadDelegate() = default; virtual ~BrowserThreadDelegate() = default;
// Called prior to completing initialization of BrowserThread::IO. // Called prior to starting the message loop
virtual void Init() = 0; virtual void Init() = 0;
// Called during teardown of BrowserThread::IO. // Called just after the message loop ends.
virtual void CleanUp() = 0; virtual void CleanUp() = 0;
}; };
......
...@@ -9,32 +9,71 @@ ...@@ -9,32 +9,71 @@
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "content/browser/browser_process_sub_thread.h"
#include "content/browser/browser_thread_impl.h" #include "content/browser/browser_thread_impl.h"
#include "content/browser/notification_service_impl.h"
#if defined(OS_WIN)
#include "base/win/scoped_com_initializer.h"
#endif
namespace content { namespace content {
class TestBrowserThreadImpl : public BrowserThreadImpl {
public:
explicit TestBrowserThreadImpl(BrowserThread::ID identifier)
: BrowserThreadImpl(identifier) {}
TestBrowserThreadImpl(BrowserThread::ID identifier,
base::MessageLoop* message_loop)
: BrowserThreadImpl(identifier, message_loop) {}
~TestBrowserThreadImpl() override { Stop(); }
void Init() override {
#if defined(OS_WIN)
com_initializer_ = std::make_unique<base::win::ScopedCOMInitializer>();
#endif
notification_service_ = std::make_unique<NotificationServiceImpl>();
BrowserThreadImpl::Init();
}
void CleanUp() override {
BrowserThreadImpl::CleanUp();
notification_service_.reset();
#if defined(OS_WIN)
com_initializer_.reset();
#endif
}
private:
#if defined(OS_WIN)
std::unique_ptr<base::win::ScopedCOMInitializer> com_initializer_;
#endif
std::unique_ptr<NotificationService> notification_service_;
DISALLOW_COPY_AND_ASSIGN(TestBrowserThreadImpl);
};
TestBrowserThread::TestBrowserThread(BrowserThread::ID identifier) TestBrowserThread::TestBrowserThread(BrowserThread::ID identifier)
: identifier_(identifier), : impl_(new TestBrowserThreadImpl(identifier)), identifier_(identifier) {}
real_thread_(std::make_unique<BrowserProcessSubThread>(identifier_)) {
real_thread_->AllowBlockingForTesting();
}
TestBrowserThread::TestBrowserThread(BrowserThread::ID identifier, TestBrowserThread::TestBrowserThread(BrowserThread::ID identifier,
base::MessageLoop* message_loop) base::MessageLoop* message_loop)
: identifier_(identifier), : impl_(new TestBrowserThreadImpl(identifier, message_loop)),
fake_thread_( identifier_(identifier) {}
new BrowserThreadImpl(identifier_, message_loop->task_runner())) {}
TestBrowserThread::~TestBrowserThread() { TestBrowserThread::~TestBrowserThread() {
// The upcoming BrowserThreadImpl::ResetGlobalsForTesting() call requires that // The upcoming BrowserThreadImpl::ResetGlobalsForTesting() call requires that
// |identifier_| have completed its SHUTDOWN phase. // |impl_| have triggered the shutdown phase for its BrowserThread::ID. This
real_thread_.reset(); // either happens when the thread is stopped (if real) or destroyed (when fake
fake_thread_.reset(); // -- i.e. using an externally provided MessageLoop).
impl_.reset();
// Resets BrowserThreadImpl's globals so that |identifier_| is no longer // Resets BrowserThreadImpl's globals so that |impl_| is no longer bound to
// bound. This is fine since the underlying MessageLoop has already been // |identifier_|. This is fine since the underlying MessageLoop has already
// flushed and deleted above. In the case of an externally provided // been flushed and deleted in Stop(). In the case of an externally provided
// MessageLoop however, this means that TaskRunners obtained through // MessageLoop however, this means that TaskRunners obtained through
// |BrowserThreadImpl::GetTaskRunnerForThread(identifier_)| will no longer // |BrowserThreadImpl::GetTaskRunnerForThread(identifier_)| will no longer
// recognize their BrowserThreadImpl for RunsTasksInCurrentSequence(). This // recognize their BrowserThreadImpl for RunsTasksInCurrentSequence(). This
...@@ -48,34 +87,30 @@ TestBrowserThread::~TestBrowserThread() { ...@@ -48,34 +87,30 @@ TestBrowserThread::~TestBrowserThread() {
BrowserThreadImpl::ResetGlobalsForTesting(identifier_); BrowserThreadImpl::ResetGlobalsForTesting(identifier_);
} }
void TestBrowserThread::Start() { bool TestBrowserThread::Start() {
CHECK(real_thread_->Start()); return impl_->Start();
RegisterAsBrowserThread();
} }
void TestBrowserThread::StartAndWaitForTesting() { bool TestBrowserThread::StartAndWaitForTesting() {
CHECK(real_thread_->StartAndWaitForTesting()); return impl_->StartAndWaitForTesting();
RegisterAsBrowserThread();
} }
void TestBrowserThread::StartIOThread() { bool TestBrowserThread::StartIOThread() {
StartIOThreadUnregistered();
RegisterAsBrowserThread();
}
void TestBrowserThread::StartIOThreadUnregistered() {
base::Thread::Options options; base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO; options.message_loop_type = base::MessageLoop::TYPE_IO;
CHECK(real_thread_->StartWithOptions(options)); return impl_->StartWithOptions(options);
} }
void TestBrowserThread::RegisterAsBrowserThread() { void TestBrowserThread::InitIOThreadDelegate() {
real_thread_->RegisterAsBrowserThread(); impl_->InitIOThreadDelegate();
} }
void TestBrowserThread::Stop() { void TestBrowserThread::Stop() {
if (real_thread_) impl_->Stop();
real_thread_->Stop(); }
bool TestBrowserThread::IsRunning() {
return impl_->IsRunning();
} }
} // namespace content } // namespace content
...@@ -12,27 +12,20 @@ ...@@ -12,27 +12,20 @@
namespace base { namespace base {
class MessageLoop; class MessageLoop;
class Thread;
} }
namespace content { namespace content {
class BrowserProcessSubThread; class TestBrowserThreadImpl;
class BrowserThreadImpl;
// DEPRECATED: use TestBrowserThreadBundle instead. See http://crbug.com/272091 // DEPRECATED: use TestBrowserThreadBundle instead. See http://crbug.com/272091
// A BrowserThread for unit tests; this lets unit tests in chrome/ create // A BrowserThread for unit tests; this lets unit tests in chrome/ create
// BrowserThread instances. // BrowserThread instances.
class TestBrowserThread { class TestBrowserThread {
public: public:
// Constructs a TestBrowserThread with a |real_thread_| and starts it (with a
// MessageLoopForIO if |identifier == BrowserThread::IO|.
explicit TestBrowserThread(BrowserThread::ID identifier); explicit TestBrowserThread(BrowserThread::ID identifier);
// Constructs a TestBrowserThread based on |message_loop| (no |real_thread_|).
TestBrowserThread(BrowserThread::ID identifier, TestBrowserThread(BrowserThread::ID identifier,
base::MessageLoop* message_loop); base::MessageLoop* message_loop);
~TestBrowserThread(); ~TestBrowserThread();
// We provide a subset of the capabilities of the Thread interface // We provide a subset of the capabilities of the Thread interface
...@@ -41,34 +34,28 @@ class TestBrowserThread { ...@@ -41,34 +34,28 @@ class TestBrowserThread {
// interface. // interface.
// Starts the thread with a generic message loop. // Starts the thread with a generic message loop.
void Start(); bool Start();
// Starts the thread with a generic message loop and waits for the // Starts the thread with a generic message loop and waits for the
// thread to run. // thread to run.
void StartAndWaitForTesting(); bool StartAndWaitForTesting();
// Starts the thread with an IOThread message loop. // Starts the thread with an IOThread message loop.
void StartIOThread(); bool StartIOThread();
// Together these are the same as StartIOThread(). They can be called in // Initializes the BrowserThreadDelegate.
// phases to test binding BrowserThread::IO after its underlying thread was void InitIOThreadDelegate();
// started.
void StartIOThreadUnregistered();
void RegisterAsBrowserThread();
// Stops the thread, no-op if this is not a real thread. // Stops the thread.
void Stop(); void Stop();
private: // Returns true if the thread is running.
const BrowserThread::ID identifier_; bool IsRunning();
// A real thread which represents |identifier_| when constructor #1 is used private:
// (null otherwise). std::unique_ptr<TestBrowserThreadImpl> impl_;
std::unique_ptr<BrowserProcessSubThread> real_thread_;
// Binds |identifier_| to |message_loop| when constructor #2 is used (null const BrowserThread::ID identifier_;
// otherwise).
std::unique_ptr<BrowserThreadImpl> fake_thread_;
DISALLOW_COPY_AND_ASSIGN(TestBrowserThread); DISALLOW_COPY_AND_ASSIGN(TestBrowserThread);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment