// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/gpu/gpu_child_thread.h" #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/threading/worker_pool.h" #include "build/build_config.h" #include "content/common/child_process.h" #include "content/common/gpu/gpu_messages.h" #include "content/gpu/gpu_info_collector.h" #include "content/gpu/gpu_watchdog_thread.h" #include "content/public/common/content_client.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_channel_handle.h" #include "ipc/ipc_sync_message_filter.h" #include "ui/gl/gl_implementation.h" const int kGpuTimeout = 10000; namespace { bool GpuProcessLogMessageHandler(int severity, const char* file, int line, size_t message_start, const std::string& str) { std::string header = str.substr(0, message_start); std::string message = str.substr(message_start); // If we are not on main thread in child process, send through // the sync_message_filter; otherwise send directly. if (MessageLoop::current() != ChildProcess::current()->main_thread()->message_loop()) { ChildProcess::current()->main_thread()->sync_message_filter()->Send( new GpuHostMsg_OnLogMessage(severity, header, message)); } else { ChildThread::current()->Send(new GpuHostMsg_OnLogMessage(severity, header, message)); } return false; } } // namespace GpuChildThread::GpuChildThread(bool dead_on_arrival, const content::GPUInfo& gpu_info) : dead_on_arrival_(dead_on_arrival), gpu_info_(gpu_info) { #if defined(OS_WIN) target_services_ = NULL; collecting_dx_diagnostics_ = false; #endif } GpuChildThread::GpuChildThread(const std::string& channel_id) : ChildThread(channel_id), dead_on_arrival_(false) { #if defined(OS_WIN) target_services_ = NULL; collecting_dx_diagnostics_ = false; #endif if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) || CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessGPU)) { // For single process and in-process GPU mode, we need to load and // initialize the GL implementation and locate the GL entry points here. if (!gfx::GLSurface::InitializeOneOff()) { VLOG(1) << "gfx::GLSurface::InitializeOneOff()"; } } } GpuChildThread::~GpuChildThread() { logging::SetLogMessageHandler(NULL); } void GpuChildThread::Init(const base::Time& process_start_time) { process_start_time_ = process_start_time; } bool GpuChildThread::Send(IPC::Message* msg) { // The GPU process must never send a synchronous IPC message to the browser // process. This could result in deadlock. DCHECK(!msg->is_sync()); return ChildThread::Send(msg); } bool GpuChildThread::OnControlMessageReceived(const IPC::Message& msg) { bool msg_is_ok = true; bool handled = true; IPC_BEGIN_MESSAGE_MAP_EX(GpuChildThread, msg, msg_is_ok) IPC_MESSAGE_HANDLER(GpuMsg_Initialize, OnInitialize) IPC_MESSAGE_HANDLER(GpuMsg_CollectGraphicsInfo, OnCollectGraphicsInfo) IPC_MESSAGE_HANDLER(GpuMsg_GetVideoMemoryUsageStats, OnGetVideoMemoryUsageStats) IPC_MESSAGE_HANDLER(GpuMsg_SetVideoMemoryWindowCount, OnSetVideoMemoryWindowCount) IPC_MESSAGE_HANDLER(GpuMsg_Clean, OnClean) IPC_MESSAGE_HANDLER(GpuMsg_Crash, OnCrash) IPC_MESSAGE_HANDLER(GpuMsg_Hang, OnHang) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP_EX() if (handled) return true; return gpu_channel_manager_.get() && gpu_channel_manager_->OnMessageReceived(msg); } void GpuChildThread::OnInitialize() { Send(new GpuHostMsg_Initialized(!dead_on_arrival_)); if (dead_on_arrival_) { VLOG(1) << "Exiting GPU process due to errors during initialization"; MessageLoop::current()->Quit(); return; } // We don't need to pipe log messages if we are running the GPU thread in // the browser process. if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess) && !CommandLine::ForCurrentProcess()->HasSwitch(switches::kInProcessGPU)) logging::SetLogMessageHandler(GpuProcessLogMessageHandler); // Record initialization only after collecting the GPU info because that can // take a significant amount of time. gpu_info_.initialization_time = base::Time::Now() - process_start_time_; // In addition to disabling the watchdog if the command line switch is // present, disable it in two other cases. OSMesa is expected to run very // slowly. Also disable the watchdog on valgrind because the code is expected // to run slowly in that case. bool enable_watchdog = !CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableGpuWatchdog) && gfx::GetGLImplementation() != gfx::kGLImplementationOSMesaGL && !RunningOnValgrind(); // Disable the watchdog in debug builds because they tend to only be run by // developers who will not appreciate the watchdog killing the GPU process. #ifndef NDEBUG enable_watchdog = false; #endif // Start the GPU watchdog only after anything that is expected to be time // consuming has completed, otherwise the process is liable to be aborted. if (enable_watchdog) { watchdog_thread_ = new GpuWatchdogThread(kGpuTimeout); watchdog_thread_->Start(); } // Defer creation of the render thread. This is to prevent it from handling // IPC messages before the sandbox has been enabled and all other necessary // initialization has succeeded. gpu_channel_manager_.reset(new GpuChannelManager( this, watchdog_thread_, ChildProcess::current()->io_message_loop_proxy(), ChildProcess::current()->GetShutDownEvent())); #if defined(OS_LINUX) // Ensure the browser process receives the GPU info before a reply to any // subsequent IPC it might send. Send(new GpuHostMsg_GraphicsInfoCollected(gpu_info_)); #endif } void GpuChildThread::StopWatchdog() { if (watchdog_thread_.get()) { watchdog_thread_->Stop(); } } void GpuChildThread::OnCollectGraphicsInfo() { CommandLine* command_line = CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kDisableGpuSandbox) || command_line->ForCurrentProcess()->HasSwitch(switches::kSingleProcess) || command_line->ForCurrentProcess()->HasSwitch(switches::kInProcessGPU)) { // GPU full info collection should only happen on un-sandboxed GPU process // or single process/in-process gpu mode. if (!gpu_info_collector::CollectGraphicsInfo(&gpu_info_)) VLOG(1) << "gpu_info_collector::CollectGraphicsInfo failed"; content::GetContentClient()->SetGpuInfo(gpu_info_); #if defined(OS_WIN) if (!collecting_dx_diagnostics_) { // Prevent concurrent collection of DirectX diagnostics. collecting_dx_diagnostics_ = true; // Asynchronously collect the DirectX diagnostics because this can take a // couple of seconds. if (!base::WorkerPool::PostTask( FROM_HERE, base::Bind(&GpuChildThread::CollectDxDiagnostics, this), true)) { // Flag GPU info as complete if the DirectX diagnostics cannot be // collected. collecting_dx_diagnostics_ = false; gpu_info_.finalized = true; } } #endif } Send(new GpuHostMsg_GraphicsInfoCollected(gpu_info_)); } void GpuChildThread::OnGetVideoMemoryUsageStats() { content::GPUVideoMemoryUsageStats video_memory_usage_stats; if (gpu_channel_manager_.get()) gpu_channel_manager_->gpu_memory_manager()->GetVideoMemoryUsageStats( video_memory_usage_stats); Send(new GpuHostMsg_VideoMemoryUsageStats(video_memory_usage_stats)); } void GpuChildThread::OnSetVideoMemoryWindowCount(uint32 window_count) { if (gpu_channel_manager_.get()) gpu_channel_manager_->gpu_memory_manager()->SetWindowCount(window_count); } void GpuChildThread::OnClean() { VLOG(1) << "GPU: Removing all contexts"; if (gpu_channel_manager_.get()) gpu_channel_manager_->LoseAllContexts(); } void GpuChildThread::OnCrash() { VLOG(1) << "GPU: Simulating GPU crash"; // Good bye, cruel world. volatile int* it_s_the_end_of_the_world_as_we_know_it = NULL; *it_s_the_end_of_the_world_as_we_know_it = 0xdead; } void GpuChildThread::OnHang() { VLOG(1) << "GPU: Simulating GPU hang"; for (;;) { // Do not sleep here. The GPU watchdog timer tracks the amount of user // time this thread is using and it doesn't use much while calling Sleep. } } #if defined(OS_WIN) // Runs on a worker thread. The GPU process never terminates voluntarily so // it is safe to assume that its message loop is valid. void GpuChildThread::CollectDxDiagnostics(GpuChildThread* thread) { content::DxDiagNode node; gpu_info_collector::GetDxDiagnostics(&node); thread->message_loop()->PostTask( FROM_HERE, base::Bind(&GpuChildThread::SetDxDiagnostics, thread, node)); } // Runs on the main thread. void GpuChildThread::SetDxDiagnostics(GpuChildThread* thread, const content::DxDiagNode& node) { thread->gpu_info_.dx_diagnostics = node; thread->gpu_info_.finalized = true; thread->collecting_dx_diagnostics_ = false; thread->Send(new GpuHostMsg_GraphicsInfoCollected(thread->gpu_info_)); } #endif