blob: 9d5ac554aba5b647c3a38a1a9d0cbc8b3ad12046 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/gpu/gpu_child_thread.h"
#include <string>
#include <vector>
#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/public/common/content_switches.h"
#include "content/gpu/gpu_info_collector.h"
#include "content/gpu/gpu_watchdog_thread.h"
#include "ipc/ipc_channel_handle.h"
#include "ui/gfx/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);
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 (!gpu_info_collector::CollectGraphicsInfo(&gpu_info_)) {
LOG(INFO) << "gpu_info_collector::CollectGraphicsInfo failed";
}
}
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_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() {
if (dead_on_arrival_) {
LOG(INFO) << "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
// Disable the watchdog for Windows. It tends to abort when the GPU process
// is not hung but still taking a long time to do something. Instead, the
// browser process displays a dialog when it notices that the child window
// is hung giving the user an opportunity to terminate it. This is the
// same mechanism used to abort hung plugins.
#if defined(OS_WIN)
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()));
// Ensure the browser process receives the GPU info before a reply to any
// subsequent IPC it might send.
Send(new GpuHostMsg_GraphicsInfoCollected(gpu_info_));
}
void GpuChildThread::StopWatchdog() {
if (watchdog_thread_.get()) {
watchdog_thread_->Stop();
}
}
void GpuChildThread::OnCollectGraphicsInfo() {
#if defined(OS_WIN)
if (!gpu_info_.finalized && !collecting_dx_diagnostics_) {
// Prevent concurrent collection of DirectX diagnostics.
collecting_dx_diagnostics_ = true;
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableGpuSandbox)) {
// 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;
} else {
// Do not send response if we are still completing the GPUInfo struct
return;
}
}
}
#endif
Send(new GpuHostMsg_GraphicsInfoCollected(gpu_info_));
}
void GpuChildThread::OnClean() {
LOG(INFO) << "GPU: Removing all contexts";
if (gpu_channel_manager_.get())
gpu_channel_manager_->LoseAllContexts();
}
void GpuChildThread::OnCrash() {
LOG(INFO) << "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() {
LOG(INFO) << "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