Windows should animate when they are about to get docked at screen edges.
[chromium-blink-merge.git] / net / disk_cache / simple / simple_index.cc
blob78ce87ed71d6c9006f3b67cdfc5575d3bc9439b3
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/disk_cache/simple/simple_index.h"
7 #include <utility>
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/file_util.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/logging.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/metrics/field_trial.h"
16 #include "base/metrics/histogram.h"
17 #include "base/pickle.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_tokenizer.h"
20 #include "base/task_runner.h"
21 #include "base/threading/worker_pool.h"
22 #include "base/time/time.h"
23 #include "net/base/net_errors.h"
24 #include "net/disk_cache/simple/simple_entry_format.h"
25 #include "net/disk_cache/simple/simple_index_file.h"
26 #include "net/disk_cache/simple/simple_synchronous_entry.h"
27 #include "net/disk_cache/simple/simple_util.h"
29 #if defined(OS_POSIX)
30 #include <sys/stat.h>
31 #include <sys/time.h>
32 #endif
34 namespace {
36 // How many milliseconds we delay writing the index to disk since the last cache
37 // operation has happened.
38 const int kDefaultWriteToDiskDelayMSecs = 20000;
39 const int kDefaultWriteToDiskOnBackgroundDelayMSecs = 100;
41 // Divides the cache space into this amount of parts to evict when only one part
42 // is left.
43 const uint32 kEvictionMarginDivisor = 20;
45 const uint32 kBytesInKb = 1024;
47 // Utility class used for timestamp comparisons in entry metadata while sorting.
48 class CompareHashesForTimestamp {
49 typedef disk_cache::SimpleIndex SimpleIndex;
50 typedef disk_cache::SimpleIndex::EntrySet EntrySet;
51 public:
52 explicit CompareHashesForTimestamp(const EntrySet& set);
54 bool operator()(uint64 hash1, uint64 hash2);
55 private:
56 const EntrySet& entry_set_;
59 CompareHashesForTimestamp::CompareHashesForTimestamp(const EntrySet& set)
60 : entry_set_(set) {
63 bool CompareHashesForTimestamp::operator()(uint64 hash1, uint64 hash2) {
64 EntrySet::const_iterator it1 = entry_set_.find(hash1);
65 DCHECK(it1 != entry_set_.end());
66 EntrySet::const_iterator it2 = entry_set_.find(hash2);
67 DCHECK(it2 != entry_set_.end());
68 return it1->second.GetLastUsedTime() < it2->second.GetLastUsedTime();
71 } // namespace
73 namespace disk_cache {
75 EntryMetadata::EntryMetadata() : last_used_time_(0), entry_size_(0) {}
77 EntryMetadata::EntryMetadata(base::Time last_used_time, uint64 entry_size)
78 : last_used_time_(last_used_time.ToInternalValue()),
79 entry_size_(entry_size) {}
81 base::Time EntryMetadata::GetLastUsedTime() const {
82 return base::Time::FromInternalValue(last_used_time_);
85 void EntryMetadata::SetLastUsedTime(const base::Time& last_used_time) {
86 last_used_time_ = last_used_time.ToInternalValue();
89 void EntryMetadata::Serialize(Pickle* pickle) const {
90 DCHECK(pickle);
91 COMPILE_ASSERT(sizeof(EntryMetadata) == (sizeof(int64) + sizeof(uint64)),
92 EntryMetadata_has_two_member_variables);
93 pickle->WriteInt64(last_used_time_);
94 pickle->WriteUInt64(entry_size_);
97 bool EntryMetadata::Deserialize(PickleIterator* it) {
98 DCHECK(it);
99 return it->ReadInt64(&last_used_time_) && it->ReadUInt64(&entry_size_);
102 SimpleIndex::SimpleIndex(base::SingleThreadTaskRunner* io_thread,
103 const base::FilePath& cache_directory,
104 scoped_ptr<SimpleIndexFile> index_file)
105 : cache_size_(0),
106 max_size_(0),
107 high_watermark_(0),
108 low_watermark_(0),
109 eviction_in_progress_(false),
110 initialized_(false),
111 cache_directory_(cache_directory),
112 index_file_(index_file.Pass()),
113 io_thread_(io_thread),
114 // Creating the callback once so it is reused every time
115 // write_to_disk_timer_.Start() is called.
116 write_to_disk_cb_(base::Bind(&SimpleIndex::WriteToDisk, AsWeakPtr())),
117 app_on_background_(false) {}
119 SimpleIndex::~SimpleIndex() {
120 DCHECK(io_thread_checker_.CalledOnValidThread());
122 // Fail all callbacks waiting for the index to come up.
123 for (CallbackList::iterator it = to_run_when_initialized_.begin(),
124 end = to_run_when_initialized_.end(); it != end; ++it) {
125 it->Run(net::ERR_ABORTED);
129 void SimpleIndex::Initialize(base::Time cache_mtime) {
130 DCHECK(io_thread_checker_.CalledOnValidThread());
132 // Take the foreground and background index flush delays from the experiment
133 // settings only if both are valid.
134 foreground_flush_delay_ = kDefaultWriteToDiskDelayMSecs;
135 background_flush_delay_ = kDefaultWriteToDiskOnBackgroundDelayMSecs;
136 const std::string index_flush_intervals = base::FieldTrialList::FindFullName(
137 "SimpleCacheIndexFlushDelay_Foreground_Background");
138 if (!index_flush_intervals.empty()) {
139 base::StringTokenizer tokens(index_flush_intervals, "_");
140 int foreground_delay, background_delay;
141 if (tokens.GetNext() &&
142 base::StringToInt(tokens.token(), &foreground_delay) &&
143 tokens.GetNext() &&
144 base::StringToInt(tokens.token(), &background_delay)) {
145 foreground_flush_delay_ = foreground_delay;
146 background_flush_delay_ = background_delay;
150 #if defined(OS_ANDROID)
151 activity_status_listener_.reset(new base::android::ActivityStatus::Listener(
152 base::Bind(&SimpleIndex::OnActivityStateChange, AsWeakPtr())));
153 #endif
155 SimpleIndexLoadResult* load_result = new SimpleIndexLoadResult();
156 scoped_ptr<SimpleIndexLoadResult> load_result_scoped(load_result);
157 base::Closure reply = base::Bind(
158 &SimpleIndex::MergeInitializingSet,
159 AsWeakPtr(),
160 base::Passed(&load_result_scoped));
161 index_file_->LoadIndexEntries(cache_mtime, reply, load_result);
164 bool SimpleIndex::SetMaxSize(int max_bytes) {
165 if (max_bytes < 0)
166 return false;
168 // Zero size means use the default.
169 if (!max_bytes)
170 return true;
172 max_size_ = max_bytes;
173 high_watermark_ = max_size_ - max_size_ / kEvictionMarginDivisor;
174 low_watermark_ = max_size_ - 2 * (max_size_ / kEvictionMarginDivisor);
175 return true;
178 int SimpleIndex::ExecuteWhenReady(const net::CompletionCallback& task) {
179 DCHECK(io_thread_checker_.CalledOnValidThread());
180 if (initialized_)
181 io_thread_->PostTask(FROM_HERE, base::Bind(task, net::OK));
182 else
183 to_run_when_initialized_.push_back(task);
184 return net::ERR_IO_PENDING;
187 scoped_ptr<SimpleIndex::HashList> SimpleIndex::RemoveEntriesBetween(
188 const base::Time initial_time, const base::Time end_time) {
189 return ExtractEntriesBetween(initial_time, end_time, true);
192 scoped_ptr<SimpleIndex::HashList> SimpleIndex::GetAllHashes() {
193 const base::Time null_time = base::Time();
194 return ExtractEntriesBetween(null_time, null_time, false);
197 int32 SimpleIndex::GetEntryCount() const {
198 // TODO(pasko): return a meaningful initial estimate before initialized.
199 return entries_set_.size();
202 void SimpleIndex::Insert(const std::string& key) {
203 DCHECK(io_thread_checker_.CalledOnValidThread());
204 // Upon insert we don't know yet the size of the entry.
205 // It will be updated later when the SimpleEntryImpl finishes opening or
206 // creating the new entry, and then UpdateEntrySize will be called.
207 const uint64 hash_key = simple_util::GetEntryHashKey(key);
208 InsertInEntrySet(
209 hash_key, EntryMetadata(base::Time::Now(), 0), &entries_set_);
210 if (!initialized_)
211 removed_entries_.erase(hash_key);
212 PostponeWritingToDisk();
215 void SimpleIndex::Remove(const std::string& key) {
216 DCHECK(io_thread_checker_.CalledOnValidThread());
217 const uint64 hash_key = simple_util::GetEntryHashKey(key);
218 EntrySet::iterator it = entries_set_.find(hash_key);
219 if (it != entries_set_.end()) {
220 UpdateEntryIteratorSize(&it, 0);
221 entries_set_.erase(it);
224 if (!initialized_)
225 removed_entries_.insert(hash_key);
226 PostponeWritingToDisk();
229 bool SimpleIndex::Has(uint64 hash) const {
230 DCHECK(io_thread_checker_.CalledOnValidThread());
231 // If not initialized, always return true, forcing it to go to the disk.
232 return !initialized_ || entries_set_.count(hash) > 0;
235 bool SimpleIndex::UseIfExists(const std::string& key) {
236 DCHECK(io_thread_checker_.CalledOnValidThread());
237 // Always update the last used time, even if it is during initialization.
238 // It will be merged later.
239 EntrySet::iterator it = entries_set_.find(simple_util::GetEntryHashKey(key));
240 if (it == entries_set_.end())
241 // If not initialized, always return true, forcing it to go to the disk.
242 return !initialized_;
243 it->second.SetLastUsedTime(base::Time::Now());
244 PostponeWritingToDisk();
245 return true;
248 void SimpleIndex::StartEvictionIfNeeded() {
249 DCHECK(io_thread_checker_.CalledOnValidThread());
250 if (eviction_in_progress_ || cache_size_ <= high_watermark_)
251 return;
253 // Take all live key hashes from the index and sort them by time.
254 eviction_in_progress_ = true;
255 eviction_start_time_ = base::TimeTicks::Now();
256 UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.CacheSizeOnStart2",
257 cache_size_ / kBytesInKb);
258 UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.MaxCacheSizeOnStart2",
259 max_size_ / kBytesInKb);
260 scoped_ptr<std::vector<uint64> > entry_hashes(new std::vector<uint64>());
261 for (EntrySet::const_iterator it = entries_set_.begin(),
262 end = entries_set_.end(); it != end; ++it) {
263 entry_hashes->push_back(it->first);
265 std::sort(entry_hashes->begin(), entry_hashes->end(),
266 CompareHashesForTimestamp(entries_set_));
268 // Remove as many entries from the index to get below |low_watermark_|.
269 std::vector<uint64>::iterator it = entry_hashes->begin();
270 uint64 evicted_so_far_size = 0;
271 while (evicted_so_far_size < cache_size_ - low_watermark_) {
272 DCHECK(it != entry_hashes->end());
273 EntrySet::iterator found_meta = entries_set_.find(*it);
274 DCHECK(found_meta != entries_set_.end());
275 uint64 to_evict_size = found_meta->second.GetEntrySize();
276 evicted_so_far_size += to_evict_size;
277 entries_set_.erase(found_meta);
278 ++it;
280 cache_size_ -= evicted_so_far_size;
282 // Take out the rest of hashes from the eviction list.
283 entry_hashes->erase(it, entry_hashes->end());
284 UMA_HISTOGRAM_COUNTS("SimpleCache.Eviction.EntryCount", entry_hashes->size());
285 UMA_HISTOGRAM_TIMES("SimpleCache.Eviction.TimeToSelectEntries",
286 base::TimeTicks::Now() - eviction_start_time_);
287 UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.SizeOfEvicted2",
288 evicted_so_far_size / kBytesInKb);
290 index_file_->DoomEntrySet(
291 entry_hashes.Pass(),
292 base::Bind(&SimpleIndex::EvictionDone, AsWeakPtr()));
295 bool SimpleIndex::UpdateEntrySize(const std::string& key, uint64 entry_size) {
296 DCHECK(io_thread_checker_.CalledOnValidThread());
297 EntrySet::iterator it = entries_set_.find(simple_util::GetEntryHashKey(key));
298 if (it == entries_set_.end())
299 return false;
301 UpdateEntryIteratorSize(&it, entry_size);
302 PostponeWritingToDisk();
303 StartEvictionIfNeeded();
304 return true;
307 void SimpleIndex::EvictionDone(int result) {
308 DCHECK(io_thread_checker_.CalledOnValidThread());
310 // Ignore the result of eviction. We did our best.
311 eviction_in_progress_ = false;
312 UMA_HISTOGRAM_BOOLEAN("SimpleCache.Eviction.Result", result == net::OK);
313 UMA_HISTOGRAM_TIMES("SimpleCache.Eviction.TimeToDone",
314 base::TimeTicks::Now() - eviction_start_time_);
315 UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.SizeWhenDone2",
316 cache_size_ / kBytesInKb);
319 // static
320 void SimpleIndex::InsertInEntrySet(
321 uint64 hash_key,
322 const disk_cache::EntryMetadata& entry_metadata,
323 EntrySet* entry_set) {
324 DCHECK(entry_set);
325 entry_set->insert(std::make_pair(hash_key, entry_metadata));
328 void SimpleIndex::PostponeWritingToDisk() {
329 if (!initialized_)
330 return;
331 const int delay = app_on_background_ ? background_flush_delay_
332 : foreground_flush_delay_;
333 // If the timer is already active, Start() will just Reset it, postponing it.
334 write_to_disk_timer_.Start(
335 FROM_HERE, base::TimeDelta::FromMilliseconds(delay), write_to_disk_cb_);
338 void SimpleIndex::UpdateEntryIteratorSize(EntrySet::iterator* it,
339 uint64 entry_size) {
340 // Update the total cache size with the new entry size.
341 DCHECK(io_thread_checker_.CalledOnValidThread());
342 DCHECK_GE(cache_size_, (*it)->second.GetEntrySize());
343 cache_size_ -= (*it)->second.GetEntrySize();
344 cache_size_ += entry_size;
345 (*it)->second.SetEntrySize(entry_size);
348 void SimpleIndex::MergeInitializingSet(
349 scoped_ptr<SimpleIndexLoadResult> load_result) {
350 DCHECK(io_thread_checker_.CalledOnValidThread());
351 DCHECK(load_result->did_load);
353 SimpleIndex::EntrySet* index_file_entries = &load_result->entries;
354 // First, remove the entries that are in the |removed_entries_| from both
355 // sets.
356 for (base::hash_set<uint64>::const_iterator it =
357 removed_entries_.begin(); it != removed_entries_.end(); ++it) {
358 entries_set_.erase(*it);
359 index_file_entries->erase(*it);
362 for (EntrySet::const_iterator it = entries_set_.begin();
363 it != entries_set_.end(); ++it) {
364 const uint64 entry_hash = it->first;
365 std::pair<EntrySet::iterator, bool> insert_result =
366 index_file_entries->insert(EntrySet::value_type(entry_hash,
367 EntryMetadata()));
368 EntrySet::iterator& possibly_inserted_entry = insert_result.first;
369 possibly_inserted_entry->second = it->second;
372 uint64 merged_cache_size = 0;
373 for (EntrySet::iterator it = index_file_entries->begin();
374 it != index_file_entries->end(); ++it) {
375 merged_cache_size += it->second.GetEntrySize();
378 entries_set_.swap(*index_file_entries);
379 cache_size_ = merged_cache_size;
380 initialized_ = true;
381 removed_entries_.clear();
383 // The actual IO is asynchronous, so calling WriteToDisk() shouldn't slow the
384 // merge down much.
385 if (load_result->flush_required)
386 WriteToDisk();
388 UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.IndexInitializationWaiters",
389 to_run_when_initialized_.size(), 0, 100, 20);
390 // Run all callbacks waiting for the index to come up.
391 for (CallbackList::iterator it = to_run_when_initialized_.begin(),
392 end = to_run_when_initialized_.end(); it != end; ++it) {
393 io_thread_->PostTask(FROM_HERE, base::Bind((*it), net::OK));
395 to_run_when_initialized_.clear();
398 #if defined(OS_ANDROID)
399 void SimpleIndex::OnActivityStateChange(
400 base::android::ActivityState state) {
401 DCHECK(io_thread_checker_.CalledOnValidThread());
402 // For more info about android activities, see:
403 // developer.android.com/training/basics/activity-lifecycle/pausing.html
404 // These values are defined in the file ActivityStatus.java
405 if (state == base::android::ACTIVITY_STATE_RESUMED) {
406 app_on_background_ = false;
407 } else if (state == base::android::ACTIVITY_STATE_STOPPED) {
408 app_on_background_ = true;
409 WriteToDisk();
412 #endif
414 void SimpleIndex::WriteToDisk() {
415 DCHECK(io_thread_checker_.CalledOnValidThread());
416 if (!initialized_)
417 return;
418 UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.IndexNumEntriesOnWrite",
419 entries_set_.size(), 0, 100000, 50);
420 const base::TimeTicks start = base::TimeTicks::Now();
421 if (!last_write_to_disk_.is_null()) {
422 if (app_on_background_) {
423 UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexWriteInterval.Background",
424 start - last_write_to_disk_);
425 } else {
426 UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexWriteInterval.Foreground",
427 start - last_write_to_disk_);
430 last_write_to_disk_ = start;
432 index_file_->WriteToDisk(entries_set_, cache_size_,
433 start, app_on_background_);
436 scoped_ptr<SimpleIndex::HashList> SimpleIndex::ExtractEntriesBetween(
437 const base::Time initial_time, const base::Time end_time,
438 bool delete_entries) {
439 DCHECK_EQ(true, initialized_);
440 const base::Time extended_end_time =
441 end_time.is_null() ? base::Time::Max() : end_time;
442 DCHECK(extended_end_time >= initial_time);
443 scoped_ptr<HashList> ret_hashes(new HashList());
444 for (EntrySet::iterator it = entries_set_.begin(), end = entries_set_.end();
445 it != end;) {
446 EntryMetadata& metadata = it->second;
447 base::Time entry_time = metadata.GetLastUsedTime();
448 if (initial_time <= entry_time && entry_time < extended_end_time) {
449 ret_hashes->push_back(it->first);
450 if (delete_entries) {
451 cache_size_ -= metadata.GetEntrySize();
452 entries_set_.erase(it++);
453 continue;
456 ++it;
458 return ret_hashes.Pass();
461 } // namespace disk_cache