chromium/chromecast/browser/lru_renderer_cache.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/browser/lru_renderer_cache.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "chromecast/browser/renderer_prelauncher.h"
#include "content/public/browser/site_instance.h"

namespace chromecast {

LRURendererCache::LRURendererCache(
    content::BrowserContext* browser_context,
    size_t max_renderers)
    : browser_context_(browser_context),
      max_renderers_(max_renderers),
      in_use_count_(0),
      weak_factory_(this) {
  DCHECK(browser_context_);
  memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
      FROM_HERE, base::BindRepeating(&LRURendererCache::OnMemoryPressure,
                                     weak_factory_.GetWeakPtr()));
}

LRURendererCache::~LRURendererCache() = default;

std::unique_ptr<RendererPrelauncher> LRURendererCache::TakeRendererPrelauncher(
    const GURL& page_url) {
  in_use_count_++;
  std::unique_ptr<RendererPrelauncher> search;
  for (auto it = cache_.begin(); it != cache_.end(); ++it) {
    if ((*it)->IsForURL(page_url)) {
      // Found pre-launched renderer for this site, return it.
      search = std::move(*it);
      cache_.erase(it);
      LOG(INFO) << "Cache hit for pre-launched SiteInstance: "
                << page_url.spec();
      break;
    }
  }

  if (!search) {
    // TODO(jlevasseur): Submit metric?
    LOG(WARNING) << "Cache miss for pre-launched SiteInstance: "
                 << page_url.spec();
  }

  EvictCache();
  return search;
}

void LRURendererCache::EvictCache() {
  // Evict least-recently-used renderers so that the total number of renderers
  // doesn't exceed |max_renderers_|, or until the cache is empty.
  while (!cache_.empty() && in_use_count_ + cache_.size() > max_renderers_) {
    LOG(INFO) << "Evicting pre-launched SiteInstance: " << cache_.back()->url();
    cache_.pop_back();
  }
}

void LRURendererCache::ReleaseRendererPrelauncher(const GURL& page_url) {
  DCHECK(in_use_count_ > 0);
  in_use_count_--;
  if (in_use_count_ >= max_renderers_) {
    DCHECK(cache_.empty());
    // We don't have room to maintain a cache, so don't prelaunch this site even
    // though it's the most recently used.
    return;
  }
  if (!page_url.is_valid()) {
    // Can't cache an invalid site.
    return;
  }
  // We have room to maintain a non-empty cache, so we can pre-launch the
  // renderer process for the next site. We post this as a task to ensure that
  // the prior site (which is in the process of being released) has completed
  // destruction; otherwise, its renderer process will overlap with the next
  // pre-launched process, temporarily exceeding |max_renderers_|.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&LRURendererCache::StartNextPrelauncher,
                                weak_factory_.GetWeakPtr(), page_url));
}

void LRURendererCache::StartNextPrelauncher(const GURL& page_url) {
  if (in_use_count_ >= max_renderers_) {
    DCHECK(cache_.empty());
    // The maximum number of renderers is already in use, so the cache must
    // remain empty.
    return;
  }
  DLOG(INFO) << "Pre-launching a renderer for: " << page_url.spec();
  if (factory_for_testing_) {
    cache_.push_front(factory_for_testing_->Create(browser_context_, page_url));
  } else {
    cache_.push_front(
        std::make_unique<RendererPrelauncher>(browser_context_, page_url));
  }
  // Evict the cache before prelaunching.
  EvictCache();
  cache_.front()->Prelaunch();
}

void LRURendererCache::OnMemoryPressure(
    base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
  if (memory_pressure_level ==
      base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
    DLOG(INFO) << "Dropping prelauncher cache due to memory pressure.";
    cache_.clear();
  }
}

void LRURendererCache::SetFactoryForTesting(
    RendererPrelauncherFactory* factory) {
  DCHECK(factory);
  factory_for_testing_ = factory;
}

}  // namespace chromecast