| 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) {
|
|
|