Commit 3558d80a authored by Thiabaud Engelbrecht's avatar Thiabaud Engelbrecht Committed by Commit Bot

Proactively purging discardable memory on Desktop

This CL adds a way for ClientDiscardableSharedMemoryManager to discard
unlocked discardable memory locally. To achieve this, it keeps track
of all locked and unlocked, unpurged instances. These are then purged
all at once when ReleaseFreeMemory() is called.

This is guarded by a new feature which is disabled by default, and as
such there is no behaviour change by default.

Bug: 1109209
Change-Id: I7755d714ff354e831557627fda401751384392b8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2321400
Commit-Queue: Thiabaud Engelbrecht <thiabaud@google.com>
Reviewed-by: default avatarPeng Huang <penghuang@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798630}
parent 1274f8ba
...@@ -236,6 +236,7 @@ test("components_unittests") { ...@@ -236,6 +236,7 @@ test("components_unittests") {
"//components/content_settings/browser:unit_tests", "//components/content_settings/browser:unit_tests",
"//components/contextual_search/core:unit_tests", "//components/contextual_search/core:unit_tests",
"//components/data_use_measurement/core:unit_tests", "//components/data_use_measurement/core:unit_tests",
"//components/discardable_memory/client:unit_tests",
"//components/discardable_memory/common:unit_tests", "//components/discardable_memory/common:unit_tests",
"//components/discardable_memory/service:unit_tests", "//components/discardable_memory/service:unit_tests",
"//components/dom_distiller/content/browser:unit_tests", "//components/dom_distiller/content/browser:unit_tests",
......
...@@ -21,3 +21,15 @@ component("client") { ...@@ -21,3 +21,15 @@ component("client") {
"//components/discardable_memory/public/mojom", "//components/discardable_memory/public/mojom",
] ]
} }
source_set("unit_tests") {
testonly = true
sources = [ "client_discardable_shared_memory_manager_unittest.cc" ]
deps = [
":client",
"//base",
"//testing/gtest",
]
}
...@@ -4,14 +4,12 @@ ...@@ -4,14 +4,12 @@
#include "components/discardable_memory/client/client_discardable_shared_memory_manager.h" #include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
#include <inttypes.h>
#include <algorithm> #include <algorithm>
#include <memory>
#include <utility> #include <utility>
#include "base/atomic_sequence_num.h" #include "base/atomic_sequence_num.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/feature_list.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/discardable_memory.h" #include "base/memory/discardable_memory.h"
#include "base/memory/discardable_shared_memory.h" #include "base/memory/discardable_shared_memory.h"
...@@ -34,6 +32,13 @@ namespace { ...@@ -34,6 +32,13 @@ namespace {
// Global atomic to generate unique discardable shared memory IDs. // Global atomic to generate unique discardable shared memory IDs.
base::AtomicSequenceNumber g_next_discardable_shared_memory_id; base::AtomicSequenceNumber g_next_discardable_shared_memory_id;
// This controls whether unlocked memory is released when |ReleaseFreeMemory| is
// called. Enabling this causes |ReleaseFreeMemory| to release all
// unlocked memory instances, as well as release all free memory (as opposed to
// merely releasing all free memory).
const base::Feature kPurgeUnlockedMemory{"PurgeUnlockedMemory",
base::FEATURE_DISABLED_BY_DEFAULT};
size_t GetDefaultAllocationSize() { size_t GetDefaultAllocationSize() {
const size_t kOneMegabyteInBytes = 1024 * 1024; const size_t kOneMegabyteInBytes = 1024 * 1024;
...@@ -62,80 +67,108 @@ size_t GetDefaultAllocationSize() { ...@@ -62,80 +67,108 @@ size_t GetDefaultAllocationSize() {
#endif #endif
} }
class DiscardableMemoryImpl : public base::DiscardableMemory { void InitManagerMojoOnIO(
public: mojo::Remote<mojom::DiscardableSharedMemoryManager>* manager_mojo,
DiscardableMemoryImpl(ClientDiscardableSharedMemoryManager* manager, mojo::PendingRemote<mojom::DiscardableSharedMemoryManager> remote) {
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) manager_mojo->Bind(std::move(remote));
: manager_(manager), span_(std::move(span)), is_locked_(true) {} }
~DiscardableMemoryImpl() override { void DeletedDiscardableSharedMemoryOnIO(
if (is_locked_) mojo::Remote<mojom::DiscardableSharedMemoryManager>* manager_mojo,
manager_->UnlockSpan(span_.get()); int32_t id) {
(*manager_mojo)->DeletedDiscardableSharedMemory(id);
}
manager_->ReleaseSpan(std::move(span_)); } // namespace
}
// Overridden from base::DiscardableMemory: ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::
bool Lock() override { DiscardableMemoryImpl(
ClientDiscardableSharedMemoryManager* manager,
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
: manager_(manager), span_(std::move(span)), is_locked_(true) {
DCHECK_NE(manager, nullptr);
}
ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::
~DiscardableMemoryImpl() {
base::AutoLock lock(manager_->GetLock());
if (!span_) {
DCHECK(!is_locked_); DCHECK(!is_locked_);
return;
}
if (is_locked_)
manager_->UnlockSpan(span_.get());
if (!manager_->LockSpan(span_.get())) manager_->ReleaseMemory(this, std::move(span_));
return false; }
is_locked_ = true; bool ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::Lock() {
return true; base::AutoLock lock(manager_->GetLock());
} DCHECK(!is_locked_);
void Unlock() override {
DCHECK(is_locked_);
manager_->UnlockSpan(span_.get()); if (!span_)
is_locked_ = false; return false;
}
void* data() const override {
DCHECK(is_locked_);
return reinterpret_cast<void*>(span_->start() * base::GetPageSize());
}
void DiscardForTesting() override { if (!manager_->LockSpan(span_.get()))
DCHECK(!is_locked_); return false;
span_->shared_memory()->Purge(base::Time::Now());
}
base::trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump( is_locked_ = true;
const char* name, return true;
base::trace_event::ProcessMemoryDump* pmd) const override { }
return manager_->CreateMemoryAllocatorDump(span_.get(), name, pmd);
}
private: void ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::Unlock() {
ClientDiscardableSharedMemoryManager* const manager_; base::AutoLock lock(manager_->GetLock());
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span_; DCHECK(is_locked_);
bool is_locked_; DCHECK(span_);
DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl); manager_->UnlockSpan(span_.get());
}; is_locked_ = false;
}
void InitManagerMojoOnIO( std::unique_ptr<DiscardableSharedMemoryHeap::Span>
mojo::Remote<mojom::DiscardableSharedMemoryManager>* manager_mojo, ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::Purge() {
mojo::PendingRemote<mojom::DiscardableSharedMemoryManager> remote) { DCHECK(span_);
manager_mojo->Bind(std::move(remote)); if (is_locked_)
return nullptr;
return std::move(span_);
} }
void DeletedDiscardableSharedMemoryOnIO( void* ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::data()
mojo::Remote<mojom::DiscardableSharedMemoryManager>* manager_mojo, const {
int32_t id) { #if DCHECK_IS_ON()
(*manager_mojo)->DeletedDiscardableSharedMemory(id); {
base::AutoLock lock(manager_->GetLock());
DCHECK(is_locked_);
}
#endif
return reinterpret_cast<void*>(span_->start() * base::GetPageSize());
} }
} // namespace void ClientDiscardableSharedMemoryManager::DiscardableMemoryImpl::
DiscardForTesting() {
#if DCHECK_IS_ON()
{
base::AutoLock lock(manager_->GetLock());
DCHECK(!is_locked_);
}
#endif
span_->shared_memory()->Purge(base::Time::Now());
}
base::trace_event::MemoryAllocatorDump* ClientDiscardableSharedMemoryManager::
DiscardableMemoryImpl::CreateMemoryAllocatorDump(
const char* name,
base::trace_event::ProcessMemoryDump* pmd) const {
return manager_->CreateMemoryAllocatorDump(span_.get(), name, pmd);
}
ClientDiscardableSharedMemoryManager::ClientDiscardableSharedMemoryManager( ClientDiscardableSharedMemoryManager::ClientDiscardableSharedMemoryManager(
mojo::PendingRemote<mojom::DiscardableSharedMemoryManager> manager, mojo::PendingRemote<mojom::DiscardableSharedMemoryManager> manager,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
: io_task_runner_(std::move(io_task_runner)), : heap_(std::make_unique<DiscardableSharedMemoryHeap>()),
io_task_runner_(std::move(io_task_runner)),
manager_mojo_(std::make_unique< manager_mojo_(std::make_unique<
mojo::Remote<mojom::DiscardableSharedMemoryManager>>()), mojo::Remote<mojom::DiscardableSharedMemoryManager>>()) {
heap_(std::make_unique<DiscardableSharedMemoryHeap>()) {
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "ClientDiscardableSharedMemoryManager", this, "ClientDiscardableSharedMemoryManager",
base::ThreadTaskRunnerHandle::Get()); base::ThreadTaskRunnerHandle::Get());
...@@ -144,6 +177,11 @@ ClientDiscardableSharedMemoryManager::ClientDiscardableSharedMemoryManager( ...@@ -144,6 +177,11 @@ ClientDiscardableSharedMemoryManager::ClientDiscardableSharedMemoryManager(
std::move(manager))); std::move(manager)));
} }
ClientDiscardableSharedMemoryManager::ClientDiscardableSharedMemoryManager(
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
: heap_(std::make_unique<DiscardableSharedMemoryHeap>()),
io_task_runner_(std::move(io_task_runner)) {}
ClientDiscardableSharedMemoryManager::~ClientDiscardableSharedMemoryManager() { ClientDiscardableSharedMemoryManager::~ClientDiscardableSharedMemoryManager() {
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this); this);
...@@ -225,7 +263,10 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory( ...@@ -225,7 +263,10 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
// at least one span from the free lists. // at least one span from the free lists.
MemoryUsageChanged(heap_->GetSize(), heap_->GetSizeOfFreeLists()); MemoryUsageChanged(heap_->GetSize(), heap_->GetSizeOfFreeLists());
return std::make_unique<DiscardableMemoryImpl>(this, std::move(free_span)); auto discardable_memory =
std::make_unique<DiscardableMemoryImpl>(this, std::move(free_span));
allocated_memory_.insert(discardable_memory.get());
return std::move(discardable_memory);
} }
// Release purged memory to free up the address space before we attempt to // Release purged memory to free up the address space before we attempt to
...@@ -279,7 +320,10 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory( ...@@ -279,7 +320,10 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
MemoryUsageChanged(heap_->GetSize(), heap_->GetSizeOfFreeLists()); MemoryUsageChanged(heap_->GetSize(), heap_->GetSizeOfFreeLists());
return std::make_unique<DiscardableMemoryImpl>(this, std::move(new_span)); auto discardable_memory =
std::make_unique<DiscardableMemoryImpl>(this, std::move(new_span));
allocated_memory_.insert(discardable_memory.get());
return std::move(discardable_memory);
} }
bool ClientDiscardableSharedMemoryManager::OnMemoryDump( bool ClientDiscardableSharedMemoryManager::OnMemoryDump(
...@@ -294,11 +338,46 @@ size_t ClientDiscardableSharedMemoryManager::GetBytesAllocated() const { ...@@ -294,11 +338,46 @@ size_t ClientDiscardableSharedMemoryManager::GetBytesAllocated() const {
return heap_->GetSize() - heap_->GetSizeOfFreeLists(); return heap_->GetSize() - heap_->GetSizeOfFreeLists();
} }
void ClientDiscardableSharedMemoryManager::PurgeUnlockedMemory() {
{
base::AutoLock lock(lock_);
// Iterate this way in order to avoid invalidating the iterator while
// removing elements from |allocated_memory_| as we iterate over it.
for (auto it = allocated_memory_.begin(); it != allocated_memory_.end();
/* nop */) {
auto prev = it++;
DiscardableMemoryImpl* mem = *prev;
// This assert is only required because the static checker can't figure
// out that |mem->manager_->GetLock()| is the same as |this->lock_|, as
// verified by the DCHECK.
DCHECK_EQ(&lock_, &mem->manager_->GetLock());
mem->manager_->GetLock().AssertAcquired();
auto span = mem->Purge();
if (span) {
allocated_memory_.erase(prev);
ReleaseSpan(std::move(span));
}
}
}
ReleaseFreeMemoryImpl();
}
void ClientDiscardableSharedMemoryManager::ReleaseFreeMemory() { void ClientDiscardableSharedMemoryManager::ReleaseFreeMemory() {
if (base::FeatureList::IsEnabled(kPurgeUnlockedMemory)) {
PurgeUnlockedMemory();
} else {
ReleaseFreeMemoryImpl();
}
}
void ClientDiscardableSharedMemoryManager::ReleaseFreeMemoryImpl() {
TRACE_EVENT0("blink", TRACE_EVENT0("blink",
"ClientDiscardableSharedMemoryManager::ReleaseFreeMemory()"); "ClientDiscardableSharedMemoryManager::ReleaseFreeMemory()");
base::AutoLock lock(lock_); base::AutoLock lock(lock_);
size_t heap_size_prior_to_releasing_memory = heap_->GetSize(); size_t heap_size_prior_to_releasing_memory = heap_->GetSize();
// Release both purged and free memory. // Release both purged and free memory.
...@@ -311,8 +390,6 @@ void ClientDiscardableSharedMemoryManager::ReleaseFreeMemory() { ...@@ -311,8 +390,6 @@ void ClientDiscardableSharedMemoryManager::ReleaseFreeMemory() {
bool ClientDiscardableSharedMemoryManager::LockSpan( bool ClientDiscardableSharedMemoryManager::LockSpan(
DiscardableSharedMemoryHeap::Span* span) { DiscardableSharedMemoryHeap::Span* span) {
base::AutoLock lock(lock_);
if (!span->shared_memory()) if (!span->shared_memory())
return false; return false;
...@@ -338,8 +415,6 @@ bool ClientDiscardableSharedMemoryManager::LockSpan( ...@@ -338,8 +415,6 @@ bool ClientDiscardableSharedMemoryManager::LockSpan(
void ClientDiscardableSharedMemoryManager::UnlockSpan( void ClientDiscardableSharedMemoryManager::UnlockSpan(
DiscardableSharedMemoryHeap::Span* span) { DiscardableSharedMemoryHeap::Span* span) {
base::AutoLock lock(lock_);
DCHECK(span->shared_memory()); DCHECK(span->shared_memory());
size_t offset = span->start() * base::GetPageSize() - size_t offset = span->start() * base::GetPageSize() -
reinterpret_cast<size_t>(span->shared_memory()->memory()); reinterpret_cast<size_t>(span->shared_memory()->memory());
...@@ -349,9 +424,18 @@ void ClientDiscardableSharedMemoryManager::UnlockSpan( ...@@ -349,9 +424,18 @@ void ClientDiscardableSharedMemoryManager::UnlockSpan(
return span->shared_memory()->Unlock(offset, length); return span->shared_memory()->Unlock(offset, length);
} }
void ClientDiscardableSharedMemoryManager::ReleaseMemory(
DiscardableMemoryImpl* memory,
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) {
DCHECK(span);
auto removed = allocated_memory_.erase(memory);
DCHECK_EQ(removed, 1u);
ReleaseSpan(std::move(span));
}
void ClientDiscardableSharedMemoryManager::ReleaseSpan( void ClientDiscardableSharedMemoryManager::ReleaseSpan(
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) { std::unique_ptr<DiscardableSharedMemoryHeap::Span> span) {
base::AutoLock lock(lock_); DCHECK(span);
// Delete span instead of merging it into free lists if memory is gone. // Delete span instead of merging it into free lists if memory is gone.
if (!span->shared_memory()) if (!span->shared_memory())
...@@ -380,6 +464,7 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory( ...@@ -380,6 +464,7 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
"ClientDiscardableSharedMemoryManager::" "ClientDiscardableSharedMemoryManager::"
"AllocateLockedDiscardableSharedMemory", "AllocateLockedDiscardableSharedMemory",
"size", size, "id", id); "size", size, "id", id);
base::UnsafeSharedMemoryRegion region; base::UnsafeSharedMemoryRegion region;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED); base::WaitableEvent::InitialState::NOT_SIGNALED);
...@@ -394,8 +479,8 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory( ...@@ -394,8 +479,8 @@ ClientDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
event.Wait(); event.Wait();
// This is likely address space exhaustion in the the browser process. We // This is likely address space exhaustion in the the browser process. We
// don't want to crash the browser process for that, which is why the check is // don't want to crash the browser process for that, which is why the check
// here, and not there. // is here, and not there.
if (!region.IsValid()) if (!region.IsValid())
return nullptr; return nullptr;
......
...@@ -8,9 +8,8 @@ ...@@ -8,9 +8,8 @@
#include <stddef.h> #include <stddef.h>
#include <memory> #include <memory>
#include <set>
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/memory/discardable_memory_allocator.h" #include "base/memory/discardable_memory_allocator.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/unsafe_shared_memory_region.h" #include "base/memory/unsafe_shared_memory_region.h"
...@@ -47,12 +46,16 @@ class DISCARDABLE_MEMORY_EXPORT ClientDiscardableSharedMemoryManager ...@@ -47,12 +46,16 @@ class DISCARDABLE_MEMORY_EXPORT ClientDiscardableSharedMemoryManager
bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override; base::trace_event::ProcessMemoryDump* pmd) override;
// Purge any unlocked memory that was allocated by this manager.
void PurgeUnlockedMemory();
// Release memory and associated resources that have been purged. // Release memory and associated resources that have been purged.
void ReleaseFreeMemory() override; void ReleaseFreeMemory() override;
bool LockSpan(DiscardableSharedMemoryHeap::Span* span); bool LockSpan(DiscardableSharedMemoryHeap::Span* span)
void UnlockSpan(DiscardableSharedMemoryHeap::Span* span); EXCLUSIVE_LOCKS_REQUIRED(GetLock());
void ReleaseSpan(std::unique_ptr<DiscardableSharedMemoryHeap::Span> span); void UnlockSpan(DiscardableSharedMemoryHeap::Span* span)
EXCLUSIVE_LOCKS_REQUIRED(GetLock());
base::trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump( base::trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
DiscardableSharedMemoryHeap::Span* span, DiscardableSharedMemoryHeap::Span* span,
...@@ -70,8 +73,47 @@ class DISCARDABLE_MEMORY_EXPORT ClientDiscardableSharedMemoryManager ...@@ -70,8 +73,47 @@ class DISCARDABLE_MEMORY_EXPORT ClientDiscardableSharedMemoryManager
bytes_allocated_limit_for_testing_ = limit; bytes_allocated_limit_for_testing_ = limit;
} }
// We only have protected members for testing, everything else should be
// either public or private.
protected:
explicit ClientDiscardableSharedMemoryManager(
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
std::unique_ptr<DiscardableSharedMemoryHeap> heap_ GUARDED_BY(lock_);
mutable base::Lock lock_;
private: private:
std::unique_ptr<base::DiscardableSharedMemory> class DiscardableMemoryImpl : public base::DiscardableMemory {
public:
DiscardableMemoryImpl(
ClientDiscardableSharedMemoryManager* manager,
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span);
~DiscardableMemoryImpl() override;
DiscardableMemoryImpl(const DiscardableMemoryImpl&) = delete;
DiscardableMemoryImpl& operator=(const DiscardableMemoryImpl&) = delete;
// Overridden from base::DiscardableMemory:
bool Lock() override;
void Unlock() override;
void* data() const override;
void DiscardForTesting() override;
base::trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
const char* name,
base::trace_event::ProcessMemoryDump* pmd) const override;
// Returns |span_| if unlocked, otherwise nullptr.
std::unique_ptr<DiscardableSharedMemoryHeap::Span> Purge()
EXCLUSIVE_LOCKS_REQUIRED(manager_->GetLock());
private:
friend class ClientDiscardableSharedMemoryManager;
ClientDiscardableSharedMemoryManager* const manager_;
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span_;
bool is_locked_ GUARDED_BY(manager_->GetLock());
};
// This is only virtual for testing.
virtual std::unique_ptr<base::DiscardableSharedMemory>
AllocateLockedDiscardableSharedMemory(size_t size, int32_t id); AllocateLockedDiscardableSharedMemory(size_t size, int32_t id);
void AllocateOnIO(size_t size, void AllocateOnIO(size_t size,
int32_t id, int32_t id,
...@@ -81,18 +123,27 @@ class DISCARDABLE_MEMORY_EXPORT ClientDiscardableSharedMemoryManager ...@@ -81,18 +123,27 @@ class DISCARDABLE_MEMORY_EXPORT ClientDiscardableSharedMemoryManager
base::ScopedClosureRunner closure_runner, base::ScopedClosureRunner closure_runner,
base::UnsafeSharedMemoryRegion ret_region); base::UnsafeSharedMemoryRegion ret_region);
void DeletedDiscardableSharedMemory(int32_t id); // This is only virtual for testing.
virtual void DeletedDiscardableSharedMemory(int32_t id);
void MemoryUsageChanged(size_t new_bytes_allocated, void MemoryUsageChanged(size_t new_bytes_allocated,
size_t new_bytes_free) const; size_t new_bytes_free) const;
void ReleaseFreeMemoryImpl();
void ReleaseMemory(DiscardableMemoryImpl* memory,
std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
void ReleaseSpan(std::unique_ptr<DiscardableSharedMemoryHeap::Span> span)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
base::Lock& GetLock() { return lock_; }
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
// TODO(penghuang): Switch to SharedRemote when it starts supporting // TODO(penghuang): Switch to SharedRemote when it starts supporting
// sync method call. // sync method call.
std::unique_ptr<mojo::Remote<mojom::DiscardableSharedMemoryManager>> std::unique_ptr<mojo::Remote<mojom::DiscardableSharedMemoryManager>>
manager_mojo_; manager_mojo_;
mutable base::Lock lock_; // Holds all locked and unlocked instances which have not yet been purged.
std::unique_ptr<DiscardableSharedMemoryHeap> heap_ GUARDED_BY(lock_); std::set<DiscardableMemoryImpl*> allocated_memory_ GUARDED_BY(lock_);
size_t bytes_allocated_limit_for_testing_ = 0; size_t bytes_allocated_limit_for_testing_ = 0;
DISALLOW_COPY_AND_ASSIGN(ClientDiscardableSharedMemoryManager); DISALLOW_COPY_AND_ASSIGN(ClientDiscardableSharedMemoryManager);
......
// Copyright 2020 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 "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
#include "base/memory/discardable_memory.h"
#include "base/memory/discardable_shared_memory.h"
#include "base/process/process_metrics.h"
#include "base/synchronization/lock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace discardable_memory {
namespace {
using base::Location;
using base::OnceClosure;
using base::TimeDelta;
class TestSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
~TestSingleThreadTaskRunner() override = default;
bool PostTask(const Location& from_here, OnceClosure task) { return true; }
template <class T>
bool DeleteSoon(const Location& from_here, const T* object) {
return true;
}
bool PostDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override {
return true;
}
bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override {
return true;
}
bool RunsTasksInCurrentSequence() const override { return true; }
};
class TestClientDiscardableSharedMemoryManager
: public ClientDiscardableSharedMemoryManager {
public:
TestClientDiscardableSharedMemoryManager()
: ClientDiscardableSharedMemoryManager(
base::MakeRefCounted<TestSingleThreadTaskRunner>()) {}
~TestClientDiscardableSharedMemoryManager() override = default;
std::unique_ptr<base::DiscardableSharedMemory>
AllocateLockedDiscardableSharedMemory(size_t size, int32_t id) override {
auto shared_memory = std::make_unique<base::DiscardableSharedMemory>();
shared_memory->CreateAndMap(size);
return shared_memory;
}
void DeletedDiscardableSharedMemory(int32_t id) override {}
size_t GetSize() const {
base::AutoLock lock(lock_);
return heap_->GetSize();
}
size_t GetSizeOfFreeLists() const {
base::AutoLock lock(lock_);
return heap_->GetSizeOfFreeLists();
}
};
// This test allocates a single piece of memory, then verifies that calling
// |PurgeUnlockedMemory| only affects the memory when it is unlocked.
TEST(ClientDiscardableSharedMemoryManagerTest, Simple) {
const size_t page_size = base::GetPageSize();
TestClientDiscardableSharedMemoryManager client;
// Initially, we should have no memory allocated
ASSERT_EQ(client.GetBytesAllocated(), 0u);
ASSERT_EQ(client.GetSizeOfFreeLists(), 0u);
auto mem = client.AllocateLockedDiscardableMemory(page_size);
// After allocation, we should have allocated a single piece of memory.
EXPECT_EQ(client.GetBytesAllocated(), page_size);
client.PurgeUnlockedMemory();
// All our memory is locked, so calling |PurgeUnlockedMemory| should have no
// effect.
EXPECT_EQ(client.GetBytesAllocated(), base::GetPageSize());
mem->Unlock();
// Unlocking has no effect on the amount of memory we have allocated.
EXPECT_EQ(client.GetBytesAllocated(), base::GetPageSize());
client.PurgeUnlockedMemory();
// Now that |mem| is unlocked, the call to |PurgeUnlockedMemory| will
// remove it.
EXPECT_EQ(client.GetBytesAllocated(), 0u);
}
// This test allocates multiple pieces of memory, then unlocks them one by one,
// verifying that |PurgeUnlockedMemory| only affects the unlocked pieces of
// memory.
TEST(ClientDiscardableSharedMemoryManagerTest, MultipleOneByOne) {
const size_t page_size = base::GetPageSize();
TestClientDiscardableSharedMemoryManager client;
ASSERT_EQ(client.GetBytesAllocated(), 0u);
ASSERT_EQ(client.GetSizeOfFreeLists(), 0u);
auto mem1 = client.AllocateLockedDiscardableMemory(page_size * 2.2);
auto mem2 = client.AllocateLockedDiscardableMemory(page_size * 1.1);
auto mem3 = client.AllocateLockedDiscardableMemory(page_size * 3.5);
auto mem4 = client.AllocateLockedDiscardableMemory(page_size * 0.2);
EXPECT_EQ(client.GetBytesAllocated(), 10 * page_size);
// Does nothing because everything is locked.
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 10 * page_size);
mem1->Unlock();
// Does nothing, since we don't have any free memory, just unlocked memory.
client.ReleaseFreeMemory();
EXPECT_EQ(client.GetBytesAllocated(), 10 * page_size);
// This gets rid of |mem1| (which is unlocked), but not the rest of the
// memory.
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 7 * page_size);
// We do similar checks to above for the rest of the memory.
mem2->Unlock();
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 5 * page_size);
mem3->Unlock();
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 1 * page_size);
mem4->Unlock();
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 0 * page_size);
}
// This test allocates multiple pieces of memory, then unlocks them all,
// verifying that |PurgeUnlockedMemory| only affects the unlocked pieces of
// memory.
TEST(ClientDiscardableSharedMemoryManagerTest, MultipleAtOnce) {
const size_t page_size = base::GetPageSize();
TestClientDiscardableSharedMemoryManager client;
ASSERT_EQ(client.GetBytesAllocated(), 0u);
ASSERT_EQ(client.GetSizeOfFreeLists(), 0u);
auto mem1 = client.AllocateLockedDiscardableMemory(page_size * 2.2);
auto mem2 = client.AllocateLockedDiscardableMemory(page_size * 1.1);
auto mem3 = client.AllocateLockedDiscardableMemory(page_size * 3.5);
auto mem4 = client.AllocateLockedDiscardableMemory(page_size * 0.2);
EXPECT_EQ(client.GetBytesAllocated(), 10 * page_size);
// Does nothing because everything is locked.
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 10 * page_size);
// Unlock all pieces of memory at once.
mem1->Unlock();
mem2->Unlock();
mem3->Unlock();
mem4->Unlock();
client.PurgeUnlockedMemory();
EXPECT_EQ(client.GetBytesAllocated(), 0 * page_size);
}
// Tests that FreeLists are only released once all memory has been released.
TEST(ClientDiscardableSharedMemoryManagerTest, Release) {
const size_t page_size = base::GetPageSize();
TestClientDiscardableSharedMemoryManager client;
ASSERT_EQ(client.GetBytesAllocated(), 0u);
ASSERT_EQ(client.GetSizeOfFreeLists(), 0u);
auto mem1 = client.AllocateLockedDiscardableMemory(page_size * 3);
auto mem2 = client.AllocateLockedDiscardableMemory(page_size * 2);
size_t freelist_size = client.GetSizeOfFreeLists();
EXPECT_EQ(client.GetBytesAllocated(), 5 * page_size);
mem1 = nullptr;
// Less memory is now allocated, but freelists are grown.
EXPECT_EQ(client.GetBytesAllocated(), page_size * 2);
EXPECT_EQ(client.GetSizeOfFreeLists(), freelist_size + page_size * 3);
client.PurgeUnlockedMemory();
// Purging doesn't remove any memory since none is unlocked, also doesn't
// remove freelists since we still have some.
EXPECT_EQ(client.GetBytesAllocated(), page_size * 2);
EXPECT_EQ(client.GetSizeOfFreeLists(), freelist_size + page_size * 3);
mem2 = nullptr;
// No memory is allocated, but freelists are grown.
EXPECT_EQ(client.GetBytesAllocated(), 0u);
EXPECT_EQ(client.GetSizeOfFreeLists(), freelist_size + page_size * 5);
client.PurgeUnlockedMemory();
// Purging now shrinks freelists as well.
EXPECT_EQ(client.GetBytesAllocated(), 0u);
EXPECT_EQ(client.GetSizeOfFreeLists(), 0u);
}
// Similar to previous test, but makes sure that freelist still shrinks when
// last piece of memory was just unlocked instead of released.
TEST(ClientDiscardableSharedMemoryManagerTest, ReleaseUnlocked) {
const size_t page_size = base::GetPageSize();
TestClientDiscardableSharedMemoryManager client;
ASSERT_EQ(client.GetBytesAllocated(), 0u);
ASSERT_EQ(client.GetSizeOfFreeLists(), 0u);
auto mem1 = client.AllocateLockedDiscardableMemory(page_size * 3);
auto mem2 = client.AllocateLockedDiscardableMemory(page_size * 2);
size_t freelist_size = client.GetSizeOfFreeLists();
EXPECT_EQ(client.GetBytesAllocated(), 5 * page_size);
mem1 = nullptr;
// Less memory is now allocated, but freelists are grown.
EXPECT_EQ(client.GetBytesAllocated(), page_size * 2);
EXPECT_EQ(client.GetSizeOfFreeLists(), freelist_size + page_size * 3);
client.PurgeUnlockedMemory();
// Purging doesn't remove any memory since none is unlocked, also doesn't
// remove freelists since we still have some.
EXPECT_EQ(client.GetBytesAllocated(), page_size * 2);
EXPECT_EQ(client.GetSizeOfFreeLists(), freelist_size + page_size * 3);
mem2->Unlock();
// No change in memory usage, since memory was only unlocked not released.
EXPECT_EQ(client.GetBytesAllocated(), page_size * 2);
EXPECT_EQ(client.GetSizeOfFreeLists(), freelist_size + page_size * 3);
client.PurgeUnlockedMemory();
// Purging now shrinks freelists as well.
EXPECT_EQ(client.GetBytesAllocated(), 0u);
EXPECT_EQ(client.GetSizeOfFreeLists(), 0u);
}
} // namespace
} // namespace discardable_memory
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