Commit 5f32298d authored by erg's avatar erg Committed by Commit bot

Add a DiscardableMemoryAllocator to html_viewer.

Recent changes to skia now require this object, and crash if this
factory isn't supplied. We create the minimal implementation to get a
Google search working in html_viewer. This allocator will free
unlocked chunks once it has reached its memory threshold.

We're starting with a soft limit of 20 megabytes. We'll adjust this
based on observed performance.

Review URL: https://codereview.chromium.org/1016493002

Cr-Commit-Position: refs/heads/master@{#321461}
parent e6297d3e
...@@ -40,6 +40,8 @@ source_set("lib") { ...@@ -40,6 +40,8 @@ source_set("lib") {
"blink_resource_constants.h", "blink_resource_constants.h",
"blink_url_request_type_converters.cc", "blink_url_request_type_converters.cc",
"blink_url_request_type_converters.h", "blink_url_request_type_converters.h",
"discardable_memory_allocator.cc",
"discardable_memory_allocator.h",
"html_document.cc", "html_document.cc",
"html_document.h", "html_document.h",
"mojo_blink_platform_impl.cc", "mojo_blink_platform_impl.cc",
...@@ -176,6 +178,7 @@ test("tests") { ...@@ -176,6 +178,7 @@ test("tests") {
output_name = "html_viewer_unittests" output_name = "html_viewer_unittests"
sources = [ sources = [
"ax_provider_impl_unittest.cc", "ax_provider_impl_unittest.cc",
"discardable_memory_allocator_unittest.cc",
] ]
deps = [ deps = [
":lib", ":lib",
......
// Copyright 2015 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 "mojo/services/html_viewer/discardable_memory_allocator.h"
#include "base/memory/discardable_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/stl_util.h"
namespace html_viewer {
// Represents an actual memory chunk. This is an object owned by
// DiscardableMemoryAllocator. DiscardableMemoryChunkImpl are passed to the
// rest of the program, and access this memory through a weak ptr.
class DiscardableMemoryAllocator::OwnedMemoryChunk {
public:
OwnedMemoryChunk(size_t size, DiscardableMemoryAllocator* allocator)
: is_locked_(true),
size_(size),
memory_(new uint8_t[size]),
allocator_(allocator),
weak_factory_(this) {}
~OwnedMemoryChunk() {}
void Lock() {
DCHECK(!is_locked_);
is_locked_ = true;
allocator_->NotifyLocked(unlocked_position_);
}
void Unlock() {
DCHECK(is_locked_);
is_locked_ = false;
unlocked_position_ = allocator_->NotifyUnlocked(this);
}
bool is_locked() const { return is_locked_; }
size_t size() const { return size_; }
void* Memory() const {
DCHECK(is_locked_);
return memory_.get();
}
base::WeakPtr<OwnedMemoryChunk> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
bool is_locked_;
size_t size_;
scoped_ptr<uint8_t[]> memory_;
DiscardableMemoryAllocator* allocator_;
std::list<OwnedMemoryChunk*>::iterator unlocked_position_;
base::WeakPtrFactory<OwnedMemoryChunk> weak_factory_;
DISALLOW_IMPLICIT_CONSTRUCTORS(OwnedMemoryChunk);
};
namespace {
// Interface to the rest of the program. These objects are owned outside of the
// allocator and wrap a weak ptr.
class DiscardableMemoryChunkImpl : public base::DiscardableMemory {
public:
explicit DiscardableMemoryChunkImpl(
base::WeakPtr<DiscardableMemoryAllocator::OwnedMemoryChunk> chunk)
: memory_chunk_(chunk) {}
~DiscardableMemoryChunkImpl() override {
// Either the memory chunk is invalid (because the backing has gone away),
// or the memory chunk is unlocked (because leaving the chunk locked once
// we deallocate means the chunk will never get cleaned up).
DCHECK(!memory_chunk_ || !memory_chunk_->is_locked());
}
// Overridden from DiscardableMemoryChunk:
bool Lock() override {
if (!memory_chunk_)
return false;
memory_chunk_->Lock();
return true;
}
void Unlock() override {
DCHECK(memory_chunk_);
memory_chunk_->Unlock();
}
void* Memory() const override {
if (memory_chunk_)
return memory_chunk_->Memory();
return nullptr;
}
private:
base::WeakPtr<DiscardableMemoryAllocator::OwnedMemoryChunk> memory_chunk_;
DISALLOW_IMPLICIT_CONSTRUCTORS(DiscardableMemoryChunkImpl);
};
} // namespace
DiscardableMemoryAllocator::DiscardableMemoryAllocator(
size_t desired_max_memory)
: desired_max_memory_(desired_max_memory),
total_live_memory_(0u),
locked_chunks_(0) {
}
DiscardableMemoryAllocator::~DiscardableMemoryAllocator() {
DCHECK_EQ(0, locked_chunks_);
STLDeleteElements(&live_unlocked_chunks_);
}
scoped_ptr<base::DiscardableMemory>
DiscardableMemoryAllocator::AllocateLockedDiscardableMemory(size_t size) {
OwnedMemoryChunk* chunk = new OwnedMemoryChunk(size, this);
total_live_memory_ += size;
locked_chunks_++;
// Go through the list of unlocked live chunks starting from the least
// recently used, freeing as many as we can until we get our size under the
// desired maximum.
auto it = live_unlocked_chunks_.begin();
while (total_live_memory_ > desired_max_memory_ &&
it != live_unlocked_chunks_.end()) {
total_live_memory_ -= (*it)->size();
delete *it;
it = live_unlocked_chunks_.erase(it);
}
return make_scoped_ptr(new DiscardableMemoryChunkImpl(chunk->GetWeakPtr()));
}
std::list<DiscardableMemoryAllocator::OwnedMemoryChunk*>::iterator
DiscardableMemoryAllocator::NotifyUnlocked(
DiscardableMemoryAllocator::OwnedMemoryChunk* chunk) {
locked_chunks_--;
return live_unlocked_chunks_.insert(live_unlocked_chunks_.end(), chunk);
}
void DiscardableMemoryAllocator::NotifyLocked(
std::list<OwnedMemoryChunk*>::iterator it) {
locked_chunks_++;
live_unlocked_chunks_.erase(it);
}
} // namespace html_viewer
// Copyright 2015 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 MOJO_SERVICES_HTML_VIEWER_DISCARDABLE_MEMORY_ALLOCATOR_H_
#define MOJO_SERVICES_HTML_VIEWER_DISCARDABLE_MEMORY_ALLOCATOR_H_
#include <list>
#include "base/memory/discardable_memory_allocator.h"
namespace html_viewer {
// A discarable memory allocator which will unallocate chunks on new
// allocations.
class DiscardableMemoryAllocator : public base::DiscardableMemoryAllocator {
public:
class OwnedMemoryChunk;
explicit DiscardableMemoryAllocator(size_t desired_max_memory);
~DiscardableMemoryAllocator() override;
// Overridden from DiscardableMemoryAllocator:
scoped_ptr<base::DiscardableMemory> AllocateLockedDiscardableMemory(
size_t size) override;
private:
friend class OwnedMemoryChunk;
// Called by OwnedMemoryChunks when they are unlocked. This puts them at the
// end of the live_unlocked_chunks_ list and passes an iterator to their
// position in the unlocked chunk list.
std::list<OwnedMemoryChunk*>::iterator NotifyUnlocked(
OwnedMemoryChunk* chunk);
// Called by OwnedMemoryChunks when they are locked. This removes the passed
// in unlocked chunk list.
void NotifyLocked(std::list<OwnedMemoryChunk*>::iterator it);
// The amount of memory we can allocate before we try to free unlocked
// chunks. We can go over this amount if all callers keep their discardable
// chunks locked.
const size_t desired_max_memory_;
// A count of the sum of memory. Used to trigger discarding the oldest
// memory.
size_t total_live_memory_;
// The number of locked chunks.
int locked_chunks_;
// A linked list of unlocked allocated chunks so that the tail is most
// recently accessed chunks.
std::list<OwnedMemoryChunk*> live_unlocked_chunks_;
DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryAllocator);
};
} // namespace html_viewer
#endif // MOJO_SERVICES_HTML_VIEWER_DISCARDABLE_MEMORY_ALLOCATOR_H_
// Copyright 2015 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 "mojo/services/html_viewer/discardable_memory_allocator.h"
#include "base/memory/discardable_memory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace html_viewer {
namespace {
const size_t kOneKilobyte = 1024;
const size_t kAlmostOneMegabyte = 1023 * kOneKilobyte;
const size_t kOneMegabyte = 1024 * kOneKilobyte;
TEST(DiscardableMemoryAllocator, Basic) {
scoped_ptr<base::DiscardableMemory> chunk;
{
DiscardableMemoryAllocator allocator(kOneMegabyte);
// Make sure the chunk is locked when allocated. In debug mode, we will
// dcheck.
chunk = allocator.AllocateLockedDiscardableMemory(kOneKilobyte);
chunk->Unlock();
// Make sure we can lock a chunk.
EXPECT_TRUE(chunk->Lock());
chunk->Unlock();
}
// The chunk's backing should have disappeared with the allocator.
EXPECT_FALSE(chunk->Lock());
}
TEST(DiscardableMemoryAllocator, DiscardChunks) {
DiscardableMemoryAllocator allocator(kOneMegabyte);
scoped_ptr<base::DiscardableMemory> chunk_to_remove =
allocator.AllocateLockedDiscardableMemory(kAlmostOneMegabyte);
chunk_to_remove->Unlock();
// Allocating a second chunk should deallocate the first one due to memory
// pressure, since we only have one megabyte available.
scoped_ptr<base::DiscardableMemory> chunk_to_keep =
allocator.AllocateLockedDiscardableMemory(kAlmostOneMegabyte);
// Fail to get a lock because allocating the second chunk removed the first.
EXPECT_FALSE(chunk_to_remove->Lock());
chunk_to_keep->Unlock();
}
TEST(DiscardableMemoryAllocator, DontDiscardLiveChunks) {
DiscardableMemoryAllocator allocator(kOneMegabyte);
scoped_ptr<base::DiscardableMemory> chunk_one =
allocator.AllocateLockedDiscardableMemory(kAlmostOneMegabyte);
scoped_ptr<base::DiscardableMemory> chunk_two =
allocator.AllocateLockedDiscardableMemory(kAlmostOneMegabyte);
scoped_ptr<base::DiscardableMemory> chunk_three =
allocator.AllocateLockedDiscardableMemory(kAlmostOneMegabyte);
// These accesses will fail if the underlying weak ptr has been deallocated.
EXPECT_NE(nullptr, chunk_one->Memory());
EXPECT_NE(nullptr, chunk_two->Memory());
EXPECT_NE(nullptr, chunk_three->Memory());
chunk_one->Unlock();
chunk_two->Unlock();
chunk_three->Unlock();
}
} // namespace
} // namespace html_viewer
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "gin/public/isolate_holder.h" #include "gin/public/isolate_holder.h"
#include "mojo/application/application_runner_chromium.h" #include "mojo/application/application_runner_chromium.h"
#include "mojo/services/html_viewer/discardable_memory_allocator.h"
#include "mojo/services/html_viewer/html_document.h" #include "mojo/services/html_viewer/html_document.h"
#include "mojo/services/html_viewer/mojo_blink_platform_impl.h" #include "mojo/services/html_viewer/mojo_blink_platform_impl.h"
#include "mojo/services/html_viewer/webmediaplayer_factory.h" #include "mojo/services/html_viewer/webmediaplayer_factory.h"
...@@ -59,6 +60,8 @@ const char kDisableEncryptedMedia[] = "disable-encrypted-media"; ...@@ -59,6 +60,8 @@ const char kDisableEncryptedMedia[] = "disable-encrypted-media";
// Prevents creation of any output surface. // Prevents creation of any output surface.
const char kIsHeadless[] = "is-headless"; const char kIsHeadless[] = "is-headless";
size_t kDesiredMaxMemory = 20 * 1024 * 1024;
class HTMLViewer; class HTMLViewer;
class HTMLViewerApplication : public mojo::Application { class HTMLViewerApplication : public mojo::Application {
...@@ -161,13 +164,18 @@ class ContentHandlerImpl : public mojo::InterfaceImpl<ContentHandler> { ...@@ -161,13 +164,18 @@ class ContentHandlerImpl : public mojo::InterfaceImpl<ContentHandler> {
class HTMLViewer : public mojo::ApplicationDelegate, class HTMLViewer : public mojo::ApplicationDelegate,
public mojo::InterfaceFactory<ContentHandler> { public mojo::InterfaceFactory<ContentHandler> {
public: public:
HTMLViewer() : compositor_thread_("compositor thread") {} HTMLViewer()
: discardable_memory_allocator_(kDesiredMaxMemory),
compositor_thread_("compositor thread") {}
~HTMLViewer() override { blink::shutdown(); } ~HTMLViewer() override { blink::shutdown(); }
private: private:
// Overridden from ApplicationDelegate: // Overridden from ApplicationDelegate:
void Initialize(mojo::ApplicationImpl* app) override { void Initialize(mojo::ApplicationImpl* app) override {
base::DiscardableMemoryAllocator::SetInstance(
&discardable_memory_allocator_);
blink_platform_.reset(new MojoBlinkPlatformImpl(app)); blink_platform_.reset(new MojoBlinkPlatformImpl(app));
#if defined(V8_USE_EXTERNAL_STARTUP_DATA) #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
// Note: this requires file system access. // Note: this requires file system access.
...@@ -234,6 +242,14 @@ class HTMLViewer : public mojo::ApplicationDelegate, ...@@ -234,6 +242,14 @@ class HTMLViewer : public mojo::ApplicationDelegate,
&request); &request);
} }
// Skia requires that we have one of these. Unlike the one used in chrome,
// this doesn't use purgable shared memory. Instead, it tries to free the
// oldest unlocked chunks on allocation.
//
// TODO(erg): In the long run, delete this allocator and get the real shared
// memory based purging allocator working here.
DiscardableMemoryAllocator discardable_memory_allocator_;
scoped_ptr<MojoBlinkPlatformImpl> blink_platform_; scoped_ptr<MojoBlinkPlatformImpl> blink_platform_;
base::Thread compositor_thread_; base::Thread compositor_thread_;
scoped_ptr<WebMediaPlayerFactory> web_media_player_factory_; scoped_ptr<WebMediaPlayerFactory> web_media_player_factory_;
......
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