Commit b80d72cc authored by Sean Topping's avatar Sean Topping Committed by Commit Bot

[Chromecast] Add LRURendererCache for prelaunch caching

Introduces an LRU cache for prelaunching renderers. This class creates a
pool of prelaunched renderers for the N most recently closed pages.

Bug: internal b/77879457
Test: cast_shell_unittests
Change-Id: I0500f24b65377360d0c74ea390b476e1b879708a
Reviewed-on: https://chromium-review.googlesource.com/1036730
Commit-Queue: Sean Topping <seantopping@chromium.org>
Reviewed-by: default avatarStephen Lanham <slan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557736}
parent b6297b2b
...@@ -57,6 +57,8 @@ cast_source_set("browser") { ...@@ -57,6 +57,8 @@ cast_source_set("browser") {
"devtools/cast_devtools_manager_delegate.h", "devtools/cast_devtools_manager_delegate.h",
"devtools/remote_debugging_server.cc", "devtools/remote_debugging_server.cc",
"devtools/remote_debugging_server.h", "devtools/remote_debugging_server.h",
"lru_renderer_cache.cc",
"lru_renderer_cache.h",
"media/media_caps_impl.cc", "media/media_caps_impl.cc",
"media/media_caps_impl.h", "media/media_caps_impl.h",
"media/supported_codec_finder.cc", "media/supported_codec_finder.cc",
...@@ -406,6 +408,7 @@ cast_source_set("unittests") { ...@@ -406,6 +408,7 @@ cast_source_set("unittests") {
"cast_media_blocker_unittest.cc", "cast_media_blocker_unittest.cc",
"cast_touch_device_manager_unittest.cc", "cast_touch_device_manager_unittest.cc",
"devtools/cast_devtools_manager_delegate_unittest.cc", "devtools/cast_devtools_manager_delegate_unittest.cc",
"lru_renderer_cache_test.cc",
"network_context_manager_unittest.cc", "network_context_manager_unittest.cc",
] ]
......
// Copyright 2018 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 "chromecast/browser/lru_renderer_cache.h"
#include <utility>
#include "base/bind.h"
#include "base/threading/sequenced_task_runner_handle.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>(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::SequencedTaskRunnerHandle::Get()->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
// Copyright 2018 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.
#ifndef CHROMECAST_BROWSER_LRU_RENDERER_CACHE_H_
#define CHROMECAST_BROWSER_LRU_RENDERER_CACHE_H_
#include <list>
#include <memory>
#include "base/macros.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "url/gurl.h"
namespace content {
class BrowserContext;
}
namespace chromecast {
class RendererPrelauncher;
// Factory class for testing.
class RendererPrelauncherFactory {
public:
virtual std::unique_ptr<RendererPrelauncher> Create(
content::BrowserContext* browser_context,
const GURL& page_url) = 0;
virtual ~RendererPrelauncherFactory() = default;
};
// This class maintains a pool of prelaunched (initialized) renderers.
class LRURendererCache {
public:
LRURendererCache(content::BrowserContext* browser_context,
size_t max_renderers);
virtual ~LRURendererCache();
// Returns a pre-launched renderer. Returns nullptr if a cached renderer isn't
// available (clients should create their own in this case).
std::unique_ptr<RendererPrelauncher> TakeRendererPrelauncher(
const GURL& page_url);
// Indicate that the renderer for |page_url| is no longer in use. If the total
// number of in-use renderers is less than |max_renderers_|, then we will
// immediately pre-load the renderer for |page_url| since it was recently
// used. This operation may evict a prelaunched renderer to keep the total
// pool size below |max_renderers_|
void ReleaseRendererPrelauncher(const GURL& page_url);
private:
friend class LRURendererCacheTest;
void SetFactoryForTesting(RendererPrelauncherFactory* factory);
void StartNextPrelauncher(const GURL& page_url);
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
// Evict pre-launched renderers so that the total number of in-use and cached
// renderers doesn't exceed |max_renderers_|.
void EvictCache();
content::BrowserContext* const browser_context_;
const size_t max_renderers_;
size_t in_use_count_;
std::list<std::unique_ptr<RendererPrelauncher>> cache_;
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
RendererPrelauncherFactory* factory_for_testing_ = nullptr;
base::WeakPtrFactory<LRURendererCache> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(LRURendererCache);
};
} // namespace chromecast
#endif // CHROMECAST_BROWSER_LRU_RENDERER_CACHE_H_
// Copyright 2018 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 "chromecast/browser/lru_renderer_cache.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "chromecast/browser/renderer_prelauncher.h"
#include "content/public/browser/site_instance.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#define EXPECT_CREATE_AND_PRELAUNCH(ptr, url) \
ptr = new MockPrelauncher(&browser_context_, url); \
EXPECT_CALL(*ptr, Prelaunch()); \
EXPECT_CALL(factory_, Create(&browser_context_, url)) \
.WillOnce(Return(ByMove(std::unique_ptr<MockPrelauncher>(ptr))));
#define EXPECT_EVICTION(ptr) EXPECT_CALL(*ptr, Destroy());
using ::testing::_;
using ::testing::ByMove;
using ::testing::Expectation;
using ::testing::Mock;
using ::testing::Return;
using ::testing::StrictMock;
namespace chromecast {
namespace {
const GURL kUrl1("https://www.one.com");
const GURL kUrl2("https://www.two.com");
const GURL kUrl3("https://www.three.com");
} // namespace
class MockPrelauncher : public RendererPrelauncher {
public:
MockPrelauncher(content::BrowserContext* browser_context,
const GURL& page_url)
: RendererPrelauncher(browser_context, page_url) {}
virtual ~MockPrelauncher() { Destroy(); }
MOCK_METHOD0(Prelaunch, void());
MOCK_METHOD0(Destroy, void());
};
class MockFactory : public RendererPrelauncherFactory {
public:
MOCK_METHOD2(Create,
std::unique_ptr<RendererPrelauncher>(
content::BrowserContext* browser_context,
const GURL& page_url));
};
class LRURendererCacheTest : public testing::Test {
protected:
void SetUp() override {}
void SetFactory() {
DCHECK(lru_cache_);
lru_cache_->SetFactoryForTesting(&factory_);
}
content::TestBrowserThreadBundle threads_;
content::TestBrowserContext browser_context_;
MockFactory factory_;
std::unique_ptr<LRURendererCache> lru_cache_;
};
TEST_F(LRURendererCacheTest, SimpleTakeAndRelease) {
lru_cache_ = std::make_unique<LRURendererCache>(&browser_context_, 1);
SetFactory();
MockPrelauncher* p1;
std::unique_ptr<RendererPrelauncher> taken;
// Don't return a prelauncher the first time, since the cache is empty.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_FALSE(taken);
// Cache: []
// In-use: [ 1 ]
// Releasing the prelauncher will cache it and prelaunch for later use.
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1 ]
// In-use: []
// Get the cached prelauncher.
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_TRUE(taken);
ASSERT_TRUE(taken->IsForURL(kUrl1));
// Cache: [ ]
// In-use: [ 1 ]
// Return the prelauncher again, it should be cached the same as before.
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1 ]
// In-use: []
}
TEST_F(LRURendererCacheTest, SimpleCacheEviction) {
lru_cache_ = std::make_unique<LRURendererCache>(&browser_context_, 1);
SetFactory();
MockPrelauncher* p1;
std::unique_ptr<RendererPrelauncher> taken;
// Fill the cache.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_FALSE(taken);
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1 ]
// In-use: []
// Taking a different prelauncher destroys the cached one.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
EXPECT_EVICTION(p1);
taken = lru_cache_->TakeRendererPrelauncher(kUrl2);
ASSERT_FALSE(taken);
// Cache: [ ]
// In-use: [ 2 ]
}
TEST_F(LRURendererCacheTest, CapacityOne) {
lru_cache_ = std::make_unique<LRURendererCache>(&browser_context_, 1);
SetFactory();
MockPrelauncher* p1;
MockPrelauncher* p2;
std::unique_ptr<RendererPrelauncher> taken;
// Don't return a prelauncher the first time, since the cache is empty.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_FALSE(taken);
// Cache: []
// In-use: [ 1 ]
// Releasing the prelauncher will cache it and prelaunch for later use.
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1 ]
// In-use: []
// Get the cached prelauncher.
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_TRUE(taken);
ASSERT_TRUE(taken->IsForURL(kUrl1));
// Cache: [ ]
// In-use: [ 1 ]
// Return the prelauncher again, it should be cached the same as before.
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1 ]
// In-use: []
// Getting the prelauncher for a non-cached URL will return nullptr. The cache
// will evict 1 to stay below the renderer limit.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
EXPECT_EVICTION(p1);
taken = lru_cache_->TakeRendererPrelauncher(kUrl2);
ASSERT_FALSE(taken);
// Cache: [ ]
// In-use: [ 2 ]
// Return prelauncher 2, it should be cached.
EXPECT_CREATE_AND_PRELAUNCH(p2, kUrl2);
lru_cache_->ReleaseRendererPrelauncher(kUrl2);
threads_.RunUntilIdle();
// Cache: [ 2 ]
// In-use: [ ]
taken = lru_cache_->TakeRendererPrelauncher(kUrl2);
ASSERT_TRUE(taken);
ASSERT_TRUE(taken->IsForURL(kUrl2));
// Cache: [ ]
// In-use: [ 2 ]
// Return prelauncher 2 once more, it will be cached.
EXPECT_CREATE_AND_PRELAUNCH(p2, kUrl2);
lru_cache_->ReleaseRendererPrelauncher(kUrl2);
threads_.RunUntilIdle();
// Cache: [ 2 ]
// In-use: [ ]
// Prelauncher 1 was evicted when 2 was cached. Taking 1 will evict 2.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
EXPECT_EVICTION(p2);
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_FALSE(taken);
// Cache: [ ]
// In-use: [ 1 ]
// Prelauncher 2 was evicted when 1 was taken.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl2);
ASSERT_FALSE(taken);
// Cache: [ ]
// In-use: [ 1, 2 ]
// Returning one of the two in-use pages to the cache won't actually cache it,
// since there's still exactly 1 renderer in-use.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
lru_cache_->ReleaseRendererPrelauncher(kUrl2);
threads_.RunUntilIdle();
// Cache: [ ]
// In-use: [ 1 ]
}
TEST_F(LRURendererCacheTest, CapacityTwo) {
lru_cache_ = std::make_unique<LRURendererCache>(&browser_context_, 2);
SetFactory();
MockPrelauncher* p1;
MockPrelauncher* p2;
std::unique_ptr<RendererPrelauncher> taken;
// Take three renderers.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_FALSE(taken);
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl2);
ASSERT_FALSE(taken);
EXPECT_CALL(factory_, Create(_, _)).Times(0);
taken = lru_cache_->TakeRendererPrelauncher(kUrl3);
ASSERT_FALSE(taken);
// Cache: []
// In-use: [ 1, 2, 3 ]
// Don't cache renderer 3 since there are still 2 in use.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
lru_cache_->ReleaseRendererPrelauncher(kUrl3);
threads_.RunUntilIdle();
// In-use: [ 1, 2 ]
// Fill the cache with remaining 2 renderers.
EXPECT_CREATE_AND_PRELAUNCH(p2, kUrl2);
lru_cache_->ReleaseRendererPrelauncher(kUrl2);
threads_.RunUntilIdle();
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1, 2 ]
// In-use: [ ]
// Cache hit for renderer 1.
taken = lru_cache_->TakeRendererPrelauncher(kUrl1);
ASSERT_TRUE(taken);
ASSERT_TRUE(taken->IsForURL(kUrl1));
// Cache: [ 2 ]
// In-use: [ 1 ]
// Return renderer 1.
EXPECT_CREATE_AND_PRELAUNCH(p1, kUrl1);
lru_cache_->ReleaseRendererPrelauncher(kUrl1);
threads_.RunUntilIdle();
// Cache: [ 1, 2 ]
// In-use: [ ]
// Evict the least-recently cached renderer (2).
EXPECT_CALL(factory_, Create(_, _)).Times(0);
EXPECT_EVICTION(p2);
taken = lru_cache_->TakeRendererPrelauncher(kUrl3);
ASSERT_FALSE(taken);
// Cache: [ 1 ]
// In-use: [ 3 ]
// Getting renderer 2 will fail since it's no long cached. This will evict
// renderer 1.
EXPECT_CALL(factory_, Create(_, _)).Times(0);
EXPECT_EVICTION(p1);
taken = lru_cache_->TakeRendererPrelauncher(kUrl2);
ASSERT_FALSE(taken);
// Cache: [ ]
// In-use: [ 2, 3 ]
}
} // namespace chromecast
...@@ -33,6 +33,12 @@ void RendererPrelauncher::Prelaunch() { ...@@ -33,6 +33,12 @@ void RendererPrelauncher::Prelaunch() {
rph->Init(); rph->Init();
} }
bool RendererPrelauncher::IsForURL(const GURL& gurl) const {
if (!site_instance())
return gurl_ == gurl;
return site_instance() == site_instance()->GetRelatedSiteInstance(gurl);
}
// We don't process any IPC messages, but we do register as an IPC receiver to // We don't process any IPC messages, but we do register as an IPC receiver to
// keep the RenderProcessHost alive. // keep the RenderProcessHost alive.
bool RendererPrelauncher::OnMessageReceived(const IPC::Message& message) { bool RendererPrelauncher::OnMessageReceived(const IPC::Message& message) {
......
...@@ -28,9 +28,11 @@ class RendererPrelauncher : private IPC::Listener { ...@@ -28,9 +28,11 @@ class RendererPrelauncher : private IPC::Listener {
const GURL& gurl); const GURL& gurl);
~RendererPrelauncher() override; ~RendererPrelauncher() override;
void Prelaunch(); virtual void Prelaunch();
bool IsForURL(const GURL& gurl) const;
scoped_refptr<content::SiteInstance> site_instance() { const GURL& url() const { return gurl_; }
scoped_refptr<content::SiteInstance> site_instance() const {
return site_instance_; return site_instance_;
} }
...@@ -40,7 +42,7 @@ class RendererPrelauncher : private IPC::Listener { ...@@ -40,7 +42,7 @@ class RendererPrelauncher : private IPC::Listener {
content::BrowserContext* const browser_context_; content::BrowserContext* const browser_context_;
scoped_refptr<content::SiteInstance> site_instance_; scoped_refptr<content::SiteInstance> site_instance_;
GURL gurl_; const GURL gurl_;
int32_t rph_routing_id_; int32_t rph_routing_id_;
DISALLOW_COPY_AND_ASSIGN(RendererPrelauncher); DISALLOW_COPY_AND_ASSIGN(RendererPrelauncher);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment