blob: 3ce52a7866c1ce6fe92fa8a9e080f5d49ac5bd9c [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/gpu/gpu_process_host.h"
#include <stddef.h>
#include <algorithm>
#include <list>
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/cxx17_backports.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_pump_type.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/discardable_memory/service/discardable_shared_memory_manager.h"
#include "components/tracing/common/tracing_switches.h"
#include "components/viz/common/features.h"
#include "components/viz/common/switches.h"
#include "content/browser/browser_child_process_host_impl.h"
#include "content/browser/child_process_launcher.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/browser/gpu/gpu_disk_cache_factory.h"
#include "content/browser/gpu/gpu_main_thread_factory.h"
#include "content/browser/gpu/gpu_memory_buffer_manager_singleton.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/worker_host/dedicated_worker_host.h"
#include "content/browser/worker_host/dedicated_worker_service_impl.h"
#include "content/common/child_process.mojom.h"
#include "content/common/child_process_host_impl.h"
#include "content/common/in_process_child_thread_params.h"
#include "content/public/browser/browser_child_process_host.h"
#include "content/public/browser/browser_main_runner.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/gpu_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#include "content/public/common/zygote/zygote_buildflags.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/config/gpu_driver_bug_list.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_preferences.h"
#include "gpu/config/gpu_switches.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "gpu/ipc/common/result_codes.h"
#include "gpu/ipc/host/gpu_disk_cache.h"
#include "media/base/media_switches.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/network_isolation_key.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/display/display_switches.h"
#include "ui/gfx/font_render_params.h"
#include "ui/gfx/switches.h"
#include "ui/gl/gl_switches.h"
#include "ui/latency/latency_info.h"
#if !BUILDFLAG(IS_ANDROID)
#include "components/metrics/stability_metrics_helper.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#include "sandbox/policy/win/sandbox_win.h"
#include "sandbox/win/src/restricted_token_utils.h"
#include "sandbox/win/src/sandbox_policy.h"
#include "sandbox/win/src/window.h"
#include "ui/gfx/win/rendering_window_manager.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/gpu_platform_support_host.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/ozone_switches.h"
#endif
#if BUILDFLAG(USE_ZYGOTE_HANDLE)
#include "content/common/zygote/zygote_handle_impl_linux.h"
#endif
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
#include "gpu/ipc/common/gpu_surface_tracker.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "content/browser/gpu/ca_transaction_gpu_coordinator.h"
#endif
namespace content {
base::subtle::Atomic32 GpuProcessHost::gpu_crash_count_ = 0;
bool GpuProcessHost::crashed_before_ = false;
int GpuProcessHost::recent_crash_count_ = 0;
gpu::GpuMode GpuProcessHost::last_crash_mode_ = gpu::GpuMode::UNKNOWN;
// RESULT_CODE_HUNG is expected to be the same in both
// content/public/common/result_codes.h and gpu/ipc/common/result_codes.h
static_assert(RESULT_CODE_HUNG == static_cast<int>(gpu::RESULT_CODE_HUNG),
"Please use the same enum value in both header files.");
namespace {
// UMA histogram names.
constexpr char kProcessLifetimeEventsHardwareAccelerated[] =
"GPU.ProcessLifetimeEvents.HardwareAccelerated";
constexpr char kProcessLifetimeEventsSwiftShader[] =
"GPU.ProcessLifetimeEvents.SwiftShader";
// Returns the UMA histogram name for the given GPU mode.
const char* GetProcessLifetimeUmaName(gpu::GpuMode gpu_mode) {
switch (gpu_mode) {
// TODO(rivr): Add separate histograms for the different hardware modes.
case gpu::GpuMode::UNKNOWN:
NOTREACHED();
return nullptr;
case gpu::GpuMode::HARDWARE_GL:
case gpu::GpuMode::HARDWARE_METAL:
case gpu::GpuMode::HARDWARE_VULKAN:
return kProcessLifetimeEventsHardwareAccelerated;
case gpu::GpuMode::SWIFTSHADER:
return kProcessLifetimeEventsSwiftShader;
case gpu::GpuMode::DISPLAY_COMPOSITOR:
return nullptr;
}
}
// Forgive one GPU process crash after this many minutes.
// This value should not be too small because then Chrome could end up in an
// endless loop where it hangs and gets killed by GPU watchdog and hangs again.
constexpr int kForgiveGpuCrashMinutes = 5;
// Forgive one GPU process crash, when the GPU process is launched to run only
// the display compositor, after this many minutes.
constexpr int kForgiveDisplayCompositorCrashMinutes = 10;
int GetForgiveMinutes(gpu::GpuMode gpu_mode) {
return gpu_mode == gpu::GpuMode::DISPLAY_COMPOSITOR
? kForgiveDisplayCompositorCrashMinutes
: kForgiveGpuCrashMinutes;
}
// This matches base::TerminationStatus.
// These values are persisted to logs. Entries (except MAX_ENUM) should not be
// renumbered and numeric values should never be reused. Should also avoid
// OS-defines in this enum to keep the values consistent on all platforms.
enum class GpuTerminationStatus {
NORMAL_TERMINATION = 0,
ABNORMAL_TERMINATION = 1,
PROCESS_WAS_KILLED = 2,
PROCESS_CRASHED = 3,
STILL_RUNNING = 4,
PROCESS_WAS_KILLED_BY_OOM = 5,
OOM_PROTECTED = 6,
LAUNCH_FAILED = 7,
OOM = 8,
MAX_ENUM = 9,
};
GpuTerminationStatus ConvertToGpuTerminationStatus(
base::TerminationStatus status) {
switch (status) {
case base::TERMINATION_STATUS_NORMAL_TERMINATION:
return GpuTerminationStatus::NORMAL_TERMINATION;
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
return GpuTerminationStatus::ABNORMAL_TERMINATION;
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
return GpuTerminationStatus::PROCESS_WAS_KILLED;
case base::TERMINATION_STATUS_PROCESS_CRASHED:
#if BUILDFLAG(IS_WIN)
// Treat integrity failure as a crash on Windows.
case base::TERMINATION_STATUS_INTEGRITY_FAILURE:
#endif
return GpuTerminationStatus::PROCESS_CRASHED;
case base::TERMINATION_STATUS_STILL_RUNNING:
return GpuTerminationStatus::STILL_RUNNING;
#if BUILDFLAG(IS_CHROMEOS)
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
return GpuTerminationStatus::PROCESS_WAS_KILLED_BY_OOM;
#endif
#if BUILDFLAG(IS_ANDROID)
case base::TERMINATION_STATUS_OOM_PROTECTED:
return GpuTerminationStatus::OOM_PROTECTED;
#endif
case base::TERMINATION_STATUS_LAUNCH_FAILED:
return GpuTerminationStatus::LAUNCH_FAILED;
case base::TERMINATION_STATUS_OOM:
return GpuTerminationStatus::OOM;
case base::TERMINATION_STATUS_MAX_ENUM:
NOTREACHED();
return GpuTerminationStatus::MAX_ENUM;
// Do not add default.
}
NOTREACHED();
return GpuTerminationStatus::ABNORMAL_TERMINATION;
}
// Command-line switches to propagate to the GPU process.
static const char* const kSwitchNames[] = {
sandbox::policy::switches::kDisableSeccompFilterSandbox,
sandbox::policy::switches::kGpuSandboxAllowSysVShm,
sandbox::policy::switches::kGpuSandboxFailuresFatal,
sandbox::policy::switches::kDisableGpuSandbox,
sandbox::policy::switches::kNoSandbox,
#if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS)
switches::kDisableDevShmUsage,
#endif
#if BUILDFLAG(IS_WIN)
switches::kDisableHighResTimer,
switches::kRaiseTimerFrequency,
#endif // BUILDFLAG(IS_WIN)
switches::kEnableANGLEFeatures,
switches::kDisableANGLEFeatures,
switches::kDisableBreakpad,
switches::kDisableGpuRasterization,
switches::kDisableGLExtensions,
switches::kDisableLogging,
switches::kDisableMipmapGeneration,
switches::kDisableShaderNameHashing,
switches::kDisableSkiaRuntimeOpts,
switches::kDisableWebRtcHWEncoding,
switches::kEnableBackgroundThreadPool,
switches::kEnableGpuRasterization,
switches::kEnableLogging,
switches::kEnableDeJelly,
switches::kDeJellyScreenWidth,
switches::kDoubleBufferCompositing,
switches::kHeadless,
switches::kLoggingLevel,
switches::kEnableLowEndDeviceMode,
switches::kDisableLowEndDeviceMode,
switches::kProfilingAtStart,
switches::kProfilingFile,
switches::kProfilingFlush,
switches::kRunAllCompositorStagesBeforeDraw,
switches::kSkiaFontCacheLimitMb,
switches::kSkiaResourceCacheLimitMb,
switches::kTestGLLib,
switches::kTraceToConsole,
switches::kUseFakeMjpegDecodeAccelerator,
switches::kUseGpuInTests,
switches::kV,
switches::kVModule,
switches::kUseAdapterLuid,
switches::kWebViewDrawFunctorUsesVulkan,
#if BUILDFLAG(IS_MAC)
sandbox::policy::switches::kEnableSandboxLogging,
sandbox::policy::switches::kDisableMetalShaderCache,
switches::kShowMacOverlayBorders,
switches::kUseHighGPUThreadPriorityForPerfTests,
#endif
#if BUILDFLAG(IS_OZONE)
switches::kOzonePlatform,
switches::kDisableExplicitDmaFences,
switches::kOzoneDumpFile,
switches::kDisableBufferBWCompression,
#endif
#if BUILDFLAG(IS_LINUX)
switches::kX11Display,
switches::kNoXshm,
#endif
switches::kGpuBlocklistTestGroup,
switches::kGpuDriverBugListTestGroup,
switches::kGpuWatchdogTimeoutSeconds,
switches::kUseCmdDecoder,
switches::kForceVideoOverlays,
#if BUILDFLAG(IS_ANDROID)
switches::kEnableReachedCodeProfiler,
switches::kReachedCodeSamplingIntervalUs,
#endif
#if BUILDFLAG(IS_CHROMEOS)
switches::kPlatformDisallowsChromeOSDirectVideoDecoder,
switches::kSchedulerBoostUrgent,
#endif
#if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
switches::kHardwareVideoDecodeFrameRate,
switches::kMaxChromeOSDecoderThreads,
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
switches::kLacrosEnablePlatformHevc,
switches::kLacrosUseChromeosProtectedMedia,
switches::kLacrosUseChromeosProtectedAv1,
#endif
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum GPUProcessLifetimeEvent {
LAUNCHED = 0,
// When the GPU process crashes the (DIED_FIRST_TIME + recent_crash_count - 1)
// bucket in the appropriate UMA histogram will be incremented. The first
// crash will be DIED_FIRST_TIME, the second DIED_FIRST_TIME+1, etc.
DIED_FIRST_TIME = 1,
GPU_PROCESS_LIFETIME_EVENT_MAX = 100,
};
// Indexed by GpuProcessKind. There is one of each kind maximum. This array may
// only be accessed from the IO thread.
GpuProcessHost* g_gpu_process_hosts[GPU_PROCESS_KIND_COUNT];
void RunCallbackOnIO(GpuProcessKind kind,
bool force_create,
base::OnceCallback<void(GpuProcessHost*)> callback) {
GpuProcessHost* host = GpuProcessHost::Get(kind, force_create);
std::move(callback).Run(host);
}
void OnGpuProcessHostDestroyedOnUI(int host_id, const std::string& message) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
GpuDataManagerImpl::GetInstance()->AddLogMessage(logging::LOG_ERROR,
"GpuProcessHost", message);
#if BUILDFLAG(IS_OZONE)
ui::OzonePlatform::GetInstance()
->GetGpuPlatformSupportHost()
->OnChannelDestroyed(host_id);
#endif
}
// NOTE: changes to this class need to be reviewed by the security team.
class GpuSandboxedProcessLauncherDelegate
: public SandboxedProcessLauncherDelegate {
public:
explicit GpuSandboxedProcessLauncherDelegate(
const base::CommandLine& cmd_line)
:
#if BUILDFLAG(IS_WIN)
enable_appcontainer_(true),
#endif
cmd_line_(cmd_line) {
}
~GpuSandboxedProcessLauncherDelegate() override = default;
#if BUILDFLAG(IS_WIN)
bool DisableDefaultPolicy() override { return true; }
std::string GetSandboxTag() override {
return sandbox::policy::SandboxWin::GetSandboxTagForDelegate(
"gpu", GetSandboxType());
}
enum GPUAppContainerEnableState{
AC_ENABLED = 0,
AC_DISABLED_GL = 1,
AC_DISABLED_FORCE = 2,
MAX_ENABLE_STATE = 3,
};
bool GetAppContainerId(std::string* appcontainer_id) override {
if (UseOpenGLRenderer()) {
return false;
}
if (!enable_appcontainer_) {
return false;
}
*appcontainer_id = base::WideToUTF8(cmd_line_.GetProgram().value());
return true;
}
// For the GPU process we gotten as far as USER_LIMITED. The next level
// which is USER_RESTRICTED breaks both the DirectX backend and the OpenGL
// backend. Note that the GPU process is connected to the interactive
// desktop.
bool PreSpawnTarget(sandbox::TargetPolicy* policy) override {
sandbox::TargetConfig* config = policy->GetConfig();
if (config->IsConfigured())
return true;
if (UseOpenGLRenderer()) {
// Open GL path.
sandbox::ResultCode result = config->SetTokenLevel(
sandbox::USER_RESTRICTED_SAME_ACCESS, sandbox::USER_LIMITED);
if (result != sandbox::SBOX_ALL_OK)
return false;
result = sandbox::policy::SandboxWin::SetJobLevel(
sandbox::mojom::Sandbox::kGpu, sandbox::JobLevel::kUnprotected, 0,
config);
if (result != sandbox::SBOX_ALL_OK)
return false;
} else {
sandbox::ResultCode result = config->SetTokenLevel(
sandbox::USER_RESTRICTED_SAME_ACCESS, sandbox::USER_LIMITED);
if (result != sandbox::SBOX_ALL_OK)
return false;
// UI restrictions break when we access Windows from outside our job.
// However, we don't want a proxy window in this process because it can
// introduce deadlocks where the renderer blocks on the gpu, which in
// turn blocks on the browser UI thread. So, instead we forgo a window
// message pump entirely and just add job restrictions to prevent child
// processes.
result = sandbox::policy::SandboxWin::SetJobLevel(
sandbox::mojom::Sandbox::kGpu, sandbox::JobLevel::kLimitedUser,
JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | JOB_OBJECT_UILIMIT_DESKTOP |
JOB_OBJECT_UILIMIT_EXITWINDOWS |
JOB_OBJECT_UILIMIT_DISPLAYSETTINGS,
config);
if (result != sandbox::SBOX_ALL_OK)
return false;
}
// Check if we are running on the winlogon desktop and set a delayed
// integrity in this case. This is needed because a low integrity gpu
// process will not be allowed to access the winlogon desktop (gpu process
// integrity has to be at least medium in order to be able to access the
// winlogon desktop normally). So instead, let the gpu process start with
// the normal integrity and delay the switch to low integrity until after
// the gpu process has started and has access to the desktop.
if (ShouldSetDelayedIntegrity()) {
config->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
} else {
sandbox::ResultCode result =
config->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
if (result != sandbox::SBOX_ALL_OK)
return false;
}
// Block this DLL even if it is not loaded by the browser process.
config->AddDllToUnload(L"cmsetac.dll");
if (cmd_line_.HasSwitch(switches::kEnableLogging)) {
std::wstring log_file_path = logging::GetLogFileFullPath();
if (!log_file_path.empty()) {
sandbox::ResultCode result = config->AddRule(
sandbox::SubSystem::kFiles, sandbox::Semantics::kFilesAllowAny,
log_file_path.c_str());
if (result != sandbox::SBOX_ALL_OK)
return false;
}
}
return true;
}
// TODO: Remove this once AppContainer sandbox is enabled by default.
void DisableAppContainer() { enable_appcontainer_ = false; }
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(USE_ZYGOTE_HANDLE)
ZygoteHandle GetZygote() override {
if (sandbox::policy::IsUnsandboxedSandboxType(GetSandboxType()))
return nullptr;
// The GPU process needs a specialized sandbox, so fork from the unsandboxed
// zygote and then apply the actual sandboxes in the forked process.
return GetUnsandboxedZygote();
}
#endif // BUILDFLAG(USE_ZYGOTE_HANDLE)
sandbox::mojom::Sandbox GetSandboxType() override {
if (cmd_line_.HasSwitch(sandbox::policy::switches::kDisableGpuSandbox)) {
DVLOG(1) << "GPU sandbox is disabled";
return sandbox::mojom::Sandbox::kNoSandbox;
}
return sandbox::mojom::Sandbox::kGpu;
}
private:
#if BUILDFLAG(IS_WIN)
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ProcessIntegrityResult{
kLowIl = 0,
kOpenGlMediumIl = 1,
kDesktopAccessMediumIl = 2,
kMaxValue = kDesktopAccessMediumIl,
};
bool UseOpenGLRenderer() {
return cmd_line_.GetSwitchValueASCII(switches::kUseGL) ==
gl::kGLImplementationDesktopName;
}
bool ShouldSetDelayedIntegrity() {
if (UseOpenGLRenderer()) {
return true;
}
// Desktop access is needed to load user32.dll, we can lower token in child
// process after that's done.
if (sandbox::CanLowIntegrityAccessDesktop()) {
return false;
}
return true;
}
bool enable_appcontainer_;
#endif
base::CommandLine cmd_line_;
};
void BindDiscardableMemoryReceiverOnIO(
mojo::PendingReceiver<
discardable_memory::mojom::DiscardableSharedMemoryManager> receiver,
discardable_memory::DiscardableSharedMemoryManager* manager) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
manager->Bind(std::move(receiver));
}
void BindDiscardableMemoryReceiverOnUI(
mojo::PendingReceiver<
discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&BindDiscardableMemoryReceiverOnIO, std::move(receiver),
discardable_memory::DiscardableSharedMemoryManager::Get()));
}
} // anonymous namespace
// static
bool GpuProcessHost::ValidateHost(GpuProcessHost* host) {
// The Gpu process is invalid if it's not using SwiftShader, the card is
// blocklisted, and we can kill it and start over.
static bool is_single_process =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSingleProcess);
static bool in_process_GPU =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kInProcessGPU);
if (is_single_process || in_process_GPU || host->valid_) {
return true;
}
host->ForceShutdown();
return false;
}
// static
GpuProcessHost* GpuProcessHost::Get(GpuProcessKind kind, bool force_create) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Do not launch the unsandboxed GPU info collection process if GPU is
// disabled
if (kind == GPU_PROCESS_KIND_INFO_COLLECTION) {
auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kDisableGpu) ||
command_line->HasSwitch(switches::kSingleProcess) ||
command_line->HasSwitch(switches::kInProcessGPU))
return nullptr;
}
if (g_gpu_process_hosts[kind] && ValidateHost(g_gpu_process_hosts[kind]))
return g_gpu_process_hosts[kind];
if (!force_create)
return nullptr;
// Do not create a new process if browser is shutting down.
if (BrowserMainRunner::ExitedMainMessageLoop()) {
DLOG(ERROR) << "BrowserMainRunner::ExitedMainMessageLoop()";
return nullptr;
}
static int last_host_id = 0;
int host_id;
host_id = ++last_host_id;
GpuProcessHost* host = new GpuProcessHost(host_id, kind);
if (host->Init())
return host;
// TODO(sievers): Revisit this behavior. It's not really a crash, but we also
// want the fallback-to-sw behavior if we cannot initialize the GPU.
LOG(ERROR) << "GPU process failed to initialize.";
host->RecordProcessCrash();
delete host;
return nullptr;
}
// static
void GpuProcessHost::GetHasGpuProcess(base::OnceCallback<void(bool)> callback) {
if (!GetUIThreadTaskRunner({})->BelongsToCurrentThread()) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&GpuProcessHost::GetHasGpuProcess, std::move(callback)));
return;
}
bool has_gpu = false;
for (size_t i = 0; i < std::size(g_gpu_process_hosts); ++i) {
GpuProcessHost* host = g_gpu_process_hosts[i];
if (host && ValidateHost(host)) {
has_gpu = true;
break;
}
}
std::move(callback).Run(has_gpu);
}
// static
void GpuProcessHost::CallOnIO(
const base::Location& location,
GpuProcessKind kind,
bool force_create,
base::OnceCallback<void(GpuProcessHost*)> callback) {
#if !BUILDFLAG(IS_WIN)
DCHECK_NE(kind, GPU_PROCESS_KIND_INFO_COLLECTION);
#endif
GetUIThreadTaskRunner({})->PostTask(
location, base::BindOnce(&RunCallbackOnIO, kind, force_create,
std::move(callback)));
}
void GpuProcessHost::BindInterface(
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
if (interface_name ==
discardable_memory::mojom::DiscardableSharedMemoryManager::Name_) {
BindDiscardableMemoryReceiver(
mojo::PendingReceiver<
discardable_memory::mojom::DiscardableSharedMemoryManager>(
std::move(interface_pipe)));
return;
}
process_->child_process()->BindReceiver(
mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe)));
}
#if BUILDFLAG(IS_OZONE)
void GpuProcessHost::TerminateGpuProcess(const std::string& message) {
// At the moment, this path is only used by Ozone/Wayland. Once others start
// to use this, start to distinguish the origin of termination. By default,
// it's unknown.
termination_origin_ = GpuTerminationOrigin::kOzoneWaylandProxy;
process_->TerminateOnBadMessageReceived(message);
}
#endif // BUILDFLAG(IS_OZONE)
// static
GpuProcessHost* GpuProcessHost::FromID(int host_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (int i = 0; i < GPU_PROCESS_KIND_COUNT; ++i) {
GpuProcessHost* host = g_gpu_process_hosts[i];
if (host && host->host_id_ == host_id && ValidateHost(host))
return host;
}
return nullptr;
}
// static
int GpuProcessHost::GetGpuCrashCount() {
return static_cast<int>(base::subtle::NoBarrier_Load(&gpu_crash_count_));
}
// static
void GpuProcessHost::IncrementCrashCount(gpu::GpuMode gpu_mode) {
int forgive_minutes = GetForgiveMinutes(gpu_mode);
DCHECK_GT(forgive_minutes, 0);
// Last time the process crashed.
static base::TimeTicks last_crash_time;
base::TimeTicks current_time = base::TimeTicks::Now();
if (gpu_mode != last_crash_mode_) {
// Reset the crash count when the GPU starts crashing in a different mode.
recent_crash_count_ = 0;
} else if (crashed_before_) {
// Remove one crash per |forgive_minutes| from the crash count, so
// occasional crashes won't add up and eventually prevent using the GPU
// process.
int minutes_delta = (current_time - last_crash_time).InMinutes();
int crashes_to_forgive = minutes_delta / forgive_minutes;
recent_crash_count_ = std::max(0, recent_crash_count_ - crashes_to_forgive);
}
recent_crash_count_ =
std::min(recent_crash_count_ + 1,
static_cast<int>(GPU_PROCESS_LIFETIME_EVENT_MAX) - 1);
crashed_before_ = true;
last_crash_mode_ = gpu_mode;
last_crash_time = current_time;
}
GpuProcessHost::GpuProcessHost(int host_id, GpuProcessKind kind)
: host_id_(host_id),
valid_(true),
in_process_(false),
kind_(kind),
process_launched_(false) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSingleProcess) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kInProcessGPU)) {
in_process_ = true;
}
#if !BUILDFLAG(IS_ANDROID)
if (!in_process_ && kind != GPU_PROCESS_KIND_INFO_COLLECTION &&
base::FeatureList::IsEnabled(
features::kForwardMemoryPressureEventsToGpuProcess)) {
memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
FROM_HERE, base::BindRepeating(&GpuProcessHost::OnMemoryPressure,
base::Unretained(this)));
}
#endif
// If the 'single GPU process' policy ever changes, we still want to maintain
// it for 'gpu thread' mode and only create one instance of host and thread.
DCHECK(!in_process_ || g_gpu_process_hosts[kind] == nullptr);
g_gpu_process_hosts[kind] = this;
process_ = std::make_unique<BrowserChildProcessHostImpl>(
PROCESS_TYPE_GPU, this, ChildProcessHost::IpcMode::kNormal);
}
GpuProcessHost::~GpuProcessHost() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (in_process_gpu_thread_)
DCHECK(process_);
SendOutstandingReplies();
#if BUILDFLAG(IS_MAC)
if (ca_transaction_gpu_coordinator_) {
ca_transaction_gpu_coordinator_->HostWillBeDestroyed();
ca_transaction_gpu_coordinator_ = nullptr;
}
#endif
// This is only called on the IO thread so no race against the constructor
// for another GpuProcessHost.
if (g_gpu_process_hosts[kind_] == this)
g_gpu_process_hosts[kind_] = nullptr;
bool block_offscreen_contexts = true;
if (!in_process_ && process_launched_) {
ChildProcessTerminationInfo info =
process_->GetTerminationInfo(/*known_dead=*/false);
std::string message;
if (kind_ == GPU_PROCESS_KIND_SANDBOXED) {
UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessTerminationStatus2",
ConvertToGpuTerminationStatus(info.status),
GpuTerminationStatus::MAX_ENUM);
int exit_code = base::clamp(info.exit_code, 0, 100);
#if !BUILDFLAG(IS_ANDROID)
if (info.status != base::TERMINATION_STATUS_NORMAL_TERMINATION &&
info.status != base::TERMINATION_STATUS_STILL_RUNNING &&
exit_code !=
static_cast<int>(content::RESULT_CODE_GPU_DEAD_ON_ARRIVAL)) {
// Add a sample to Stability.Counts2's GPU crash bucket.
//
// On Android Chrome and Android WebLayer, GPU crashes are logged via
// ContentStabilityMetricsProvider::OnCrashDumpProcessed() and
// StabilityMetricsHelper::IncreaseGpuCrashCount().
metrics::StabilityMetricsHelper::RecordStabilityEvent(
metrics::StabilityEventType::kGpuCrash);
}
#endif // !BUILDFLAG(IS_ANDROID)
if (info.status == base::TERMINATION_STATUS_NORMAL_TERMINATION ||
info.status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION ||
info.status == base::TERMINATION_STATUS_PROCESS_CRASHED) {
// Windows always returns PROCESS_CRASHED on abnormal termination, as it
// doesn't have a way to distinguish the two.
base::UmaHistogramSparse("GPU.GPUProcessExitCode", exit_code);
}
message = "The GPU process ";
} else {
message = "The info collection GPU process ";
}
bool unexpected_exit = false;
switch (info.status) {
case base::TERMINATION_STATUS_NORMAL_TERMINATION:
// Don't block offscreen contexts (and force page reload for webgl)
// if this was an intentional shutdown or the OOM killer on Android
// killed us while Chrome was in the background.
block_offscreen_contexts = false;
message += "exited normally. Everything is okay.";
break;
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
message += base::StringPrintf("exited with code %d.", info.exit_code);
unexpected_exit = true;
break;
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
UMA_HISTOGRAM_ENUMERATION("GPU.GPUProcessTerminationOrigin",
termination_origin_,
GpuTerminationOrigin::kMax);
message += "was killed by you! Why?";
break;
case base::TERMINATION_STATUS_PROCESS_CRASHED:
message += "crashed!";
unexpected_exit = true;
break;
case base::TERMINATION_STATUS_STILL_RUNNING:
message += "hasn't exited yet.";
break;
#if BUILDFLAG(IS_CHROMEOS)
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
message += "was killed due to out of memory.";
unexpected_exit = true;
break;
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_ANDROID)
case base::TERMINATION_STATUS_OOM_PROTECTED:
message += "was protected from out of memory kill.";
unexpected_exit = true;
break;
#endif // BUILDFLAG(IS_ANDROID)
case base::TERMINATION_STATUS_LAUNCH_FAILED:
message += "failed to start!";
unexpected_exit = true;
break;
case base::TERMINATION_STATUS_OOM:
message += "died due to out of memory.";
unexpected_exit = true;
break;
#if BUILDFLAG(IS_WIN)
case base::TERMINATION_STATUS_INTEGRITY_FAILURE:
message += "failed integrity checks.";
unexpected_exit = true;
break;
#endif
case base::TERMINATION_STATUS_MAX_ENUM:
NOTREACHED();
break;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceBrowserCrashOnGpuCrash)) {
CHECK(!unexpected_exit)
<< "Force Chrome to crash due to unexpected GPU process crash";
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&OnGpuProcessHostDestroyedOnUI, host_id_, message));
}
// If there are any remaining offscreen contexts at the point the GPU process
// exits, assume something went wrong, and block their URLs from accessing
// client 3D APIs without prompting.
if (block_offscreen_contexts && gpu_host_)
gpu_host_->BlockLiveOffscreenContexts();
}
bool GpuProcessHost::Init() {
init_start_time_ = base::TimeTicks::Now();
TRACE_EVENT_INSTANT0("gpu", "LaunchGpuProcess", TRACE_EVENT_SCOPE_THREAD);
process_->GetHost()->CreateChannelMojo();
mode_ = GpuDataManagerImpl::GetInstance()->GetGpuMode();
if (in_process_) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(GetGpuMainThreadFactory());
gpu::GpuPreferences gpu_preferences = GetGpuPreferencesFromCommandLine();
GpuDataManagerImpl::GetInstance()->UpdateGpuPreferences(
&gpu_preferences, GPU_PROCESS_KIND_SANDBOXED);
in_process_gpu_thread_.reset(GetGpuMainThreadFactory()(
InProcessChildThreadParams(
base::SingleThreadTaskRunner::GetCurrentDefault(),
process_->GetInProcessMojoInvitation()),
gpu_preferences));
base::Thread::Options options;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
// WGL needs to create its own window and pump messages on it.
options.message_pump_type = base::MessagePumpType::UI;
#endif
options.thread_type = base::ThreadType::kCompositing;
in_process_gpu_thread_->StartWithOptions(std::move(options));
} else if (!LaunchGpuProcess()) {
return false;
}
mojo::PendingRemote<viz::mojom::VizMain> viz_main_pending_remote;
process_->child_process()->BindServiceInterface(
viz_main_pending_remote.InitWithNewPipeAndPassReceiver());
viz::GpuHostImpl::InitParams params;
params.restart_id = host_id_;
params.disable_gpu_shader_disk_cache =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableGpuShaderDiskCache);
params.product = GetContentClient()->browser()->GetProduct();
params.deadline_to_synchronize_surfaces =
switches::GetDeadlineToSynchronizeSurfaces();
params.main_thread_task_runner = GetUIThreadTaskRunner({});
params.info_collection_gpu_process =
kind_ == GPU_PROCESS_KIND_INFO_COLLECTION;
gpu_host_ = std::make_unique<viz::GpuHostImpl>(
this, std::move(viz_main_pending_remote), std::move(params));
if (in_process_) {
// Fake a callback that the process is ready.
OnProcessLaunched();
}
#if BUILDFLAG(IS_MAC)
ca_transaction_gpu_coordinator_ = CATransactionGPUCoordinator::Create(this);
#endif
return true;
}
void GpuProcessHost::OnProcessLaunched() {
UMA_HISTOGRAM_TIMES("GPU.GPUProcessLaunchTime",
base::TimeTicks::Now() - init_start_time_);
DCHECK(gpu_host_);
if (in_process_) {
// Don't set |process_id_| as it is publicly available through process_id().
gpu_host_->SetProcessId(base::GetCurrentProcId());
} else {
process_id_ = process_->GetProcess().Pid();
DCHECK_NE(base::kNullProcessId, process_id_);
gpu_host_->SetProcessId(process_id_);
}
}
void GpuProcessHost::OnProcessLaunchFailed(int error_code) {
LOG(ERROR) << "GPU process launch failed: error_code=" << error_code;
RecordProcessCrash();
}
void GpuProcessHost::OnProcessCrashed(int exit_code) {
// Record crash before doing anything that could start a new GPU process.
LOG(ERROR) << "GPU process exited unexpectedly: exit_code=" << exit_code;
RecordProcessCrash();
gpu_host_->OnProcessCrashed();
SendOutstandingReplies();
GpuDataManagerImpl::GetInstance()->ProcessCrashed();
}
gpu::GPUInfo GpuProcessHost::GetGPUInfo() const {
return GpuDataManagerImpl::GetInstance()->GetGPUInfo();
}
gpu::GpuFeatureInfo GpuProcessHost::GetGpuFeatureInfo() const {
return GpuDataManagerImpl::GetInstance()->GetGpuFeatureInfo();
}
void GpuProcessHost::DidInitialize(
const gpu::GPUInfo& gpu_info,
const gpu::GpuFeatureInfo& gpu_feature_info,
const absl::optional<gpu::GPUInfo>& gpu_info_for_hardware_gpu,
const absl::optional<gpu::GpuFeatureInfo>&
gpu_feature_info_for_hardware_gpu,
const gfx::GpuExtraInfo& gpu_extra_info) {
if (GetGpuCrashCount() > 0) {
LOG(WARNING) << "Reinitialized the GPU process after a crash. The reported "
"initialization time was "
<< gpu_info.initialization_time.InMilliseconds() << " ms";
}
if (kind_ != GPU_PROCESS_KIND_INFO_COLLECTION) {
auto* gpu_data_manager = GpuDataManagerImpl::GetInstance();
// Update GpuFeatureInfo first, because UpdateGpuInfo() will notify all
// listeners.
gpu_data_manager->UpdateGpuFeatureInfo(gpu_feature_info,
gpu_feature_info_for_hardware_gpu);
gpu_data_manager->UpdateGpuInfo(gpu_info, gpu_info_for_hardware_gpu);
gpu_data_manager->UpdateGpuExtraInfo(gpu_extra_info);
}
#if BUILDFLAG(IS_ANDROID)
// Android may kill the GPU process to free memory, especially when the app
// is the background, so Android cannot have a hard limit on GPU starts.
// Reset crash count on Android when context creation succeeds, but only if no
// fallback option is available.
if (!GpuDataManagerImpl::GetInstance()->CanFallback())
recent_crash_count_ = 0;
#endif
}
void GpuProcessHost::DidFailInitialize() {
did_fail_initialize_ = true;
if (kind_ == GPU_PROCESS_KIND_SANDBOXED)
GpuDataManagerImpl::GetInstance()->FallBackToNextGpuMode();
}
void GpuProcessHost::DidCreateContextSuccessfully() {
#if BUILDFLAG(IS_ANDROID)
// Android may kill the GPU process to free memory, especially when the app
// is the background, so Android cannot have a hard limit on GPU starts.
// Reset crash count on Android when context creation succeeds, but only if no
// fallback option is available.
if (!GpuDataManagerImpl::GetInstance()->CanFallback())
recent_crash_count_ = 0;
#endif
}
void GpuProcessHost::MaybeShutdownGpuProcess() {
if (!in_process_ &&
GetContentClient()->browser()->CanShutdownGpuProcessNowOnIOThread()) {
delete this;
}
}
void GpuProcessHost::DidUpdateGPUInfo(const gpu::GPUInfo& gpu_info) {
GpuDataManagerImpl::GetInstance()->UpdateGpuInfo(gpu_info, absl::nullopt);
}
#if BUILDFLAG(IS_WIN)
void GpuProcessHost::DidUpdateOverlayInfo(
const gpu::OverlayInfo& overlay_info) {
GpuDataManagerImpl::GetInstance()->UpdateOverlayInfo(overlay_info);
}
void GpuProcessHost::DidUpdateDXGIInfo(gfx::mojom::DXGIInfoPtr dxgi_info) {
GpuDataManagerImpl::GetInstance()->UpdateDXGIInfo(std::move(dxgi_info));
}
#endif
std::string GpuProcessHost::GetIsolationKey(
int32_t process_id,
const blink::WebGPUExecutionContextToken& token) {
if (token.Is<blink::DocumentToken>()) {
// Return an empty isolation key if the frame host is gone. This could
// happen if the frame is destroyed (or being destroyed) in between when we
// are trying to get the isolation key.
RenderFrameHostImpl* frame_host = RenderFrameHostImpl::FromDocumentToken(
process_id, token.GetAs<blink::DocumentToken>());
if (!frame_host) {
return "";
}
auto isolation_key =
frame_host->GetNetworkIsolationKey().ToCacheKeyString();
return isolation_key ? *isolation_key : "";
} else if (token.Is<blink::DedicatedWorkerToken>()) {
// Return an empty isolation key if the process host or the worker host is
// gone. This may happen if the worker is destroyed (or being destroyed) in
// between when we are trying to get the isolation key.
RenderProcessHost* render_process_host =
RenderProcessHost::FromID(process_id);
if (!render_process_host ||
!render_process_host->IsInitializedAndNotDead()) {
return "";
}
DedicatedWorkerHost* dedicated_worker_host =
static_cast<StoragePartitionImpl*>(
render_process_host->GetStoragePartition())
->GetDedicatedWorkerService()
->GetDedicatedWorkerHostFromToken(
token.GetAs<blink::DedicatedWorkerToken>());
if (!dedicated_worker_host) {
return "";
}
auto isolation_key =
dedicated_worker_host->GetNetworkIsolationKey().ToCacheKeyString();
return isolation_key ? *isolation_key : "";
}
NOTREACHED();
return "";
}
void GpuProcessHost::BlockDomainsFrom3DAPIs(const std::set<GURL>& urls,
gpu::DomainGuilt guilt) {
GpuDataManagerImpl::GetInstance()->BlockDomainsFrom3DAPIs(urls, guilt);
}
bool GpuProcessHost::GpuAccessAllowed() const {
return GpuDataManagerImpl::GetInstance()->GpuAccessAllowed(nullptr);
}
void GpuProcessHost::DisableGpuCompositing() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
DLOG(ERROR) << "Can't disable GPU compositing";
#else
// TODO(crbug.com/819474): The switch from GPU to software compositing should
// be handled here instead of by ImageTransportFactory.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce([]() {
if (auto* factory = ImageTransportFactory::GetInstance())
factory->DisableGpuCompositing();
}));
#endif
}
gpu::GpuDiskCacheFactory* GpuProcessHost::GetGpuDiskCacheFactory() {
return GetGpuDiskCacheFactorySingleton();
}
void GpuProcessHost::RecordLogMessage(int32_t severity,
const std::string& header,
const std::string& message) {
GpuDataManagerImpl::GetInstance()->AddLogMessage(severity, header, message);
}
void GpuProcessHost::BindDiscardableMemoryReceiver(
mojo::PendingReceiver<
discardable_memory::mojom::DiscardableSharedMemoryManager> receiver) {
BindDiscardableMemoryReceiverOnUI(std::move(receiver));
}
GpuProcessKind GpuProcessHost::kind() {
return kind_;
}
// Atomically shut down the GPU process with a normal termination status.
void GpuProcessHost::ForceShutdown() {
// This is only called on the IO thread so no race against the constructor
// for another GpuProcessHost.
if (g_gpu_process_hosts[kind_] == this)
g_gpu_process_hosts[kind_] = nullptr;
process_->ForceShutdown();
}
void GpuProcessHost::DumpProcessStack() {
#if BUILDFLAG(IS_ANDROID)
if (in_process_)
return;
process_->DumpProcessStack();
#endif
}
void GpuProcessHost::RunServiceImpl(mojo::GenericPendingReceiver receiver) {
process_->child_process()->BindServiceInterface(std::move(receiver));
}
bool GpuProcessHost::LaunchGpuProcess() {
const base::CommandLine& browser_command_line =
*base::CommandLine::ForCurrentProcess();
base::CommandLine::StringType gpu_launcher =
browser_command_line.GetSwitchValueNative(switches::kGpuLauncher);
#if BUILDFLAG(IS_ANDROID)
// crbug.com/447735. readlink("self/proc/exe") sometimes fails on Android
// at startup with EACCES. As a workaround ignore this here, since the
// executable name is actually not used or useful anyways.
std::unique_ptr<base::CommandLine> cmd_line =
std::make_unique<base::CommandLine>(base::CommandLine::NO_PROGRAM);
#else
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
int child_flags = gpu_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF
: ChildProcessHost::CHILD_NORMAL;
#elif BUILDFLAG(IS_MAC)
int child_flags = ChildProcessHost::CHILD_GPU;
#else
int child_flags = ChildProcessHost::CHILD_NORMAL;
#endif
base::FilePath exe_path = ChildProcessHost::GetChildPath(child_flags);
if (exe_path.empty())
return false;
std::unique_ptr<base::CommandLine> cmd_line =
std::make_unique<base::CommandLine>(exe_path);
#endif
cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kGpuProcess);
BrowserChildProcessHostImpl::CopyTraceStartupFlags(cmd_line.get());
#if BUILDFLAG(IS_WIN)
cmd_line->AppendArg(switches::kPrefetchArgumentGpu);
#endif // BUILDFLAG(IS_WIN)
if (kind_ == GPU_PROCESS_KIND_INFO_COLLECTION) {
cmd_line->AppendSwitch(sandbox::policy::switches::kDisableGpuSandbox);
cmd_line->AppendSwitchASCII(switches::kUseGL,
gl::kGLImplementationDisabledName);
// Pass the current device info to the info-collection GPU process for
// crash key logging.
const gpu::GPUInfo::GPUDevice device_info = GetGPUInfo().active_gpu();
cmd_line->AppendSwitchASCII(
switches::kGpuVendorId,
base::StringPrintf("%u", device_info.vendor_id));
cmd_line->AppendSwitchASCII(
switches::kGpuDeviceId,
base::StringPrintf("%u", device_info.device_id));
#if BUILDFLAG(IS_WIN)
cmd_line->AppendSwitchASCII(
switches::kGpuSubSystemId,
base::StringPrintf("%u", device_info.sub_sys_id));
cmd_line->AppendSwitchASCII(switches::kGpuRevision,
base::StringPrintf("%u", device_info.revision));
#endif
if (device_info.driver_version.length()) {
cmd_line->AppendSwitchASCII(switches::kGpuDriverVersion,
device_info.driver_version);
}
}
// TODO(penghuang): Replace all GPU related switches with GpuPreferences.
// https://crbug.com/590825
// If you want a browser command-line switch passed to the GPU process
// you need to add it to |kSwitchNames| at the beginning of this file.
cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
std::size(kSwitchNames));
cmd_line->CopySwitchesFrom(
browser_command_line, switches::kGLSwitchesCopiedFromGpuProcessHost,
switches::kGLSwitchesCopiedFromGpuProcessHostNumSwitches);
if (browser_command_line.HasSwitch(switches::kDisableFrameRateLimit))
cmd_line->AppendSwitch(switches::kDisableGpuVsync);
std::vector<const char*> gpu_workarounds;
gpu::GpuDriverBugList::AppendAllWorkarounds(&gpu_workarounds);
cmd_line->CopySwitchesFrom(browser_command_line, gpu_workarounds.data(),
gpu_workarounds.size());
// Because AppendExtraCommandLineSwitches is called here, we should call
// LaunchWithoutExtraCommandLineSwitches() instead of Launch for gpu process
// launch below.
GetContentClient()->browser()->AppendExtraCommandLineSwitches(
cmd_line.get(), process_->GetData().id);
// TODO(kylechar): The command line flags added here should be based on
// |mode_|.
GpuDataManagerImpl::GetInstance()->AppendGpuCommandLine(cmd_line.get(),
kind_);
// If specified, prepend a launcher program to the command line.
if (!gpu_launcher.empty())
cmd_line->PrependWrapper(gpu_launcher);
std::unique_ptr<GpuSandboxedProcessLauncherDelegate> delegate =
std::make_unique<GpuSandboxedProcessLauncherDelegate>(*cmd_line);
#if BUILDFLAG(IS_WIN)
if (crashed_before_)
delegate->DisableAppContainer();
#endif // BUILDFLAG(IS_WIN)
// Do not call process_->Launch() here.
// AppendExtraCommandLineSwitches will be called again in process_->Launch(),
// Call LaunchWithoutExtraCommandLineSwitches() so the command line switches
// will not be appended twice.
process_->LaunchWithoutExtraCommandLineSwitches(
std::move(delegate), std::move(cmd_line),
/*file_data=*/
std::make_unique<ChildProcessLauncherFileData>(), true);
process_launched_ = true;
if (kind_ == GPU_PROCESS_KIND_SANDBOXED) {
auto* const histogram = GetProcessLifetimeUmaName(mode_);
if (histogram) {
base::UmaHistogramEnumeration(histogram, LAUNCHED,
GPU_PROCESS_LIFETIME_EVENT_MAX);
}
}
return true;
}
void GpuProcessHost::SendOutstandingReplies() {
valid_ = false;
if (gpu_host_)
gpu_host_->SendOutstandingReplies();
}
void GpuProcessHost::RecordProcessCrash() {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
// Maximum number of times the GPU process can crash before we try something
// different, like disabling hardware acceleration or all GL.
constexpr int kGpuFallbackCrashCount = 3;
#else
// Android and Chrome OS switch to software compositing and fallback crashes
// the browser process. For Android the OS can also kill the GPU process
// arbitrarily. Use a larger maximum crash count here.
constexpr int kGpuFallbackCrashCount = 6;
#endif
// Ending only acts as a failure if the GPU process was actually started and
// was intended for actual rendering (and not just checking caps or other
// options).
if (!process_launched_ || kind_ != GPU_PROCESS_KIND_SANDBOXED)
return;
// Keep track of the total number of GPU crashes.
base::subtle::NoBarrier_AtomicIncrement(&gpu_crash_count_, 1);
LOG(WARNING) << "The GPU process has crashed " << GetGpuCrashCount()
<< " time(s)";
// It's possible GPU mode fallback has already happened. In this case, |mode_|
// will still be the mode of the failed process.
IncrementCrashCount(mode_);
auto* const histogram = GetProcessLifetimeUmaName(mode_);
if (histogram) {
base::UmaHistogramExactLinear(
histogram, DIED_FIRST_TIME + recent_crash_count_ - 1,
static_cast<int>(GPU_PROCESS_LIFETIME_EVENT_MAX));
}
// GPU process initialization failed and fallback already happened.
if (did_fail_initialize_)
return;
bool disable_crash_limit = base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableGpuProcessCrashLimit);
// GPU process crashed too many times, fallback on a different GPU process
// mode.
if (recent_crash_count_ >= kGpuFallbackCrashCount && !disable_crash_limit)
GpuDataManagerImpl::GetInstance()->FallBackToNextGpuMode();
}
viz::mojom::GpuService* GpuProcessHost::gpu_service() {
DCHECK(gpu_host_);
return gpu_host_->gpu_service();
}
#if BUILDFLAG(IS_WIN)
viz::mojom::InfoCollectionGpuService*
GpuProcessHost::info_collection_gpu_service() {
DCHECK(gpu_host_);
return gpu_host_->info_collection_gpu_service();
}
#endif
int GpuProcessHost::GetIDForTesting() const {
return process_->GetData().id;
}
#if !BUILDFLAG(IS_ANDROID)
void GpuProcessHost::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level) {
gpu_host_->gpu_service()->OnMemoryPressure(level);
}
#endif
} // namespace content