Skip to content

Commit

Permalink
Keep strong references to the resources in the memory cache
Browse files Browse the repository at this point in the history
The CL adds an LRU cache to the blink memory cache with strong
references to the resources. The strong references will be removed when

* The freshness timeout of the resource has expired.
* Memory pressure is signaled. All the strong references are removed.
* After 5 minute timeout after the original page is unloaded.

The design doc can be found at
https://docs.google.com/document/d/1jcdXkoFBbdxmp_EYIrrOJvEgYd0hGBAr4Nbno7KWnIs/edit#heading=h.3esx691s5nmc

Bug: 1409349
Change-Id: I1a3ce9eb46512ac3cb2747594755542d42630303
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4190775
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Hiroshige Hayashizaki <hiroshige@chromium.org>
Commit-Queue: Jiacheng Guo <gjc@google.com>
Cr-Commit-Position: refs/heads/main@{#1117940}
  • Loading branch information
Jiacheng Guo authored and Chromium LUCI CQ committed Mar 16, 2023
1 parent 28d0c35 commit 737f399
Show file tree
Hide file tree
Showing 15 changed files with 394 additions and 12 deletions.
12 changes: 12 additions & 0 deletions third_party/blink/common/features.cc
Expand Up @@ -1714,5 +1714,17 @@ BASE_FEATURE(kURLSetPortCheckOverflow,
"URLSetPortCheckOverflow",
base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kMemoryCacheStrongReference,
"MemoryCacheStrongReference",
base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kMemoryCacheStrongReferenceSingleUnload,
"MemoryCacheStrongReferenceSingleUnload",
base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kMemoryCacheStrongReferenceFilterImages,
"MemoryCacheStrongReferenceFilterImages",
base::FEATURE_DISABLED_BY_DEFAULT);

} // namespace features
} // namespace blink
14 changes: 14 additions & 0 deletions third_party/blink/public/common/features.h
Expand Up @@ -1048,6 +1048,20 @@ BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
// See https://crbug.com/1416017
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kURLSetPortCheckOverflow);

// Keep strong references in the blink memory cache to maximize resource reuse.
// See https://crbug.com/1409349.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kMemoryCacheStrongReference);

// Save only one unloaded page's resources in the memory cache.
// See https://crbug.com/1409349.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kMemoryCacheStrongReferenceSingleUnload);

// Save strong references only for fonts, stylesheets and scripts
// See https://crbug.com/1409349.
BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
kMemoryCacheStrongReferenceFilterImages);

} // namespace features
} // namespace blink

Expand Down
24 changes: 24 additions & 0 deletions third_party/blink/renderer/core/frame/frame.cc
Expand Up @@ -65,6 +65,7 @@
#include "third_party/blink/renderer/platform/instrumentation/instance_counters.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"

namespace blink {
Expand Down Expand Up @@ -866,4 +867,27 @@ void Frame::DetachFromParent() {
Parent()->RemoveChild(this);
}

HeapVector<Member<Resource>> Frame::AllResourcesUnderFrame() {
DCHECK(base::FeatureList::IsEnabled(features::kMemoryCacheStrongReference));

HeapVector<Member<Resource>> resources;
if (IsLocalFrame()) {
if (auto* this_local_frame = DynamicTo<LocalFrame>(this)) {
HeapHashSet<Member<Resource>> local_frame_resources =
this_local_frame->GetDocument()
->Fetcher()
->MoveResourceStrongReferences();
for (Resource* resource : local_frame_resources) {
resources.push_back(resource);
}
}
}

for (Frame* child = Tree().FirstChild(); child;
child = child->Tree().NextSibling()) {
resources.AppendVector(child->AllResourcesUnderFrame());
}
return resources;
}

} // namespace blink
5 changes: 5 additions & 0 deletions third_party/blink/renderer/core/frame/frame.h
Expand Up @@ -57,6 +57,7 @@
#include "third_party/blink/renderer/core/loader/frame_loader_types.h"
#include "third_party/blink/renderer/core/page/frame_tree.h"
#include "third_party/blink/renderer/platform/graphics/touch_action.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
Expand All @@ -79,6 +80,7 @@ class HTMLFrameOwnerElement;
class LayoutEmbeddedContent;
class LocalFrame;
class Page;
class Resource;
class SecurityContext;
class Settings;
class WindowProxy;
Expand Down Expand Up @@ -441,6 +443,9 @@ class CORE_EXPORT Frame : public GarbageCollected<Frame> {
// on a detached frame.
absl::optional<mojom::blink::FencedFrameMode> GetFencedFrameMode() const;

// Returns all the resources under the frame tree of this node.
HeapVector<Member<Resource>> AllResourcesUnderFrame();

protected:
// |inheriting_agent_factory| should basically be set to the parent frame or
// opener's WindowAgentFactory. Pass nullptr if the frame is isolated from
Expand Down
8 changes: 8 additions & 0 deletions third_party/blink/renderer/core/loader/frame_loader.cc
Expand Up @@ -116,6 +116,7 @@
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/instance_counters.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
Expand Down Expand Up @@ -1004,6 +1005,7 @@ void FrameLoader::CommitNavigation(
DCHECK(document_loader_);
DCHECK(frame_->GetDocument());
DCHECK(Client()->HasWebView());

if (!frame_->IsNavigationAllowed() ||
frame_->GetDocument()->PageDismissalEventBeingDispatched() !=
Document::kNoDismissal) {
Expand Down Expand Up @@ -1568,6 +1570,12 @@ bool FrameLoader::ShouldClose(bool is_reload) {
descendant_frame->GetDocument()->BeforeUnloadDoneWillUnload();
}

if (!frame_->IsDetached() && frame_->IsOutermostMainFrame() &&
base::FeatureList::IsEnabled(features::kMemoryCacheStrongReference)) {
MemoryCache::Get()->SavePageResourceStrongReferences(
frame_->AllResourcesUnderFrame());
}

if (!is_reload) {
// Records only when a non-reload navigation occurs.
base::UmaHistogramMediumTimes(
Expand Down
Expand Up @@ -26,6 +26,7 @@
#include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
#include "third_party/blink/renderer/platform/testing/mock_context_lifecycle_notifier.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
#include "third_party/blink/renderer/platform/testing/url_loader_mock_factory.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
Expand All @@ -51,7 +52,19 @@ class CacheAwareFontResourceTest : public FontResourceTest {
base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests if ResourceFetcher works fine with FontResource that requires defered
class FontResourceStrongReferenceTest : public FontResourceTest {
public:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
features::kMemoryCacheStrongReference);
FontResourceTest::SetUp();
}

private:
base::test::ScopedFeatureList scoped_feature_list_;
};

// Tests if ResourceFetcher works fine with FontResource that requires deferred
// loading supports.
TEST_F(FontResourceTest,
ResourceFetcherRevalidateDeferedResourceFromTwoInitiators) {
Expand Down Expand Up @@ -279,4 +292,67 @@ TEST_F(CacheAwareFontResourceTest, CacheAwareFontLoading) {
MemoryCache::Get()->Remove(&resource);
}

TEST_F(FontResourceStrongReferenceTest, FontResourceStrongReference) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
MockFetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
ResourceFetcherInit(properties->MakeDetachable(), context,
base::MakeRefCounted<scheduler::FakeTaskRunner>(),
base::MakeRefCounted<scheduler::FakeTaskRunner>(),
MakeGarbageCollected<TestLoaderFactory>(),
MakeGarbageCollected<MockContextLifecycleNotifier>(),
nullptr /* back_forward_cache_loader_helper */));

KURL url_font("http://127.0.0.1:8000/font.ttf");
ResourceResponse response_font(url_font);
response_font.SetHttpStatusCode(200);
response_font.SetHttpHeaderField(http_names::kCacheControl, "max-age=3600");
url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
url_font, "", WrappedResourceResponse(response_font));

FetchParameters fetch_params =
FetchParameters::CreateForTest(ResourceRequest(url_font));
Resource* resource = FontResource::Fetch(fetch_params, fetcher, nullptr);
fetcher->StartLoad(resource);
url_test_helpers::ServeAsynchronousRequests();
ASSERT_TRUE(resource);

auto strong_referenced_resources = fetcher->MoveResourceStrongReferences();
ASSERT_EQ(strong_referenced_resources.size(), 1u);

strong_referenced_resources = fetcher->MoveResourceStrongReferences();
ASSERT_EQ(strong_referenced_resources.size(), 0u);
}

TEST_F(FontResourceStrongReferenceTest, FollowCacheControl) {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
MockFetchContext* context = MakeGarbageCollected<MockFetchContext>();
auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
ResourceFetcherInit(properties->MakeDetachable(), context,
base::MakeRefCounted<scheduler::FakeTaskRunner>(),
base::MakeRefCounted<scheduler::FakeTaskRunner>(),
MakeGarbageCollected<TestLoaderFactory>(),
MakeGarbageCollected<MockContextLifecycleNotifier>(),
nullptr /* back_forward_cache_loader_helper */));

KURL url_font_no_store("http://127.0.0.1:8000/font_no_store.ttf");
ResourceResponse response_font_no_store(url_font_no_store);
response_font_no_store.SetHttpStatusCode(200);
response_font_no_store.SetHttpHeaderField(http_names::kCacheControl,
"no-cache, no-store");
url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
url_font_no_store, "", WrappedResourceResponse(response_font_no_store));

FetchParameters fetch_params_no_store =
FetchParameters::CreateForTest(ResourceRequest(url_font_no_store));
Resource* resource_no_store =
FontResource::Fetch(fetch_params_no_store, fetcher, nullptr);
fetcher->StartLoad(resource_no_store);
url_test_helpers::ServeAsynchronousRequests();
ASSERT_TRUE(resource_no_store);

auto strong_referenced_resources = fetcher->MoveResourceStrongReferences();
ASSERT_EQ(strong_referenced_resources.size(), 0u);
}

} // namespace blink
49 changes: 44 additions & 5 deletions third_party/blink/renderer/platform/loader/fetch/memory_cache.cc
Expand Up @@ -25,9 +25,12 @@
#include <utility>

#include "base/auto_reset.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
Expand All @@ -46,7 +49,10 @@ static const unsigned kCDefaultCacheCapacity = 8192 * 1024;
static const base::TimeDelta kCMinDelayBeforeLiveDecodedPrune =
base::Seconds(1);
static const base::TimeDelta kCMaxPruneDeferralDelay = base::Milliseconds(500);
static const base::TimeDelta kCUnloadPageResourceSaveTime = base::Minutes(5);

static constexpr char kPageSavedResourceStrongReferenceSize[] =
"Blink.MemoryCache.PageSavedResourceStrongReferenceSize";
// Percentage of capacity toward which we prune, to avoid immediately pruning
// again.
static const float kCTargetPrunePercentage = .95f;
Expand Down Expand Up @@ -84,9 +90,7 @@ MemoryCache* MemoryCache::Get() {

MemoryCache::MemoryCache(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: in_prune_resources_(false),
prune_pending_(false),
capacity_(kCDefaultCacheCapacity),
: capacity_(kCDefaultCacheCapacity),
delay_before_live_decoded_prune_(kCMinDelayBeforeLiveDecodedPrune),
size_(0),
task_runner_(std::move(task_runner)) {
Expand All @@ -99,6 +103,7 @@ MemoryCache::~MemoryCache() = default;

void MemoryCache::Trace(Visitor* visitor) const {
visitor->Trace(resource_maps_);
visitor->Trace(saved_page_resources_);
MemoryCacheDumpClient::Trace(visitor);
MemoryPressureListener::Trace(visitor);
}
Expand Down Expand Up @@ -265,6 +270,12 @@ void MemoryCache::PruneResources(PruneStrategy strategy) {
size_t target_size =
static_cast<size_t>(size_limit * kCTargetPrunePercentage);

// Release the strong referenced cached objects
// TODO(crbug.com/1409349): Filter page loading metrics when prune happens.
if (base::FeatureList::IsEnabled(
blink::features::kMemoryCacheStrongReference)) {
saved_page_resources_.clear();
}
for (const auto& resource_map_iter : resource_maps_) {
for (const auto& resource_iter : *resource_map_iter.value) {
Resource* resource = resource_iter.value->GetResource();
Expand All @@ -273,11 +284,13 @@ void MemoryCache::PruneResources(PruneStrategy strategy) {
// Check to see if the remaining resources are too new to prune.
if (strategy == kAutomaticPrune &&
prune_frame_time_stamp_.since_origin() <
delay_before_live_decoded_prune_)
delay_before_live_decoded_prune_) {
continue;
}
resource->Prune();
if (size_ <= target_size)
if (size_ <= target_size) {
return;
}
}
}
}
Expand Down Expand Up @@ -468,4 +481,30 @@ void MemoryCache::OnMemoryPressure(
PruneAll();
}

void MemoryCache::SavePageResourceStrongReferences(
HeapVector<Member<Resource>> resources) {
DCHECK(base::FeatureList::IsEnabled(features::kMemoryCacheStrongReference));
if (base::FeatureList::IsEnabled(
features::kMemoryCacheStrongReferenceSingleUnload)) {
saved_page_resources_.clear();
}
base::UmaHistogramCustomCounts(kPageSavedResourceStrongReferenceSize,
resources.size(), 0, 200, 50);
saved_page_token_++;
saved_page_resources_.insert(
saved_page_token_,
MakeGarbageCollected<HeapVector<Member<Resource>>>(std::move(resources)));
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&MemoryCache::RemovePageResourceStrongReference,
WrapWeakPersistent(this), saved_page_token_),
kCUnloadPageResourceSaveTime);
}

void MemoryCache::RemovePageResourceStrongReference(uint32_t saved_page_token) {
DCHECK(base::FeatureList::IsEnabled(features::kMemoryCacheStrongReference));

saved_page_resources_.erase(saved_page_token);
}

} // namespace blink
19 changes: 17 additions & 2 deletions third_party/blink/renderer/platform/loader/fetch/memory_cache.h
Expand Up @@ -157,6 +157,11 @@ class PLATFORM_EXPORT MemoryCache final : public GarbageCollected<MemoryCache>,

void UpdateFramePaintTimestamp();

// Called by the loader to notify that a new page is being loaded.
// The strong references the memory cache is holding for the current page
// will be moved to the previous generation.
void SavePageResourceStrongReferences(HeapVector<Member<Resource>> resources);

// Take memory usage snapshot for tracing.
bool OnMemoryDump(WebMemoryDumpLevelOfDetail, WebProcessMemoryDump*) override;

Expand Down Expand Up @@ -186,8 +191,10 @@ class PLATFORM_EXPORT MemoryCache final : public GarbageCollected<MemoryCache>,
void PruneResources(PruneStrategy);
void PruneNow(PruneStrategy);

bool in_prune_resources_;
bool prune_pending_;
void RemovePageResourceStrongReference(uint32_t saved_page_token);

bool in_prune_resources_ = false;
bool prune_pending_ = false;
base::TimeDelta max_prune_deferral_delay_;
base::TimeTicks prune_time_stamp_;
base::TimeTicks prune_frame_time_stamp_;
Expand All @@ -200,9 +207,17 @@ class PLATFORM_EXPORT MemoryCache final : public GarbageCollected<MemoryCache>,
// The number of bytes currently consumed by resources in the cache.
size_t size_;

// The size of strong reference to resources is not limited.
// The strong references will be removed when memory pressure is signaled.
HeapHashMap<uint32_t, Member<HeapVector<Member<Resource>>>>
saved_page_resources_;
uint32_t saved_page_token_ = 0;

scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

friend class MemoryCacheTest;
FRIEND_TEST_ALL_PREFIXES(MemoryCacheStrongReferenceTest, ResourceTimeout);
FRIEND_TEST_ALL_PREFIXES(MemoryCacheStrongReferenceTest, SaveSinglePage);
};

// Sets the global cache, used to swap in a test instance. Returns the old
Expand Down

0 comments on commit 737f399

Please sign in to comment.