blob: 78ce87ed71d6c9006f3b67cdfc5575d3bc9439b3 [file] [log] [blame]
// Copyright (c) 2013 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/simple/simple_index.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/pickle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "base/task_runner.h"
#include "base/threading/worker_pool.h"
#include "base/time/time.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/simple/simple_entry_format.h"
#include "net/disk_cache/simple/simple_index_file.h"
#include "net/disk_cache/simple/simple_synchronous_entry.h"
#include "net/disk_cache/simple/simple_util.h"
#if defined(OS_POSIX)
#include <sys/stat.h>
#include <sys/time.h>
#endif
namespace {
// How many milliseconds we delay writing the index to disk since the last cache
// operation has happened.
const int kDefaultWriteToDiskDelayMSecs = 20000;
const int kDefaultWriteToDiskOnBackgroundDelayMSecs = 100;
// Divides the cache space into this amount of parts to evict when only one part
// is left.
const uint32 kEvictionMarginDivisor = 20;
const uint32 kBytesInKb = 1024;
// Utility class used for timestamp comparisons in entry metadata while sorting.
class CompareHashesForTimestamp {
typedef disk_cache::SimpleIndex SimpleIndex;
typedef disk_cache::SimpleIndex::EntrySet EntrySet;
public:
explicit CompareHashesForTimestamp(const EntrySet& set);
bool operator()(uint64 hash1, uint64 hash2);
private:
const EntrySet& entry_set_;
};
CompareHashesForTimestamp::CompareHashesForTimestamp(const EntrySet& set)
: entry_set_(set) {
}
bool CompareHashesForTimestamp::operator()(uint64 hash1, uint64 hash2) {
EntrySet::const_iterator it1 = entry_set_.find(hash1);
DCHECK(it1 != entry_set_.end());
EntrySet::const_iterator it2 = entry_set_.find(hash2);
DCHECK(it2 != entry_set_.end());
return it1->second.GetLastUsedTime() < it2->second.GetLastUsedTime();
}
} // namespace
namespace disk_cache {
EntryMetadata::EntryMetadata() : last_used_time_(0), entry_size_(0) {}
EntryMetadata::EntryMetadata(base::Time last_used_time, uint64 entry_size)
: last_used_time_(last_used_time.ToInternalValue()),
entry_size_(entry_size) {}
base::Time EntryMetadata::GetLastUsedTime() const {
return base::Time::FromInternalValue(last_used_time_);
}
void EntryMetadata::SetLastUsedTime(const base::Time& last_used_time) {
last_used_time_ = last_used_time.ToInternalValue();
}
void EntryMetadata::Serialize(Pickle* pickle) const {
DCHECK(pickle);
COMPILE_ASSERT(sizeof(EntryMetadata) == (sizeof(int64) + sizeof(uint64)),
EntryMetadata_has_two_member_variables);
pickle->WriteInt64(last_used_time_);
pickle->WriteUInt64(entry_size_);
}
bool EntryMetadata::Deserialize(PickleIterator* it) {
DCHECK(it);
return it->ReadInt64(&last_used_time_) && it->ReadUInt64(&entry_size_);
}
SimpleIndex::SimpleIndex(base::SingleThreadTaskRunner* io_thread,
const base::FilePath& cache_directory,
scoped_ptr<SimpleIndexFile> index_file)
: cache_size_(0),
max_size_(0),
high_watermark_(0),
low_watermark_(0),
eviction_in_progress_(false),
initialized_(false),
cache_directory_(cache_directory),
index_file_(index_file.Pass()),
io_thread_(io_thread),
// Creating the callback once so it is reused every time
// write_to_disk_timer_.Start() is called.
write_to_disk_cb_(base::Bind(&SimpleIndex::WriteToDisk, AsWeakPtr())),
app_on_background_(false) {}
SimpleIndex::~SimpleIndex() {
DCHECK(io_thread_checker_.CalledOnValidThread());
// Fail all callbacks waiting for the index to come up.
for (CallbackList::iterator it = to_run_when_initialized_.begin(),
end = to_run_when_initialized_.end(); it != end; ++it) {
it->Run(net::ERR_ABORTED);
}
}
void SimpleIndex::Initialize(base::Time cache_mtime) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// Take the foreground and background index flush delays from the experiment
// settings only if both are valid.
foreground_flush_delay_ = kDefaultWriteToDiskDelayMSecs;
background_flush_delay_ = kDefaultWriteToDiskOnBackgroundDelayMSecs;
const std::string index_flush_intervals = base::FieldTrialList::FindFullName(
"SimpleCacheIndexFlushDelay_Foreground_Background");
if (!index_flush_intervals.empty()) {
base::StringTokenizer tokens(index_flush_intervals, "_");
int foreground_delay, background_delay;
if (tokens.GetNext() &&
base::StringToInt(tokens.token(), &foreground_delay) &&
tokens.GetNext() &&
base::StringToInt(tokens.token(), &background_delay)) {
foreground_flush_delay_ = foreground_delay;
background_flush_delay_ = background_delay;
}
}
#if defined(OS_ANDROID)
activity_status_listener_.reset(new base::android::ActivityStatus::Listener(
base::Bind(&SimpleIndex::OnActivityStateChange, AsWeakPtr())));
#endif
SimpleIndexLoadResult* load_result = new SimpleIndexLoadResult();
scoped_ptr<SimpleIndexLoadResult> load_result_scoped(load_result);
base::Closure reply = base::Bind(
&SimpleIndex::MergeInitializingSet,
AsWeakPtr(),
base::Passed(&load_result_scoped));
index_file_->LoadIndexEntries(cache_mtime, reply, load_result);
}
bool SimpleIndex::SetMaxSize(int max_bytes) {
if (max_bytes < 0)
return false;
// Zero size means use the default.
if (!max_bytes)
return true;
max_size_ = max_bytes;
high_watermark_ = max_size_ - max_size_ / kEvictionMarginDivisor;
low_watermark_ = max_size_ - 2 * (max_size_ / kEvictionMarginDivisor);
return true;
}
int SimpleIndex::ExecuteWhenReady(const net::CompletionCallback& task) {
DCHECK(io_thread_checker_.CalledOnValidThread());
if (initialized_)
io_thread_->PostTask(FROM_HERE, base::Bind(task, net::OK));
else
to_run_when_initialized_.push_back(task);
return net::ERR_IO_PENDING;
}
scoped_ptr<SimpleIndex::HashList> SimpleIndex::RemoveEntriesBetween(
const base::Time initial_time, const base::Time end_time) {
return ExtractEntriesBetween(initial_time, end_time, true);
}
scoped_ptr<SimpleIndex::HashList> SimpleIndex::GetAllHashes() {
const base::Time null_time = base::Time();
return ExtractEntriesBetween(null_time, null_time, false);
}
int32 SimpleIndex::GetEntryCount() const {
// TODO(pasko): return a meaningful initial estimate before initialized.
return entries_set_.size();
}
void SimpleIndex::Insert(const std::string& key) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// Upon insert we don't know yet the size of the entry.
// It will be updated later when the SimpleEntryImpl finishes opening or
// creating the new entry, and then UpdateEntrySize will be called.
const uint64 hash_key = simple_util::GetEntryHashKey(key);
InsertInEntrySet(
hash_key, EntryMetadata(base::Time::Now(), 0), &entries_set_);
if (!initialized_)
removed_entries_.erase(hash_key);
PostponeWritingToDisk();
}
void SimpleIndex::Remove(const std::string& key) {
DCHECK(io_thread_checker_.CalledOnValidThread());
const uint64 hash_key = simple_util::GetEntryHashKey(key);
EntrySet::iterator it = entries_set_.find(hash_key);
if (it != entries_set_.end()) {
UpdateEntryIteratorSize(&it, 0);
entries_set_.erase(it);
}
if (!initialized_)
removed_entries_.insert(hash_key);
PostponeWritingToDisk();
}
bool SimpleIndex::Has(uint64 hash) const {
DCHECK(io_thread_checker_.CalledOnValidThread());
// If not initialized, always return true, forcing it to go to the disk.
return !initialized_ || entries_set_.count(hash) > 0;
}
bool SimpleIndex::UseIfExists(const std::string& key) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// Always update the last used time, even if it is during initialization.
// It will be merged later.
EntrySet::iterator it = entries_set_.find(simple_util::GetEntryHashKey(key));
if (it == entries_set_.end())
// If not initialized, always return true, forcing it to go to the disk.
return !initialized_;
it->second.SetLastUsedTime(base::Time::Now());
PostponeWritingToDisk();
return true;
}
void SimpleIndex::StartEvictionIfNeeded() {
DCHECK(io_thread_checker_.CalledOnValidThread());
if (eviction_in_progress_ || cache_size_ <= high_watermark_)
return;
// Take all live key hashes from the index and sort them by time.
eviction_in_progress_ = true;
eviction_start_time_ = base::TimeTicks::Now();
UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.CacheSizeOnStart2",
cache_size_ / kBytesInKb);
UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.MaxCacheSizeOnStart2",
max_size_ / kBytesInKb);
scoped_ptr<std::vector<uint64> > entry_hashes(new std::vector<uint64>());
for (EntrySet::const_iterator it = entries_set_.begin(),
end = entries_set_.end(); it != end; ++it) {
entry_hashes->push_back(it->first);
}
std::sort(entry_hashes->begin(), entry_hashes->end(),
CompareHashesForTimestamp(entries_set_));
// Remove as many entries from the index to get below |low_watermark_|.
std::vector<uint64>::iterator it = entry_hashes->begin();
uint64 evicted_so_far_size = 0;
while (evicted_so_far_size < cache_size_ - low_watermark_) {
DCHECK(it != entry_hashes->end());
EntrySet::iterator found_meta = entries_set_.find(*it);
DCHECK(found_meta != entries_set_.end());
uint64 to_evict_size = found_meta->second.GetEntrySize();
evicted_so_far_size += to_evict_size;
entries_set_.erase(found_meta);
++it;
}
cache_size_ -= evicted_so_far_size;
// Take out the rest of hashes from the eviction list.
entry_hashes->erase(it, entry_hashes->end());
UMA_HISTOGRAM_COUNTS("SimpleCache.Eviction.EntryCount", entry_hashes->size());
UMA_HISTOGRAM_TIMES("SimpleCache.Eviction.TimeToSelectEntries",
base::TimeTicks::Now() - eviction_start_time_);
UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.SizeOfEvicted2",
evicted_so_far_size / kBytesInKb);
index_file_->DoomEntrySet(
entry_hashes.Pass(),
base::Bind(&SimpleIndex::EvictionDone, AsWeakPtr()));
}
bool SimpleIndex::UpdateEntrySize(const std::string& key, uint64 entry_size) {
DCHECK(io_thread_checker_.CalledOnValidThread());
EntrySet::iterator it = entries_set_.find(simple_util::GetEntryHashKey(key));
if (it == entries_set_.end())
return false;
UpdateEntryIteratorSize(&it, entry_size);
PostponeWritingToDisk();
StartEvictionIfNeeded();
return true;
}
void SimpleIndex::EvictionDone(int result) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// Ignore the result of eviction. We did our best.
eviction_in_progress_ = false;
UMA_HISTOGRAM_BOOLEAN("SimpleCache.Eviction.Result", result == net::OK);
UMA_HISTOGRAM_TIMES("SimpleCache.Eviction.TimeToDone",
base::TimeTicks::Now() - eviction_start_time_);
UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.SizeWhenDone2",
cache_size_ / kBytesInKb);
}
// static
void SimpleIndex::InsertInEntrySet(
uint64 hash_key,
const disk_cache::EntryMetadata& entry_metadata,
EntrySet* entry_set) {
DCHECK(entry_set);
entry_set->insert(std::make_pair(hash_key, entry_metadata));
}
void SimpleIndex::PostponeWritingToDisk() {
if (!initialized_)
return;
const int delay = app_on_background_ ? background_flush_delay_
: foreground_flush_delay_;
// If the timer is already active, Start() will just Reset it, postponing it.
write_to_disk_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(delay), write_to_disk_cb_);
}
void SimpleIndex::UpdateEntryIteratorSize(EntrySet::iterator* it,
uint64 entry_size) {
// Update the total cache size with the new entry size.
DCHECK(io_thread_checker_.CalledOnValidThread());
DCHECK_GE(cache_size_, (*it)->second.GetEntrySize());
cache_size_ -= (*it)->second.GetEntrySize();
cache_size_ += entry_size;
(*it)->second.SetEntrySize(entry_size);
}
void SimpleIndex::MergeInitializingSet(
scoped_ptr<SimpleIndexLoadResult> load_result) {
DCHECK(io_thread_checker_.CalledOnValidThread());
DCHECK(load_result->did_load);
SimpleIndex::EntrySet* index_file_entries = &load_result->entries;
// First, remove the entries that are in the |removed_entries_| from both
// sets.
for (base::hash_set<uint64>::const_iterator it =
removed_entries_.begin(); it != removed_entries_.end(); ++it) {
entries_set_.erase(*it);
index_file_entries->erase(*it);
}
for (EntrySet::const_iterator it = entries_set_.begin();
it != entries_set_.end(); ++it) {
const uint64 entry_hash = it->first;
std::pair<EntrySet::iterator, bool> insert_result =
index_file_entries->insert(EntrySet::value_type(entry_hash,
EntryMetadata()));
EntrySet::iterator& possibly_inserted_entry = insert_result.first;
possibly_inserted_entry->second = it->second;
}
uint64 merged_cache_size = 0;
for (EntrySet::iterator it = index_file_entries->begin();
it != index_file_entries->end(); ++it) {
merged_cache_size += it->second.GetEntrySize();
}
entries_set_.swap(*index_file_entries);
cache_size_ = merged_cache_size;
initialized_ = true;
removed_entries_.clear();
// The actual IO is asynchronous, so calling WriteToDisk() shouldn't slow the
// merge down much.
if (load_result->flush_required)
WriteToDisk();
UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.IndexInitializationWaiters",
to_run_when_initialized_.size(), 0, 100, 20);
// Run all callbacks waiting for the index to come up.
for (CallbackList::iterator it = to_run_when_initialized_.begin(),
end = to_run_when_initialized_.end(); it != end; ++it) {
io_thread_->PostTask(FROM_HERE, base::Bind((*it), net::OK));
}
to_run_when_initialized_.clear();
}
#if defined(OS_ANDROID)
void SimpleIndex::OnActivityStateChange(
base::android::ActivityState state) {
DCHECK(io_thread_checker_.CalledOnValidThread());
// For more info about android activities, see:
// developer.android.com/training/basics/activity-lifecycle/pausing.html
// These values are defined in the file ActivityStatus.java
if (state == base::android::ACTIVITY_STATE_RESUMED) {
app_on_background_ = false;
} else if (state == base::android::ACTIVITY_STATE_STOPPED) {
app_on_background_ = true;
WriteToDisk();
}
}
#endif
void SimpleIndex::WriteToDisk() {
DCHECK(io_thread_checker_.CalledOnValidThread());
if (!initialized_)
return;
UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.IndexNumEntriesOnWrite",
entries_set_.size(), 0, 100000, 50);
const base::TimeTicks start = base::TimeTicks::Now();
if (!last_write_to_disk_.is_null()) {
if (app_on_background_) {
UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexWriteInterval.Background",
start - last_write_to_disk_);
} else {
UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexWriteInterval.Foreground",
start - last_write_to_disk_);
}
}
last_write_to_disk_ = start;
index_file_->WriteToDisk(entries_set_, cache_size_,
start, app_on_background_);
}
scoped_ptr<SimpleIndex::HashList> SimpleIndex::ExtractEntriesBetween(
const base::Time initial_time, const base::Time end_time,
bool delete_entries) {
DCHECK_EQ(true, initialized_);
const base::Time extended_end_time =
end_time.is_null() ? base::Time::Max() : end_time;
DCHECK(extended_end_time >= initial_time);
scoped_ptr<HashList> ret_hashes(new HashList());
for (EntrySet::iterator it = entries_set_.begin(), end = entries_set_.end();
it != end;) {
EntryMetadata& metadata = it->second;
base::Time entry_time = metadata.GetLastUsedTime();
if (initial_time <= entry_time && entry_time < extended_end_time) {
ret_hashes->push_back(it->first);
if (delete_entries) {
cache_size_ -= metadata.GetEntrySize();
entries_set_.erase(it++);
continue;
}
}
++it;
}
return ret_hashes.Pass();
}
} // namespace disk_cache