910e62b5创建于 1月15日历史提交
// 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/browser_gpu_channel_host_factory.h"

#include <utility>

#include "base/android/orderfile/orderfile_buildflags.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/process/process_handle.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/viz/host/gpu_host_impl.h"
#include "content/browser/child_process_host_impl.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/browser/gpu/gpu_disk_cache_factory.h"
#include "content/browser/gpu/gpu_process_host.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_data_manager.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "gpu/ipc/common/gpu_watchdog_timeout.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/constants.mojom.h"

#if BUILDFLAG(IS_MAC)
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#endif

namespace content {

namespace {

#if BUILDFLAG(IS_ANDROID)

// This is used as the stack frame to group these timeout crashes, so avoid
// renaming it or moving the LOG(FATAL) call.
NOINLINE void TimedOut() {
  LOG(FATAL) << "Timed out waiting for GPU channel.";
}

void DumpGpuStackOnProcessThread() {
  GpuProcessHost* host =
      GpuProcessHost::Get(GPU_PROCESS_KIND_SANDBOXED, /*force_create=*/false);
  if (host) {
    host->DumpProcessStack();
  }
  TimedOut();
}

#endif  // BUILDFLAG(IS_ANDROID)

}  // namespace

BrowserGpuChannelHostFactory* BrowserGpuChannelHostFactory::instance_ = nullptr;

class BrowserGpuChannelHostFactory::EstablishRequest
    : public base::RefCountedThreadSafe<EstablishRequest> {
 public:
  static scoped_refptr<EstablishRequest> Create(
      int gpu_client_id,
      uint64_t gpu_client_tracing_id,
      bool sync,
      std::vector<gpu::GpuChannelEstablishedCallback> established_callbacks);

  void Wait();
  void Cancel();

  void AddCallback(gpu::GpuChannelEstablishedCallback callback) {
    established_callbacks_.push_back(std::move(callback));
  }

  std::vector<gpu::GpuChannelEstablishedCallback> TakeCallbacks() {
    return std::move(established_callbacks_);
  }

  const scoped_refptr<gpu::GpuChannelHost>& gpu_channel() {
    return gpu_channel_;
  }

  bool finished() const { return finished_; }

 private:
  friend class base::RefCountedThreadSafe<EstablishRequest>;
  EstablishRequest(
      int gpu_client_id,
      uint64_t gpu_client_tracing_id,
      std::vector<gpu::GpuChannelEstablishedCallback> established_callbacks);
  ~EstablishRequest() {}
  void RestartTimeout();
  // Note |sync| is only true if EstablishGpuChannelSync is being called. In
  // that case we make the sync mojo call since we're on the UI thread and
  // therefore can't wait for an async mojo reply on the same thread.
  void Establish(bool sync);
  void OnEstablished(
      mojo::ScopedMessagePipeHandle channel_handle,
      const gpu::GPUInfo& gpu_info,
      const gpu::GpuFeatureInfo& gpu_feature_info,
      const gpu::SharedImageCapabilities& shared_image_capabilities,
      viz::GpuHostImpl::EstablishChannelStatus status);
  void Finish();
  void FinishAndRunCallbacksOnMain();
  void FinishOnMain();
  void RunCallbacksOnMain();

  std::vector<gpu::GpuChannelEstablishedCallback> established_callbacks_;
  base::WaitableEvent event_;
  const int gpu_client_id_;
  const uint64_t gpu_client_tracing_id_;
  scoped_refptr<gpu::GpuChannelHost> gpu_channel_;
  bool finished_;
  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
};

scoped_refptr<BrowserGpuChannelHostFactory::EstablishRequest>
BrowserGpuChannelHostFactory::EstablishRequest::Create(
    int gpu_client_id,
    uint64_t gpu_client_tracing_id,
    bool sync,
    std::vector<gpu::GpuChannelEstablishedCallback> established_callbacks) {
  scoped_refptr<EstablishRequest> establish_request = new EstablishRequest(
      gpu_client_id, gpu_client_tracing_id, std::move(established_callbacks));
  establish_request->Establish(sync);
  return establish_request;
}

BrowserGpuChannelHostFactory::EstablishRequest::EstablishRequest(
    int gpu_client_id,
    uint64_t gpu_client_tracing_id,
    std::vector<gpu::GpuChannelEstablishedCallback> established_callbacks)
    : established_callbacks_(std::move(established_callbacks)),
      event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
             base::WaitableEvent::InitialState::NOT_SIGNALED),
      gpu_client_id_(gpu_client_id),
      gpu_client_tracing_id_(gpu_client_tracing_id),
      finished_(false),
#if BUILDFLAG(IS_MAC)
      main_task_runner_(ui::WindowResizeHelperMac::Get()->task_runner())
#else
      main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault())
#endif
{
}

void BrowserGpuChannelHostFactory::EstablishRequest::RestartTimeout() {
  BrowserGpuChannelHostFactory* factory =
      BrowserGpuChannelHostFactory::instance();
  if (factory)
    factory->RestartTimeout();
}

void BrowserGpuChannelHostFactory::EstablishRequest::Establish(bool sync) {
  GpuProcessHost* host = GpuProcessHost::Get();
  if (!host) {
    LOG(ERROR) << "Failed to launch GPU process.";
    Finish();
    return;
  }

  bool is_gpu_host = true;
  host->gpu_host()->EstablishGpuChannel(
      gpu_client_id_, gpu_client_tracing_id_, is_gpu_host,
      /*enable_extra_handles_validation=*/false, sync,
      base::BindOnce(
          &BrowserGpuChannelHostFactory::EstablishRequest::OnEstablished,
          this));
  host->gpu_host()->SetChannelClientPid(gpu_client_id_,
                                        base::GetCurrentProcId());
}

void BrowserGpuChannelHostFactory::EstablishRequest::OnEstablished(
    mojo::ScopedMessagePipeHandle channel_handle,
    const gpu::GPUInfo& gpu_info,
    const gpu::GpuFeatureInfo& gpu_feature_info,
    const gpu::SharedImageCapabilities& shared_image_capabilities,
    viz::GpuHostImpl::EstablishChannelStatus status) {
  if (!channel_handle.is_valid() &&
      status == viz::GpuHostImpl::EstablishChannelStatus::kGpuHostInvalid &&
      // Ask client every time instead of passing this down from UI thread to
      // avoid having the value be stale.
      GetContentClient()->browser()->AllowGpuLaunchRetryOnIOThread()) {
    DVLOG(1) << "Failed to create channel on existing GPU process. Trying to "
                "restart GPU process.";
    main_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(
            &BrowserGpuChannelHostFactory::EstablishRequest::RestartTimeout,
            this));
    // TODO(jam): can we ever enter this when it was a sync call?
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            &BrowserGpuChannelHostFactory::EstablishRequest::Establish, this,
            false));
    return;
  }

  if (channel_handle.is_valid()) {
    gpu_channel_ = base::MakeRefCounted<gpu::GpuChannelHost>(
        gpu_client_id_, gpu_info, gpu_feature_info, shared_image_capabilities,
        std::move(channel_handle), GetIOThreadTaskRunner({}));
  }
  Finish();
}

void BrowserGpuChannelHostFactory::EstablishRequest::Finish() {
  event_.Signal();
  FinishAndRunCallbacksOnMain();
}

void BrowserGpuChannelHostFactory::EstablishRequest::
    FinishAndRunCallbacksOnMain() {
  FinishOnMain();
  RunCallbacksOnMain();
}

void BrowserGpuChannelHostFactory::EstablishRequest::FinishOnMain() {
  if (!finished_) {
    BrowserGpuChannelHostFactory* factory =
        BrowserGpuChannelHostFactory::instance();
    factory->GpuChannelEstablished(this);
    finished_ = true;
  }
}

void BrowserGpuChannelHostFactory::EstablishRequest::RunCallbacksOnMain() {
  std::vector<gpu::GpuChannelEstablishedCallback> established_callbacks;
  established_callbacks_.swap(established_callbacks);
  for (auto& callback : established_callbacks) {
    std::move(callback).Run(gpu_channel_);
  }
}

void BrowserGpuChannelHostFactory::EstablishRequest::Wait() {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  {
    // We're blocking the UI thread, which is generally undesirable.
    // In this case we need to wait for this before we can show any UI
    // /anyway/, so it won't cause additional jank.
    // TODO(piman): Make this asynchronous (http://crbug.com/125248).
    TRACE_EVENT0("browser",
                 "BrowserGpuChannelHostFactory::EstablishGpuChannelSync");
    base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
    event_.Wait();
  }
  FinishOnMain();
}

void BrowserGpuChannelHostFactory::EstablishRequest::Cancel() {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  finished_ = true;
  established_callbacks_.clear();
}

void BrowserGpuChannelHostFactory::Initialize(bool establish_gpu_channel) {
  DCHECK(!instance_);
  instance_ = new BrowserGpuChannelHostFactory();
  if (establish_gpu_channel) {
    instance_->EstablishGpuChannel(gpu::GpuChannelEstablishedCallback());
  }
}

void BrowserGpuChannelHostFactory::Terminate() {
  DCHECK(instance_);
  delete instance_;
  instance_ = nullptr;
}

void BrowserGpuChannelHostFactory::MaybeCloseChannel() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!gpu_channel_ || !gpu_channel_->HasOneRef())
    return;

  gpu_channel_->DestroyChannel();
  gpu_channel_ = nullptr;
}

void BrowserGpuChannelHostFactory::CloseChannel() {
  if (gpu_channel_) {
    gpu_channel_->DestroyChannel();
    gpu_channel_ = nullptr;
  }
}

BrowserGpuChannelHostFactory::BrowserGpuChannelHostFactory()
    : gpu_client_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()),
      gpu_client_tracing_id_(
          memory_instrumentation::mojom::kServiceTracingProcessId) {}

BrowserGpuChannelHostFactory::~BrowserGpuChannelHostFactory() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (pending_request_.get())
    pending_request_->Cancel();
  if (gpu_channel_) {
    gpu_channel_->DestroyChannel();
    gpu_channel_ = nullptr;
  }
}

void BrowserGpuChannelHostFactory::EstablishGpuChannel(
    gpu::GpuChannelEstablishedCallback callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  EstablishGpuChannel(std::move(callback), false);
}

// Blocking the UI thread to open a GPU channel is not supported on Android.
// (Opening the initial channel to a child process involves handling a reply
// task on the UI thread first, so we cannot block here.)
scoped_refptr<gpu::GpuChannelHost>
BrowserGpuChannelHostFactory::EstablishGpuChannelSync() {
#if BUILDFLAG(IS_ANDROID)
  NOTREACHED();
#else
  EstablishGpuChannel(gpu::GpuChannelEstablishedCallback(), true);
  return gpu_channel_;
#endif
}

void BrowserGpuChannelHostFactory::EstablishGpuChannel(
    gpu::GpuChannelEstablishedCallback callback,
    bool sync) {
  if (gpu_channel_.get() && gpu_channel_->IsLost()) {
// TODO(crbug.com/40790884): DCHECKs are disabled during automated testing on
// CrOS and this check failed when tested on an experimental builder. Revert
// https://crrev.com/c/3174621 to enable it. See go/chrome-dcheck-on-cros
// or http://crbug.com/1113456 for more details.
#if !BUILDFLAG(IS_CHROMEOS)
    DCHECK(!pending_request_.get());
#endif
    // Recreate the channel if it has been lost.
    gpu_channel_->DestroyChannel();
    gpu_channel_ = nullptr;
  }

  std::vector<gpu::GpuChannelEstablishedCallback> callbacks;
  if (sync && !gpu_channel_ && pending_request_) {
    // There's a previous request. Cancel it since we must call the synchronous
    // version of the mojo method and the previous call was asynchronous.
    callbacks = pending_request_->TakeCallbacks();
    GpuProcessHost* host = GpuProcessHost::Get();
    if (host)
      host->gpu_host()->CloseChannel(gpu_client_id_);
    pending_request_->Cancel();
    pending_request_ = nullptr;
  }

  if (pending_request_) {
    DCHECK(callbacks.empty());
    if (!callback.is_null())
      pending_request_->AddCallback(std::move(callback));

    return;
  }

  if (!callback.is_null())
    callbacks.push_back(std::move(callback));

  if (!gpu_channel_) {
    // We should only get here if the context was lost.
    DCHECK(!pending_request_);

    scoped_refptr<EstablishRequest> request = EstablishRequest::Create(
        gpu_client_id_, gpu_client_tracing_id_, sync, std::move(callbacks));

    // If the establish request is a sync call, or the request fails
    // immediately, it is already marked as finished at this point.
    if (!request->finished())
      pending_request_ = std::move(request);

    // Sync and timeouts aren't currently compatible, which is fine since sync
    // isn't used on Android while timeouts are only used on Android.
    if (!sync)
      RestartTimeout();

    return;
  }

  DCHECK(gpu_channel_);
  for (auto& cb : callbacks)
    std::move(cb).Run(gpu_channel_);
}

// Ensures that any pending timeout is cancelled when we are backgrounded.
// Restarts the timeout when we return to the foreground.
void BrowserGpuChannelHostFactory::SetApplicationVisible(bool is_visible) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (is_visible_ == is_visible)
    return;

  is_visible_ = is_visible;
  if (is_visible_) {
    RestartTimeout();
  } else {
    timeout_.Stop();
  }
}

gpu::GpuChannelHost* BrowserGpuChannelHostFactory::GetGpuChannel() {
  if (gpu_channel_.get() && !gpu_channel_->IsLost())
    return gpu_channel_.get();

  return nullptr;
}

void BrowserGpuChannelHostFactory::GpuChannelEstablished(
    EstablishRequest* request) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!pending_request_ || pending_request_ == request);
  gpu_channel_ = request->gpu_channel();
  pending_request_ = nullptr;
  timeout_.Stop();
  if (gpu_channel_)
    GetContentClient()->SetGpuInfo(gpu_channel_->gpu_info());
}

void BrowserGpuChannelHostFactory::RestartTimeout() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Only implement timeout on Android, which does not have a software fallback.
#if BUILDFLAG(IS_ANDROID)
  base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
  if (cl->HasSwitch(switches::kDisableTimeoutsForProfiling)) {
    return;
  }
  // Only enable it for out of process GPU. In-process generally only has false
  // positives.
  if (cl->HasSwitch(switches::kSingleProcess) ||
      cl->HasSwitch(switches::kInProcessGPU)) {
    return;
  }

  // Don't restart the timeout if we aren't visible. This function will be
  // re-called when we become visible again.
  if (!pending_request_ || !is_visible_)
    return;

#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
    BUILDFLAG(ORDERFILE_INSTRUMENTATION)
  constexpr int64_t kGpuChannelTimeoutInSeconds = 40;
#else
  // This is also monitored by the GPU watchdog (restart or initialization
  // event) in the GPU process. Make this slightly longer than the GPU watchdog
  // timeout to give the GPU a chance to crash itself before crashing the
  // browser.
  int64_t kGpuChannelTimeoutInSeconds =
      gpu::kGpuWatchdogTimeout.InSeconds() * gpu::kRestartFactor + 5;
#endif

  timeout_.Start(FROM_HERE, base::Seconds(kGpuChannelTimeoutInSeconds),
                 base::BindOnce(&DumpGpuStackOnProcessThread));
#endif  // BUILDFLAG(IS_ANDROID)
}

}  // namespace content