| // Copyright (c) 2012 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 "net/disk_cache/memory/mem_backend_impl.h" | |
| #include <algorithm> | |
| #include <functional> | |
| #include <memory> | |
| #include <utility> | |
| #include "base/logging.h" | |
| #include "base/sys_info.h" | |
| #include "base/task/post_task.h" | |
| #include "base/threading/sequenced_task_runner_handle.h" | |
| #include "base/trace_event/memory_usage_estimator.h" | |
| #include "base/trace_event/process_memory_dump.h" | |
| #include "net/base/net_errors.h" | |
| #include "net/disk_cache/cache_util.h" | |
| #include "net/disk_cache/memory/mem_entry_impl.h" | |
| using base::Time; | |
| namespace disk_cache { | |
| namespace { | |
| const int kDefaultInMemoryCacheSize = 10 * 1024 * 1024; | |
| const int kDefaultEvictionSize = kDefaultInMemoryCacheSize / 10; | |
| bool CheckLRUListOrder(const base::LinkedList<MemEntryImpl>& lru_list) { | |
| // TODO(gavinp): Check MemBackendImpl::current_size_ here as well. | |
| base::Time previous_last_use_time; | |
| for (base::LinkNode<MemEntryImpl>* node = lru_list.head(); | |
| node != lru_list.end(); node = node->next()) { | |
| if (node->value()->GetLastUsed() < previous_last_use_time) | |
| return false; | |
| previous_last_use_time = node->value()->GetLastUsed(); | |
| } | |
| return true; | |
| } | |
| // Returns the next entry after |node| in |lru_list| that's not a child | |
| // of |node|. This is useful when dooming, since dooming a parent entry | |
| // will also doom its children. | |
| base::LinkNode<MemEntryImpl>* NextSkippingChildren( | |
| const base::LinkedList<MemEntryImpl>& lru_list, | |
| base::LinkNode<MemEntryImpl>* node) { | |
| MemEntryImpl* cur = node->value(); | |
| do { | |
| node = node->next(); | |
| } while (node != lru_list.end() && node->value()->parent() == cur); | |
| return node; | |
| } | |
| } // namespace | |
| MemBackendImpl::MemBackendImpl(net::NetLog* net_log) | |
| : max_size_(0), | |
| current_size_(0), | |
| net_log_(net_log), | |
| memory_pressure_listener_( | |
| base::BindRepeating(&MemBackendImpl::OnMemoryPressure, | |
| base::Unretained(this))), | |
| weak_factory_(this) {} | |
| MemBackendImpl::~MemBackendImpl() { | |
| DCHECK(CheckLRUListOrder(lru_list_)); | |
| while (!entries_.empty()) | |
| entries_.begin()->second->Doom(); | |
| if (!post_cleanup_callback_.is_null()) | |
| base::SequencedTaskRunnerHandle::Get()->PostTask( | |
| FROM_HERE, std::move(post_cleanup_callback_)); | |
| } | |
| // static | |
| std::unique_ptr<MemBackendImpl> MemBackendImpl::CreateBackend( | |
| int64_t max_bytes, | |
| net::NetLog* net_log) { | |
| std::unique_ptr<MemBackendImpl> cache( | |
| std::make_unique<MemBackendImpl>(net_log)); | |
| if (cache->SetMaxSize(max_bytes) && cache->Init()) | |
| return cache; | |
| LOG(ERROR) << "Unable to create cache"; | |
| return nullptr; | |
| } | |
| bool MemBackendImpl::Init() { | |
| if (max_size_) | |
| return true; | |
| int64_t total_memory = base::SysInfo::AmountOfPhysicalMemory(); | |
| if (total_memory <= 0) { | |
| max_size_ = kDefaultInMemoryCacheSize; | |
| return true; | |
| } | |
| // We want to use up to 2% of the computer's memory, with a limit of 50 MB, | |
| // reached on system with more than 2.5 GB of RAM. | |
| total_memory = total_memory * 2 / 100; | |
| if (total_memory > kDefaultInMemoryCacheSize * 5) | |
| max_size_ = kDefaultInMemoryCacheSize * 5; | |
| else | |
| max_size_ = static_cast<int32_t>(total_memory); | |
| return true; | |
| } | |
| bool MemBackendImpl::SetMaxSize(int64_t max_bytes) { | |
| if (max_bytes < 0 || max_bytes > std::numeric_limits<int>::max()) | |
| return false; | |
| // Zero size means use the default. | |
| if (!max_bytes) | |
| return true; | |
| max_size_ = max_bytes; | |
| return true; | |
| } | |
| int MemBackendImpl::MaxFileSize() const { | |
| return max_size_ / 8; | |
| } | |
| void MemBackendImpl::OnEntryInserted(MemEntryImpl* entry) { | |
| lru_list_.Append(entry); | |
| } | |
| void MemBackendImpl::OnEntryUpdated(MemEntryImpl* entry) { | |
| DCHECK(CheckLRUListOrder(lru_list_)); | |
| // LinkedList<>::RemoveFromList() removes |entry| from |lru_list_|. | |
| entry->RemoveFromList(); | |
| lru_list_.Append(entry); | |
| } | |
| void MemBackendImpl::OnEntryDoomed(MemEntryImpl* entry) { | |
| DCHECK(CheckLRUListOrder(lru_list_)); | |
| if (entry->type() == MemEntryImpl::PARENT_ENTRY) | |
| entries_.erase(entry->key()); | |
| // LinkedList<>::RemoveFromList() removes |entry| from |lru_list_|. | |
| entry->RemoveFromList(); | |
| } | |
| void MemBackendImpl::ModifyStorageSize(int32_t delta) { | |
| current_size_ += delta; | |
| if (delta > 0) | |
| EvictIfNeeded(); | |
| } | |
| bool MemBackendImpl::HasExceededStorageSize() const { | |
| return current_size_ > max_size_; | |
| } | |
| void MemBackendImpl::SetPostCleanupCallback(base::OnceClosure cb) { | |
| DCHECK(post_cleanup_callback_.is_null()); | |
| post_cleanup_callback_ = std::move(cb); | |
| } | |
| net::CacheType MemBackendImpl::GetCacheType() const { | |
| return net::MEMORY_CACHE; | |
| } | |
| int32_t MemBackendImpl::GetEntryCount() const { | |
| return static_cast<int32_t>(entries_.size()); | |
| } | |
| net::Error MemBackendImpl::OpenEntry(const std::string& key, | |
| net::RequestPriority request_priority, | |
| Entry** entry, | |
| CompletionOnceCallback callback) { | |
| auto it = entries_.find(key); | |
| if (it == entries_.end()) | |
| return net::ERR_FAILED; | |
| it->second->Open(); | |
| *entry = it->second; | |
| return net::OK; | |
| } | |
| net::Error MemBackendImpl::CreateEntry(const std::string& key, | |
| net::RequestPriority request_priority, | |
| Entry** entry, | |
| CompletionOnceCallback callback) { | |
| std::pair<EntryMap::iterator, bool> create_result = | |
| entries_.insert(EntryMap::value_type(key, nullptr)); | |
| const bool did_insert = create_result.second; | |
| if (!did_insert) | |
| return net::ERR_FAILED; | |
| MemEntryImpl* cache_entry = | |
| new MemEntryImpl(weak_factory_.GetWeakPtr(), key, net_log_); | |
| create_result.first->second = cache_entry; | |
| *entry = cache_entry; | |
| return net::OK; | |
| } | |
| net::Error MemBackendImpl::DoomEntry(const std::string& key, | |
| net::RequestPriority priority, | |
| CompletionOnceCallback callback) { | |
| auto it = entries_.find(key); | |
| if (it == entries_.end()) | |
| return net::ERR_FAILED; | |
| it->second->Doom(); | |
| return net::OK; | |
| } | |
| net::Error MemBackendImpl::DoomAllEntries(CompletionOnceCallback callback) { | |
| return DoomEntriesBetween(Time(), Time(), std::move(callback)); | |
| } | |
| net::Error MemBackendImpl::DoomEntriesBetween(Time initial_time, | |
| Time end_time, | |
| CompletionOnceCallback callback) { | |
| if (end_time.is_null()) | |
| end_time = Time::Max(); | |
| DCHECK_GE(end_time, initial_time); | |
| base::LinkNode<MemEntryImpl>* node = lru_list_.head(); | |
| while (node != lru_list_.end() && node->value()->GetLastUsed() < initial_time) | |
| node = node->next(); | |
| while (node != lru_list_.end() && node->value()->GetLastUsed() < end_time) { | |
| MemEntryImpl* to_doom = node->value(); | |
| node = NextSkippingChildren(lru_list_, node); | |
| to_doom->Doom(); | |
| } | |
| return net::OK; | |
| } | |
| net::Error MemBackendImpl::DoomEntriesSince(Time initial_time, | |
| CompletionOnceCallback callback) { | |
| return DoomEntriesBetween(initial_time, Time::Max(), std::move(callback)); | |
| } | |
| int64_t MemBackendImpl::CalculateSizeOfAllEntries( | |
| Int64CompletionOnceCallback callback) { | |
| return current_size_; | |
| } | |
| int64_t MemBackendImpl::CalculateSizeOfEntriesBetween( | |
| base::Time initial_time, | |
| base::Time end_time, | |
| Int64CompletionOnceCallback callback) { | |
| if (end_time.is_null()) | |
| end_time = Time::Max(); | |
| DCHECK_GE(end_time, initial_time); | |
| int size = 0; | |
| base::LinkNode<MemEntryImpl>* node = lru_list_.head(); | |
| while (node != lru_list_.end() && node->value()->GetLastUsed() < initial_time) | |
| node = node->next(); | |
| while (node != lru_list_.end() && node->value()->GetLastUsed() < end_time) { | |
| MemEntryImpl* entry = node->value(); | |
| size += entry->GetStorageSize(); | |
| node = node->next(); | |
| } | |
| return size; | |
| } | |
| class MemBackendImpl::MemIterator final : public Backend::Iterator { | |
| public: | |
| explicit MemIterator(base::WeakPtr<MemBackendImpl> backend) | |
| : backend_(backend) {} | |
| net::Error OpenNextEntry(Entry** next_entry, | |
| CompletionOnceCallback callback) override { | |
| if (!backend_) | |
| return net::ERR_FAILED; | |
| if (!backend_keys_) { | |
| backend_keys_ = std::make_unique<Strings>(backend_->entries_.size()); | |
| for (const auto& iter : backend_->entries_) | |
| backend_keys_->push_back(iter.first); | |
| current_ = backend_keys_->begin(); | |
| } else { | |
| current_++; | |
| } | |
| while (true) { | |
| if (current_ == backend_keys_->end()) { | |
| *next_entry = nullptr; | |
| backend_keys_.reset(); | |
| return net::ERR_FAILED; | |
| } | |
| const auto& entry_iter = backend_->entries_.find(*current_); | |
| if (entry_iter == backend_->entries_.end()) { | |
| // The key is no longer in the cache, move on to the next key. | |
| current_++; | |
| continue; | |
| } | |
| entry_iter->second->Open(); | |
| *next_entry = entry_iter->second; | |
| return net::OK; | |
| } | |
| } | |
| private: | |
| using Strings = std::vector<std::string>; | |
| base::WeakPtr<MemBackendImpl> backend_; | |
| std::unique_ptr<Strings> backend_keys_; | |
| Strings::iterator current_; | |
| }; | |
| std::unique_ptr<Backend::Iterator> MemBackendImpl::CreateIterator() { | |
| return std::unique_ptr<Backend::Iterator>( | |
| new MemIterator(weak_factory_.GetWeakPtr())); | |
| } | |
| void MemBackendImpl::OnExternalCacheHit(const std::string& key) { | |
| auto it = entries_.find(key); | |
| if (it != entries_.end()) | |
| it->second->UpdateStateOnUse(MemEntryImpl::ENTRY_WAS_NOT_MODIFIED); | |
| } | |
| size_t MemBackendImpl::DumpMemoryStats( | |
| base::trace_event::ProcessMemoryDump* pmd, | |
| const std::string& parent_absolute_name) const { | |
| base::trace_event::MemoryAllocatorDump* dump = | |
| pmd->CreateAllocatorDump(parent_absolute_name + "/memory_backend"); | |
| // Entries in lru_list_ will be counted by EMU but not in entries_ since | |
| // they're pointers. | |
| size_t size = base::trace_event::EstimateMemoryUsage(lru_list_) + | |
| base::trace_event::EstimateMemoryUsage(entries_); | |
| dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, | |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, size); | |
| dump->AddScalar("mem_backend_size", | |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, | |
| current_size_); | |
| dump->AddScalar("mem_backend_max_size", | |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, | |
| max_size_); | |
| return size; | |
| } | |
| void MemBackendImpl::EvictIfNeeded() { | |
| if (current_size_ <= max_size_) | |
| return; | |
| int target_size = std::max(0, max_size_ - kDefaultEvictionSize); | |
| EvictTill(target_size); | |
| } | |
| void MemBackendImpl::EvictTill(int target_size) { | |
| base::LinkNode<MemEntryImpl>* entry = lru_list_.head(); | |
| while (current_size_ > target_size && entry != lru_list_.end()) { | |
| MemEntryImpl* to_doom = entry->value(); | |
| entry = NextSkippingChildren(lru_list_, entry); | |
| if (!to_doom->InUse()) | |
| to_doom->Doom(); | |
| } | |
| } | |
| void MemBackendImpl::OnMemoryPressure( | |
| base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { | |
| switch (memory_pressure_level) { | |
| case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE: | |
| // Not supposed to get this here, but if there is no problem, there is | |
| // no problem... | |
| break; | |
| case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE: | |
| EvictTill(max_size_ / 2); | |
| break; | |
| case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL: | |
| EvictTill(max_size_ / 10); | |
| break; | |
| } | |
| } | |
| } // namespace disk_cache |