Index: net/disk_cache/backend_impl.cc |
=================================================================== |
--- net/disk_cache/backend_impl.cc (revision 11078) |
+++ net/disk_cache/backend_impl.cc (working copy) |
@@ -17,6 +17,9 @@ |
#include "net/disk_cache/hash.h" |
#include "net/disk_cache/file.h" |
+// Uncomment this to use the new eviction algorithm. |
+// #define USE_NEW_EVICTION |
+ |
using base::Time; |
using base::TimeDelta; |
@@ -184,6 +187,10 @@ |
if (init_) |
return false; |
+#ifdef USE_NEW_EVICTION |
+ new_eviction_ = true; |
+#endif |
+ |
bool create_files = false; |
if (!InitBackingStore(&create_files)) { |
ReportError(ERR_STORAGE_ERROR); |
@@ -231,7 +238,7 @@ |
if (!stats_.Init(this, &data_->header.stats)) |
return false; |
- disabled_ = !rankings_.Init(this); |
+ disabled_ = !rankings_.Init(this, new_eviction_); |
eviction_.Init(this); |
return !disabled_; |
@@ -256,7 +263,16 @@ |
int32 BackendImpl::GetEntryCount() const { |
if (!index_) |
return 0; |
- return data_->header.num_entries; |
+ // num_entries includes entries already evicted. |
+ int32 not_deleted = data_->header.num_entries - |
+ data_->header.lru.sizes[Rankings::DELETED]; |
+ |
+ if (not_deleted < 0) { |
+ NOTREACHED(); |
+ not_deleted = 0; |
+ } |
+ |
+ return not_deleted; |
} |
bool BackendImpl::OpenEntry(const std::string& key, Entry** entry) { |
@@ -272,6 +288,13 @@ |
return false; |
} |
+ if (ENTRY_NORMAL != cache_entry->entry()->Data()->state) { |
+ // The entry was already evicted. |
+ cache_entry->Release(); |
+ stats_.OnEvent(Stats::OPEN_MISS); |
+ return false; |
+ } |
+ |
eviction_.OnOpenEntry(cache_entry); |
DCHECK(entry); |
*entry = cache_entry; |
@@ -285,16 +308,24 @@ |
if (disabled_ || key.empty()) |
return false; |
+ DCHECK(entry); |
+ *entry = NULL; |
+ |
Time start = Time::Now(); |
uint32 hash = Hash(key); |
scoped_refptr<EntryImpl> parent; |
Addr entry_address(data_->table[hash & mask_]); |
if (entry_address.is_initialized()) { |
+ // We have an entry already. It could be the one we are looking for, or just |
+ // a hash conflict. |
+ EntryImpl* old_entry = MatchEntry(key, hash, false); |
+ if (old_entry) |
+ return ResurrectEntry(old_entry, entry); |
+ |
EntryImpl* parent_entry = MatchEntry(key, hash, true); |
if (!parent_entry) { |
- stats_.OnEvent(Stats::CREATE_MISS); |
- Trace("create entry miss "); |
+ NOTREACHED(); |
return false; |
} |
parent.swap(&parent_entry); |
@@ -339,14 +370,11 @@ |
block_files_.GetFile(entry_address)->Store(cache_entry->entry()); |
block_files_.GetFile(node_address)->Store(cache_entry->rankings()); |
- data_->header.num_entries++; |
- DCHECK(data_->header.num_entries > 0); |
+ IncreaseNumEntries(); |
eviction_.OnCreateEntry(cache_entry); |
if (!parent.get()) |
data_->table[hash & mask_] = entry_address.value(); |
- DCHECK(entry); |
- *entry = NULL; |
cache_entry.swap(reinterpret_cast<EntryImpl**>(entry)); |
UMA_HISTOGRAM_TIMES("DiskCache.CreateTime", Time::Now() - start); |
@@ -451,8 +479,8 @@ |
} |
void BackendImpl::EndEnumeration(void** iter) { |
- Rankings::ScopedRankingsBlock rankings(&rankings_, |
- reinterpret_cast<CacheRankingsBlock*>(*iter)); |
+ scoped_ptr<Rankings::Iterator> iterator( |
+ reinterpret_cast<Rankings::Iterator*>(*iter)); |
*iter = NULL; |
} |
@@ -600,11 +628,26 @@ |
data_->table[hash & mask_] = child; |
} |
- data_->header.num_entries--; |
- DCHECK(data_->header.num_entries >= 0); |
+ if (!new_eviction_) { |
+ DecreaseNumEntries(); |
+ } |
+ |
stats_.OnEvent(Stats::DOOM_ENTRY); |
} |
+// An entry may be linked on the DELETED list for a while after being doomed. |
+// This function is called when we want to remove it. |
+void BackendImpl::RemoveEntry(EntryImpl* entry) { |
+ if (!new_eviction_) |
+ return; |
+ |
+ DCHECK(ENTRY_DOOMED == entry->entry()->Data()->state); |
+ |
+ Trace("Remove entry 0x%p", entry); |
+ eviction_.OnDestroyEntry(entry); |
+ DecreaseNumEntries(); |
+} |
+ |
void BackendImpl::CacheEntryDestroyed() { |
DecreaseNumRefs(); |
} |
@@ -742,6 +785,10 @@ |
IndexHeader header; |
header.table_len = DesiredIndexTableLen(max_size_); |
+ // We need file version 2.1 for the new eviction algorithm. |
+ if (new_eviction_) |
+ header.version = 0x20001; |
+ |
if (!file->Write(&header, sizeof(header), 0)) |
return false; |
@@ -962,49 +1009,153 @@ |
if (disabled_) |
return false; |
- Rankings::ScopedRankingsBlock rankings(&rankings_, |
- reinterpret_cast<CacheRankingsBlock*>(*iter)); |
- CacheRankingsBlock* next_block = forward ? |
- rankings_.GetNext(rankings.get(), Rankings::NO_USE) : |
- rankings_.GetPrev(rankings.get(), Rankings::NO_USE); |
- Rankings::ScopedRankingsBlock next(&rankings_, next_block); |
+ DCHECK(iter); |
+ DCHECK(next_entry); |
*next_entry = NULL; |
+ |
+ const int kListsToSearch = 3; |
+ scoped_refptr<EntryImpl> entries[kListsToSearch]; |
+ scoped_ptr<Rankings::Iterator> iterator( |
+ reinterpret_cast<Rankings::Iterator*>(*iter)); |
*iter = NULL; |
- if (!next.get()) |
+ |
+ if (!iterator.get()) { |
+ iterator.reset(new Rankings::Iterator(&rankings_)); |
+ bool ret = false; |
+ |
+ // Get an entry from each list. |
+ for (int i = 0; i < kListsToSearch; i++) { |
+ EntryImpl* temp = NULL; |
+ ret |= OpenFollowingEntryFromList(forward, static_cast<Rankings::List>(i), |
+ &iterator->nodes[i], &temp); |
+ entries[i].swap(&temp); // The entry was already addref'd. |
+ } |
+ if (!ret) |
+ return false; |
+ } else { |
+ // Get the next entry from the last list, and the actual entries for the |
+ // elements on the other lists. |
+ for (int i = 0; i < kListsToSearch; i++) { |
+ EntryImpl* temp = NULL; |
+ if (iterator->list == i) { |
+ OpenFollowingEntryFromList(forward, iterator->list, |
+ &iterator->nodes[i], &temp); |
+ } else { |
+ temp = GetEnumeratedEntry(iterator->nodes[i]); |
+ } |
+ |
+ entries[i].swap(&temp); // The entry was already addref'd. |
+ } |
+ } |
+ |
+ int newest = -1; |
+ int oldest = -1; |
+ Time access_times[kListsToSearch]; |
+ for (int i = 0; i < kListsToSearch; i++) { |
+ if (entries[i].get()) { |
+ access_times[i] = entries[i]->GetLastUsed(); |
+ if (newest < 0) { |
+ DCHECK(oldest < 0); |
+ newest = oldest = i; |
+ continue; |
+ } |
+ if (access_times[i] > access_times[newest]) |
+ newest = i; |
+ if (access_times[i] < access_times[oldest]) |
+ oldest = i; |
+ } |
+ } |
+ |
+ if (newest < 0 || oldest < 0) |
return false; |
+ if (forward) { |
+ entries[newest].swap(reinterpret_cast<EntryImpl**>(next_entry)); |
+ iterator->list = static_cast<Rankings::List>(newest); |
+ } else { |
+ entries[oldest].swap(reinterpret_cast<EntryImpl**>(next_entry)); |
+ iterator->list = static_cast<Rankings::List>(oldest); |
+ } |
+ |
+ *iter = iterator.release(); |
+ return true; |
+} |
+ |
+bool BackendImpl::OpenFollowingEntryFromList(bool forward, Rankings::List list, |
+ CacheRankingsBlock** from_entry, |
+ EntryImpl** next_entry) { |
+ if (disabled_) |
+ return false; |
+ |
+ if (!new_eviction_ && Rankings::NO_USE != list) |
+ return false; |
+ |
+ Rankings::ScopedRankingsBlock rankings(&rankings_, *from_entry); |
+ CacheRankingsBlock* next_block = forward ? |
+ rankings_.GetNext(rankings.get(), list) : |
+ rankings_.GetPrev(rankings.get(), list); |
+ Rankings::ScopedRankingsBlock next(&rankings_, next_block); |
+ *from_entry = NULL; |
+ |
+ *next_entry = GetEnumeratedEntry(next.get()); |
+ if (!*next_entry) |
+ return false; |
+ |
+ *from_entry = next.release(); |
+ return true; |
+} |
+ |
+EntryImpl* BackendImpl::GetEnumeratedEntry(CacheRankingsBlock* next) { |
+ if (!next) |
+ return NULL; |
+ |
scoped_refptr<EntryImpl> entry; |
+ EntryImpl* temp = NULL; |
+ |
if (next->Data()->pointer) { |
entry = reinterpret_cast<EntryImpl*>(next->Data()->pointer); |
- } else { |
- bool dirty; |
- EntryImpl* temp = NULL; |
- if (NewEntry(Addr(next->Data()->contents), &temp, &dirty)) |
- return false; |
entry.swap(&temp); |
+ return temp; |
+ } |
- if (dirty) { |
- // We cannot trust this entry. Call MatchEntry to go through the regular |
- // path and take the appropriate action. |
- std::string key = entry->GetKey(); |
- uint32 hash = entry->GetHash(); |
- entry = NULL; // Release the entry. |
- temp = MatchEntry(key, hash, false); |
- if (temp) |
- temp->Release(); |
+ bool dirty; |
+ if (NewEntry(Addr(next->Data()->contents), &temp, &dirty)) |
+ return NULL; |
+ entry.swap(&temp); |
- return false; |
- } |
+ if (dirty) { |
+ // We cannot trust this entry. Call MatchEntry to go through the regular |
+ // path and take the appropriate action. |
+ std::string key = entry->GetKey(); |
+ uint32 hash = entry->GetHash(); |
+ entry = NULL; // Release the entry. |
+ temp = MatchEntry(key, hash, false); |
+ if (temp) |
+ temp->Release(); |
- entry.swap(&temp); |
- temp = EntryImpl::Update(temp); // Update returns an adref'd entry. |
- entry.swap(&temp); |
- if (!entry.get()) |
- return false; |
+ return NULL; |
} |
- entry.swap(reinterpret_cast<EntryImpl**>(next_entry)); |
- *iter = next.release(); |
+ entry.swap(&temp); |
+ return EntryImpl::Update(temp); // Update returns an adref'd entry. |
+} |
+ |
+bool BackendImpl::ResurrectEntry(EntryImpl* deleted_entry, Entry** entry) { |
+ if (ENTRY_NORMAL == deleted_entry->entry()->Data()->state) { |
+ deleted_entry->Release(); |
+ stats_.OnEvent(Stats::CREATE_MISS); |
+ Trace("create entry miss "); |
+ return false; |
+ } |
+ |
+ // We are attempting to create an entry and found out that the entry was |
+ // previously deleted. |
+ |
+ eviction_.OnCreateEntry(deleted_entry); |
+ *entry = deleted_entry; |
+ |
+ stats_.OnEvent(Stats::CREATE_HIT); |
+ Trace("Resurrect entry hit "); |
return true; |
} |
@@ -1017,8 +1168,8 @@ |
eviction_.OnDoomEntry(entry); |
entry->InternalDoom(); |
- data_->header.num_entries--; |
- DCHECK(data_->header.num_entries >= 0); |
+ if (!new_eviction_) |
+ DecreaseNumEntries(); |
stats_.OnEvent(Stats::INVALID_ENTRY); |
} |
@@ -1049,6 +1200,19 @@ |
RestartCache(); |
} |
+void BackendImpl::IncreaseNumEntries() { |
+ data_->header.num_entries++; |
+ DCHECK(data_->header.num_entries > 0); |
+} |
+ |
+void BackendImpl::DecreaseNumEntries() { |
+ data_->header.num_entries--; |
+ if (data_->header.num_entries < 0) { |
+ NOTREACHED(); |
+ data_->header.num_entries = 0; |
+ } |
+} |
+ |
void BackendImpl::LogStats() { |
StatsItems stats; |
GetStats(&stats); |
@@ -1058,6 +1222,14 @@ |
} |
} |
+void BackendImpl::UpgradeTo2_1() { |
+ // 2.1 is basically the same as 2.0, except that new fields are actually |
+ // updated by the new eviction algorithm. |
+ DCHECK(0x20000 == data_->header.version); |
+ data_->header.version = 0x20001; |
+ data_->header.lru.sizes[Rankings::NO_USE] = data_->header.num_entries; |
+} |
+ |
bool BackendImpl::CheckIndex() { |
if (!data_) { |
LOG(ERROR) << "Unable to map Index file"; |
@@ -1070,10 +1242,23 @@ |
return false; |
} |
- if (kIndexMagic != data_->header.magic || |
- kCurrentVersion != data_->header.version) { |
- LOG(ERROR) << "Invalid file version or magic"; |
- return false; |
+ if (new_eviction_) { |
+ // We support versions 2.0 and 2.1, upgrading 2.0 to 2.1. |
+ if (kIndexMagic != data_->header.magic || |
+ kCurrentVersion >> 16 != data_->header.version >> 16) { |
+ LOG(ERROR) << "Invalid file version or magic"; |
+ return false; |
+ } |
+ if (kCurrentVersion == data_->header.version) { |
+ // We need file version 2.1 for the new eviction algorithm. |
+ UpgradeTo2_1(); |
+ } |
+ } else { |
+ if (kIndexMagic != data_->header.magic || |
+ kCurrentVersion != data_->header.version) { |
+ LOG(ERROR) << "Invalid file version or magic"; |
+ return false; |
+ } |
} |
if (!data_->header.table_len) { |