Home | History | Annotate | Download | only in disk_cache
      1 // Copyright (c) 2006-2008 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.
      4 
      5 #include "net/disk_cache/rankings.h"
      6 
      7 #include "base/histogram.h"
      8 #include "net/disk_cache/backend_impl.h"
      9 #include "net/disk_cache/entry_impl.h"
     10 #include "net/disk_cache/errors.h"
     11 #include "net/disk_cache/histogram_macros.h"
     12 
     13 using base::Time;
     14 
     15 // This is used by crash_cache.exe to generate unit test files.
     16 disk_cache::RankCrashes g_rankings_crash = disk_cache::NO_CRASH;
     17 
     18 namespace {
     19 
     20 enum Operation {
     21   INSERT = 1,
     22   REMOVE
     23 };
     24 
     25 // This class provides a simple lock for the LRU list of rankings. Whenever an
     26 // entry is to be inserted or removed from the list, a transaction object should
     27 // be created to keep track of the operation. If the process crashes before
     28 // finishing the operation, the transaction record (stored as part of the user
     29 // data on the file header) can be used to finish the operation.
     30 class Transaction {
     31  public:
     32   // addr is the cache addres of the node being inserted or removed. We want to
     33   // avoid having the compiler doing optimizations on when to read or write
     34   // from user_data because it is the basis of the crash detection. Maybe
     35   // volatile is not enough for that, but it should be a good hint.
     36   Transaction(volatile disk_cache::LruData* data, disk_cache::Addr addr,
     37               Operation op, int list);
     38   ~Transaction();
     39  private:
     40   volatile disk_cache::LruData* data_;
     41   DISALLOW_COPY_AND_ASSIGN(Transaction);
     42 };
     43 
     44 Transaction::Transaction(volatile disk_cache::LruData* data,
     45                          disk_cache::Addr addr, Operation op, int list)
     46     : data_(data) {
     47   DCHECK(!data_->transaction);
     48   DCHECK(addr.is_initialized());
     49   data_->operation = op;
     50   data_->operation_list = list;
     51   data_->transaction = addr.value();
     52 }
     53 
     54 Transaction::~Transaction() {
     55   DCHECK(data_->transaction);
     56   data_->transaction = 0;
     57   data_->operation = 0;
     58   data_->operation_list = 0;
     59 }
     60 
     61 // Code locations that can generate crashes.
     62 enum CrashLocation {
     63   ON_INSERT_1, ON_INSERT_2, ON_INSERT_3, ON_INSERT_4, ON_REMOVE_1, ON_REMOVE_2,
     64   ON_REMOVE_3, ON_REMOVE_4, ON_REMOVE_5, ON_REMOVE_6, ON_REMOVE_7, ON_REMOVE_8
     65 };
     66 
     67 void TerminateSelf() {
     68 #if defined(OS_WIN)
     69   // Windows does more work on _exit() than we would like, so we force exit.
     70   TerminateProcess(GetCurrentProcess(), 0);
     71 #elif defined(OS_POSIX)
     72   // On POSIX, _exit() will terminate the process with minimal cleanup,
     73   // and it is cleaner than killing.
     74   _exit(0);
     75 #endif
     76 }
     77 
     78 // Generates a crash on debug builds, acording to the value of g_rankings_crash.
     79 // This used by crash_cache.exe to generate unit-test files.
     80 void GenerateCrash(CrashLocation location) {
     81 #ifndef NDEBUG
     82   if (disk_cache::NO_CRASH == g_rankings_crash)
     83     return;
     84   switch (location) {
     85     case ON_INSERT_1:
     86       switch (g_rankings_crash) {
     87         case disk_cache::INSERT_ONE_1:
     88         case disk_cache::INSERT_LOAD_1:
     89           TerminateSelf();
     90         default:
     91           break;
     92       }
     93       break;
     94     case ON_INSERT_2:
     95       if (disk_cache::INSERT_EMPTY_1 == g_rankings_crash)
     96         TerminateSelf();
     97       break;
     98     case ON_INSERT_3:
     99       switch (g_rankings_crash) {
    100         case disk_cache::INSERT_EMPTY_2:
    101         case disk_cache::INSERT_ONE_2:
    102         case disk_cache::INSERT_LOAD_2:
    103           TerminateSelf();
    104         default:
    105           break;
    106       }
    107       break;
    108     case ON_INSERT_4:
    109       switch (g_rankings_crash) {
    110         case disk_cache::INSERT_EMPTY_3:
    111         case disk_cache::INSERT_ONE_3:
    112           TerminateSelf();
    113         default:
    114           break;
    115       }
    116       break;
    117     case ON_REMOVE_1:
    118       switch (g_rankings_crash) {
    119         case disk_cache::REMOVE_ONE_1:
    120         case disk_cache::REMOVE_HEAD_1:
    121         case disk_cache::REMOVE_TAIL_1:
    122         case disk_cache::REMOVE_LOAD_1:
    123           TerminateSelf();
    124         default:
    125           break;
    126       }
    127       break;
    128     case ON_REMOVE_2:
    129       if (disk_cache::REMOVE_ONE_2 == g_rankings_crash)
    130         TerminateSelf();
    131       break;
    132     case ON_REMOVE_3:
    133       if (disk_cache::REMOVE_ONE_3 == g_rankings_crash)
    134         TerminateSelf();
    135       break;
    136     case ON_REMOVE_4:
    137       if (disk_cache::REMOVE_HEAD_2 == g_rankings_crash)
    138         TerminateSelf();
    139       break;
    140     case ON_REMOVE_5:
    141       if (disk_cache::REMOVE_TAIL_2 == g_rankings_crash)
    142         TerminateSelf();
    143       break;
    144     case ON_REMOVE_6:
    145       if (disk_cache::REMOVE_TAIL_3 == g_rankings_crash)
    146         TerminateSelf();
    147       break;
    148     case ON_REMOVE_7:
    149       switch (g_rankings_crash) {
    150         case disk_cache::REMOVE_ONE_4:
    151         case disk_cache::REMOVE_LOAD_2:
    152         case disk_cache::REMOVE_HEAD_3:
    153           TerminateSelf();
    154         default:
    155           break;
    156       }
    157       break;
    158     case ON_REMOVE_8:
    159       switch (g_rankings_crash) {
    160         case disk_cache::REMOVE_HEAD_4:
    161         case disk_cache::REMOVE_LOAD_3:
    162           TerminateSelf();
    163         default:
    164           break;
    165       }
    166       break;
    167     default:
    168       NOTREACHED();
    169       return;
    170   }
    171 #endif  // NDEBUG
    172 }
    173 
    174 }  // namespace
    175 
    176 namespace disk_cache {
    177 
    178 bool Rankings::Init(BackendImpl* backend, bool count_lists) {
    179   DCHECK(!init_);
    180   if (init_)
    181     return false;
    182 
    183   backend_ = backend;
    184   control_data_ = backend_->GetLruData();
    185   count_lists_ = count_lists;
    186 
    187   ReadHeads();
    188   ReadTails();
    189 
    190   if (control_data_->transaction)
    191     CompleteTransaction();
    192 
    193   init_ = true;
    194   return true;
    195 }
    196 
    197 void Rankings::Reset() {
    198   init_ = false;
    199   for (int i = 0; i < LAST_ELEMENT; i++) {
    200     heads_[i].set_value(0);
    201     tails_[i].set_value(0);
    202   }
    203   control_data_ = NULL;
    204 }
    205 
    206 bool Rankings::GetRanking(CacheRankingsBlock* rankings) {
    207   if (!rankings->address().is_initialized())
    208     return false;
    209 
    210   Time start = Time::Now();
    211   if (!rankings->Load())
    212     return false;
    213 
    214   if (!SanityCheck(rankings, true)) {
    215     backend_->CriticalError(ERR_INVALID_LINKS);
    216     return false;
    217   }
    218 
    219   backend_->OnEvent(Stats::OPEN_RANKINGS);
    220 
    221   // "dummy" is the old "pointer" value, so it has to be 0.
    222   if (!rankings->Data()->dirty && !rankings->Data()->dummy)
    223     return true;
    224 
    225   EntryImpl* entry = backend_->GetOpenEntry(rankings);
    226   if (backend_->GetCurrentEntryId() != rankings->Data()->dirty || !entry) {
    227     // We cannot trust this entry, but we cannot initiate a cleanup from this
    228     // point (we may be in the middle of a cleanup already). Just get rid of
    229     // the invalid pointer and continue; the entry will be deleted when detected
    230     // from a regular open/create path.
    231     rankings->Data()->dummy = 0;
    232     rankings->Data()->dirty = backend_->GetCurrentEntryId() - 1;
    233     if (!rankings->Data()->dirty)
    234       rankings->Data()->dirty--;
    235     return true;
    236   }
    237 
    238   // Note that we should not leave this module without deleting rankings first.
    239   rankings->SetData(entry->rankings()->Data());
    240 
    241   CACHE_UMA(AGE_MS, "GetRankings", 0, start);
    242   return true;
    243 }
    244 
    245 void Rankings::ConvertToLongLived(CacheRankingsBlock* rankings) {
    246   if (rankings->own_data())
    247     return;
    248 
    249   // We cannot return a shared node because we are not keeping a reference
    250   // to the entry that owns the buffer. Make this node a copy of the one that
    251   // we have, and let the iterator logic update it when the entry changes.
    252   CacheRankingsBlock temp(NULL, Addr(0));
    253   *temp.Data() = *rankings->Data();
    254   rankings->StopSharingData();
    255   *rankings->Data() = *temp.Data();
    256 }
    257 
    258 void Rankings::Insert(CacheRankingsBlock* node, bool modified, List list) {
    259   Trace("Insert 0x%x", node->address().value());
    260   DCHECK(node->HasData());
    261   Addr& my_head = heads_[list];
    262   Addr& my_tail = tails_[list];
    263   Transaction lock(control_data_, node->address(), INSERT, list);
    264   CacheRankingsBlock head(backend_->File(my_head), my_head);
    265   if (my_head.is_initialized()) {
    266     if (!GetRanking(&head))
    267       return;
    268 
    269     if (head.Data()->prev != my_head.value() &&  // Normal path.
    270         head.Data()->prev != node->address().value()) {  // FinishInsert().
    271       backend_->CriticalError(ERR_INVALID_LINKS);
    272       return;
    273     }
    274 
    275     head.Data()->prev = node->address().value();
    276     head.Store();
    277     GenerateCrash(ON_INSERT_1);
    278     UpdateIterators(&head);
    279   }
    280 
    281   node->Data()->next = my_head.value();
    282   node->Data()->prev = node->address().value();
    283   my_head.set_value(node->address().value());
    284 
    285   if (!my_tail.is_initialized() || my_tail.value() == node->address().value()) {
    286     my_tail.set_value(node->address().value());
    287     node->Data()->next = my_tail.value();
    288     WriteTail(list);
    289     GenerateCrash(ON_INSERT_2);
    290   }
    291 
    292   Time now = Time::Now();
    293   node->Data()->last_used = now.ToInternalValue();
    294   if (modified)
    295     node->Data()->last_modified = now.ToInternalValue();
    296   node->Store();
    297   GenerateCrash(ON_INSERT_3);
    298 
    299   // The last thing to do is move our head to point to a node already stored.
    300   WriteHead(list);
    301   IncrementCounter(list);
    302   GenerateCrash(ON_INSERT_4);
    303 }
    304 
    305 // If a, b and r are elements on the list, and we want to remove r, the possible
    306 // states for the objects if a crash happens are (where y(x, z) means for object
    307 // y, prev is x and next is z):
    308 // A. One element:
    309 //    1. r(r, r), head(r), tail(r)                    initial state
    310 //    2. r(r, r), head(0), tail(r)                    WriteHead()
    311 //    3. r(r, r), head(0), tail(0)                    WriteTail()
    312 //    4. r(0, 0), head(0), tail(0)                    next.Store()
    313 //
    314 // B. Remove a random element:
    315 //    1. a(x, r), r(a, b), b(r, y), head(x), tail(y)  initial state
    316 //    2. a(x, r), r(a, b), b(a, y), head(x), tail(y)  next.Store()
    317 //    3. a(x, b), r(a, b), b(a, y), head(x), tail(y)  prev.Store()
    318 //    4. a(x, b), r(0, 0), b(a, y), head(x), tail(y)  node.Store()
    319 //
    320 // C. Remove head:
    321 //    1. r(r, b), b(r, y), head(r), tail(y)           initial state
    322 //    2. r(r, b), b(r, y), head(b), tail(y)           WriteHead()
    323 //    3. r(r, b), b(b, y), head(b), tail(y)           next.Store()
    324 //    4. r(0, 0), b(b, y), head(b), tail(y)           prev.Store()
    325 //
    326 // D. Remove tail:
    327 //    1. a(x, r), r(a, r), head(x), tail(r)           initial state
    328 //    2. a(x, r), r(a, r), head(x), tail(a)           WriteTail()
    329 //    3. a(x, a), r(a, r), head(x), tail(a)           prev.Store()
    330 //    4. a(x, a), r(0, 0), head(x), tail(a)           next.Store()
    331 void Rankings::Remove(CacheRankingsBlock* node, List list) {
    332   Trace("Remove 0x%x (0x%x 0x%x)", node->address().value(), node->Data()->next,
    333         node->Data()->prev);
    334   DCHECK(node->HasData());
    335   InvalidateIterators(node);
    336   Addr next_addr(node->Data()->next);
    337   Addr prev_addr(node->Data()->prev);
    338   if (!next_addr.is_initialized() || next_addr.is_separate_file() ||
    339       !prev_addr.is_initialized() || prev_addr.is_separate_file()) {
    340     LOG(WARNING) << "Invalid rankings info.";
    341     return;
    342   }
    343 
    344   CacheRankingsBlock next(backend_->File(next_addr), next_addr);
    345   CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
    346   if (!GetRanking(&next) || !GetRanking(&prev))
    347     return;
    348 
    349   if (!CheckLinks(node, &prev, &next, list))
    350     return;
    351 
    352   Transaction lock(control_data_, node->address(), REMOVE, list);
    353   prev.Data()->next = next.address().value();
    354   next.Data()->prev = prev.address().value();
    355   GenerateCrash(ON_REMOVE_1);
    356 
    357   CacheAddr node_value = node->address().value();
    358   Addr& my_head = heads_[list];
    359   Addr& my_tail = tails_[list];
    360   if (node_value == my_head.value() || node_value == my_tail.value()) {
    361     if (my_head.value() == my_tail.value()) {
    362       my_head.set_value(0);
    363       my_tail.set_value(0);
    364 
    365       WriteHead(list);
    366       GenerateCrash(ON_REMOVE_2);
    367       WriteTail(list);
    368       GenerateCrash(ON_REMOVE_3);
    369     } else if (node_value == my_head.value()) {
    370       my_head.set_value(next.address().value());
    371       next.Data()->prev = next.address().value();
    372 
    373       WriteHead(list);
    374       GenerateCrash(ON_REMOVE_4);
    375     } else if (node_value == my_tail.value()) {
    376       my_tail.set_value(prev.address().value());
    377       prev.Data()->next = prev.address().value();
    378 
    379       WriteTail(list);
    380       GenerateCrash(ON_REMOVE_5);
    381 
    382       // Store the new tail to make sure we can undo the operation if we crash.
    383       prev.Store();
    384       GenerateCrash(ON_REMOVE_6);
    385     }
    386   }
    387 
    388   // Nodes out of the list can be identified by invalid pointers.
    389   node->Data()->next = 0;
    390   node->Data()->prev = 0;
    391 
    392   // The last thing to get to disk is the node itself, so before that there is
    393   // enough info to recover.
    394   next.Store();
    395   GenerateCrash(ON_REMOVE_7);
    396   prev.Store();
    397   GenerateCrash(ON_REMOVE_8);
    398   node->Store();
    399   DecrementCounter(list);
    400   UpdateIterators(&next);
    401   UpdateIterators(&prev);
    402 }
    403 
    404 // A crash in between Remove and Insert will lead to a dirty entry not on the
    405 // list. We want to avoid that case as much as we can (as while waiting for IO),
    406 // but the net effect is just an assert on debug when attempting to remove the
    407 // entry. Otherwise we'll need reentrant transactions, which is an overkill.
    408 void Rankings::UpdateRank(CacheRankingsBlock* node, bool modified, List list) {
    409   Time start = Time::Now();
    410   Remove(node, list);
    411   Insert(node, modified, list);
    412   CACHE_UMA(AGE_MS, "UpdateRank", 0, start);
    413 }
    414 
    415 void Rankings::CompleteTransaction() {
    416   Addr node_addr(static_cast<CacheAddr>(control_data_->transaction));
    417   if (!node_addr.is_initialized() || node_addr.is_separate_file()) {
    418     NOTREACHED();
    419     LOG(ERROR) << "Invalid rankings info.";
    420     return;
    421   }
    422 
    423   Trace("CompleteTransaction 0x%x", node_addr.value());
    424 
    425   CacheRankingsBlock node(backend_->File(node_addr), node_addr);
    426   if (!node.Load())
    427     return;
    428 
    429   node.Data()->dummy = 0;
    430   node.Store();
    431 
    432   Addr& my_head = heads_[control_data_->operation_list];
    433   Addr& my_tail = tails_[control_data_->operation_list];
    434 
    435   // We want to leave the node inside the list. The entry must me marked as
    436   // dirty, and will be removed later. Otherwise, we'll get assertions when
    437   // attempting to remove the dirty entry.
    438   if (INSERT == control_data_->operation) {
    439     Trace("FinishInsert h:0x%x t:0x%x", my_head.value(), my_tail.value());
    440     FinishInsert(&node);
    441   } else if (REMOVE == control_data_->operation) {
    442     Trace("RevertRemove h:0x%x t:0x%x", my_head.value(), my_tail.value());
    443     RevertRemove(&node);
    444   } else {
    445     NOTREACHED();
    446     LOG(ERROR) << "Invalid operation to recover.";
    447   }
    448 }
    449 
    450 void Rankings::FinishInsert(CacheRankingsBlock* node) {
    451   control_data_->transaction = 0;
    452   control_data_->operation = 0;
    453   Addr& my_head = heads_[control_data_->operation_list];
    454   Addr& my_tail = tails_[control_data_->operation_list];
    455   if (my_head.value() != node->address().value()) {
    456     if (my_tail.value() == node->address().value()) {
    457       // This part will be skipped by the logic of Insert.
    458       node->Data()->next = my_tail.value();
    459     }
    460 
    461     Insert(node, true, static_cast<List>(control_data_->operation_list));
    462   }
    463 
    464   // Tell the backend about this entry.
    465   backend_->RecoveredEntry(node);
    466 }
    467 
    468 void Rankings::RevertRemove(CacheRankingsBlock* node) {
    469   Addr next_addr(node->Data()->next);
    470   Addr prev_addr(node->Data()->prev);
    471   if (!next_addr.is_initialized() || !prev_addr.is_initialized()) {
    472     // The operation actually finished. Nothing to do.
    473     control_data_->transaction = 0;
    474     return;
    475   }
    476   if (next_addr.is_separate_file() || prev_addr.is_separate_file()) {
    477     NOTREACHED();
    478     LOG(WARNING) << "Invalid rankings info.";
    479     control_data_->transaction = 0;
    480     return;
    481   }
    482 
    483   CacheRankingsBlock next(backend_->File(next_addr), next_addr);
    484   CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
    485   if (!next.Load() || !prev.Load())
    486     return;
    487 
    488   CacheAddr node_value = node->address().value();
    489   DCHECK(prev.Data()->next == node_value ||
    490          prev.Data()->next == prev_addr.value() ||
    491          prev.Data()->next == next.address().value());
    492   DCHECK(next.Data()->prev == node_value ||
    493          next.Data()->prev == next_addr.value() ||
    494          next.Data()->prev == prev.address().value());
    495 
    496   if (node_value != prev_addr.value())
    497     prev.Data()->next = node_value;
    498   if (node_value != next_addr.value())
    499     next.Data()->prev = node_value;
    500 
    501   List my_list = static_cast<List>(control_data_->operation_list);
    502   Addr& my_head = heads_[my_list];
    503   Addr& my_tail = tails_[my_list];
    504   if (!my_head.is_initialized() || !my_tail.is_initialized()) {
    505     my_head.set_value(node_value);
    506     my_tail.set_value(node_value);
    507     WriteHead(my_list);
    508     WriteTail(my_list);
    509   } else if (my_head.value() == next.address().value()) {
    510     my_head.set_value(node_value);
    511     prev.Data()->next = next.address().value();
    512     WriteHead(my_list);
    513   } else if (my_tail.value() == prev.address().value()) {
    514     my_tail.set_value(node_value);
    515     next.Data()->prev = prev.address().value();
    516     WriteTail(my_list);
    517   }
    518 
    519   next.Store();
    520   prev.Store();
    521   control_data_->transaction = 0;
    522   control_data_->operation = 0;
    523 }
    524 
    525 CacheRankingsBlock* Rankings::GetNext(CacheRankingsBlock* node, List list) {
    526   ScopedRankingsBlock next(this);
    527   if (!node) {
    528     Addr& my_head = heads_[list];
    529     if (!my_head.is_initialized())
    530       return NULL;
    531     next.reset(new CacheRankingsBlock(backend_->File(my_head), my_head));
    532   } else {
    533     if (!node->HasData())
    534       node->Load();
    535     Addr& my_tail = tails_[list];
    536     if (!my_tail.is_initialized())
    537       return NULL;
    538     if (my_tail.value() == node->address().value())
    539       return NULL;
    540     Addr address(node->Data()->next);
    541     if (address.value() == node->address().value())
    542       return NULL;  // Another tail? fail it.
    543     next.reset(new CacheRankingsBlock(backend_->File(address), address));
    544   }
    545 
    546   TrackRankingsBlock(next.get(), true);
    547 
    548   if (!GetRanking(next.get()))
    549     return NULL;
    550 
    551   ConvertToLongLived(next.get());
    552   if (node && !CheckSingleLink(node, next.get()))
    553     return NULL;
    554 
    555   return next.release();
    556 }
    557 
    558 CacheRankingsBlock* Rankings::GetPrev(CacheRankingsBlock* node, List list) {
    559   ScopedRankingsBlock prev(this);
    560   if (!node) {
    561     Addr& my_tail = tails_[list];
    562     if (!my_tail.is_initialized())
    563       return NULL;
    564     prev.reset(new CacheRankingsBlock(backend_->File(my_tail), my_tail));
    565   } else {
    566     if (!node->HasData())
    567       node->Load();
    568     Addr& my_head = heads_[list];
    569     if (!my_head.is_initialized())
    570       return NULL;
    571     if (my_head.value() == node->address().value())
    572       return NULL;
    573     Addr address(node->Data()->prev);
    574     if (address.value() == node->address().value())
    575       return NULL;  // Another head? fail it.
    576     prev.reset(new CacheRankingsBlock(backend_->File(address), address));
    577   }
    578 
    579   TrackRankingsBlock(prev.get(), true);
    580 
    581   if (!GetRanking(prev.get()))
    582     return NULL;
    583 
    584   ConvertToLongLived(prev.get());
    585   if (node && !CheckSingleLink(prev.get(), node))
    586     return NULL;
    587 
    588   return prev.release();
    589 }
    590 
    591 void Rankings::FreeRankingsBlock(CacheRankingsBlock* node) {
    592   TrackRankingsBlock(node, false);
    593 }
    594 
    595 void Rankings::TrackRankingsBlock(CacheRankingsBlock* node,
    596                                   bool start_tracking) {
    597   if (!node)
    598     return;
    599 
    600   IteratorPair current(node->address().value(), node);
    601 
    602   if (start_tracking)
    603     iterators_.push_back(current);
    604   else
    605     iterators_.remove(current);
    606 }
    607 
    608 int Rankings::SelfCheck() {
    609   int total = 0;
    610   for (int i = 0; i < LAST_ELEMENT; i++) {
    611     int partial = CheckList(static_cast<List>(i));
    612     if (partial < 0)
    613       return partial;
    614     total += partial;
    615   }
    616   return total;
    617 }
    618 
    619 bool Rankings::SanityCheck(CacheRankingsBlock* node, bool from_list) {
    620   const RankingsNode* data = node->Data();
    621   if (!data->contents)
    622     return false;
    623 
    624   // It may have never been inserted.
    625   if (from_list && (!data->last_used || !data->last_modified))
    626     return false;
    627 
    628   if ((!data->next && data->prev) || (data->next && !data->prev))
    629     return false;
    630 
    631   // Both pointers on zero is a node out of the list.
    632   if (!data->next && !data->prev && from_list)
    633     return false;
    634 
    635   if ((node->address().value() == data->prev) && !IsHead(data->prev))
    636     return false;
    637 
    638   if ((node->address().value() == data->next) && !IsTail(data->next))
    639     return false;
    640 
    641   return true;
    642 }
    643 
    644 void Rankings::ReadHeads() {
    645   for (int i = 0; i < LAST_ELEMENT; i++)
    646     heads_[i] = Addr(control_data_->heads[i]);
    647 }
    648 
    649 void Rankings::ReadTails() {
    650   for (int i = 0; i < LAST_ELEMENT; i++)
    651     tails_[i] = Addr(control_data_->tails[i]);
    652 }
    653 
    654 void Rankings::WriteHead(List list) {
    655   control_data_->heads[list] = heads_[list].value();
    656 }
    657 
    658 void Rankings::WriteTail(List list) {
    659   control_data_->tails[list] = tails_[list].value();
    660 }
    661 
    662 bool Rankings::CheckEntry(CacheRankingsBlock* rankings) {
    663   if (!rankings->Data()->dummy)
    664     return true;
    665 
    666   // If this entry is not dirty, it is a serious problem.
    667   return backend_->GetCurrentEntryId() != rankings->Data()->dirty;
    668 }
    669 
    670 bool Rankings::CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
    671                           CacheRankingsBlock* next, List list) {
    672   if ((prev->Data()->next != node->address().value() &&
    673        heads_[list].value() != node->address().value()) ||
    674       (next->Data()->prev != node->address().value() &&
    675        tails_[list].value() != node->address().value())) {
    676     LOG(ERROR) << "Inconsistent LRU.";
    677 
    678     if (prev->Data()->next == next->address().value() &&
    679         next->Data()->prev == prev->address().value()) {
    680       // The list is actually ok, node is wrong.
    681       node->Data()->next = 0;
    682       node->Data()->prev = 0;
    683       node->Store();
    684       return false;
    685     }
    686     backend_->CriticalError(ERR_INVALID_LINKS);
    687     return false;
    688   }
    689 
    690   return true;
    691 }
    692 
    693 bool Rankings::CheckSingleLink(CacheRankingsBlock* prev,
    694                                CacheRankingsBlock* next) {
    695   if (prev->Data()->next != next->address().value() ||
    696       next->Data()->prev != prev->address().value()) {
    697     LOG(ERROR) << "Inconsistent LRU.";
    698 
    699     backend_->CriticalError(ERR_INVALID_LINKS);
    700     return false;
    701   }
    702 
    703   return true;
    704 }
    705 
    706 int Rankings::CheckList(List list) {
    707   Addr& my_head = heads_[list];
    708   Addr& my_tail = tails_[list];
    709   if (!my_head.is_initialized()) {
    710     if (!my_tail.is_initialized())
    711       return 0;
    712     // If there is no head, having a tail is an error.
    713     return ERR_INVALID_TAIL;
    714   }
    715   // If there is no tail, having a head is an error.
    716   if (!my_tail.is_initialized())
    717     return ERR_INVALID_HEAD;
    718 
    719   if (my_tail.is_separate_file())
    720     return ERR_INVALID_TAIL;
    721 
    722   if (my_head.is_separate_file())
    723     return ERR_INVALID_HEAD;
    724 
    725   int num_items = 0;
    726   Addr address(my_head.value());
    727   Addr prev(my_head.value());
    728   scoped_ptr<CacheRankingsBlock> node;
    729   do {
    730     node.reset(new CacheRankingsBlock(backend_->File(address), address));
    731     node->Load();
    732     if (node->Data()->prev != prev.value())
    733       return ERR_INVALID_PREV;
    734     if (!CheckEntry(node.get()))
    735       return ERR_INVALID_ENTRY;
    736 
    737     prev.set_value(address.value());
    738     address.set_value(node->Data()->next);
    739     if (!address.is_initialized() || address.is_separate_file())
    740       return ERR_INVALID_NEXT;
    741 
    742     num_items++;
    743   } while (node->address().value() != address.value());
    744   return num_items;
    745 }
    746 
    747 bool Rankings::IsHead(CacheAddr addr) {
    748   for (int i = 0; i < LAST_ELEMENT; i++)
    749     if (addr == heads_[i].value())
    750       return true;
    751   return false;
    752 }
    753 
    754 bool Rankings::IsTail(CacheAddr addr) {
    755   for (int i = 0; i < LAST_ELEMENT; i++)
    756     if (addr == tails_[i].value())
    757       return true;
    758   return false;
    759 }
    760 
    761 // We expect to have just a few iterators at any given time, maybe two or three,
    762 // But we could have more than one pointing at the same mode. We walk the list
    763 // of cache iterators and update all that are pointing to the given node.
    764 void Rankings::UpdateIterators(CacheRankingsBlock* node) {
    765   CacheAddr address = node->address().value();
    766   for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
    767        ++it) {
    768     if (it->first == address && it->second->HasData()) {
    769       CacheRankingsBlock* other = it->second;
    770       *other->Data() = *node->Data();
    771     }
    772   }
    773 }
    774 
    775 void Rankings::InvalidateIterators(CacheRankingsBlock* node) {
    776   CacheAddr address = node->address().value();
    777   for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
    778        ++it) {
    779     if (it->first == address) {
    780       LOG(WARNING) << "Invalidating iterator at 0x" << std::hex << address;
    781       it->second->Discard();
    782     }
    783   }
    784 }
    785 
    786 void Rankings::IncrementCounter(List list) {
    787   if (!count_lists_)
    788     return;
    789 
    790   DCHECK(control_data_->sizes[list] < kint32max);
    791   if (control_data_->sizes[list] < kint32max)
    792     control_data_->sizes[list]++;
    793 }
    794 
    795 void Rankings::DecrementCounter(List list) {
    796   if (!count_lists_)
    797     return;
    798 
    799   DCHECK(control_data_->sizes[list] > 0);
    800   if (control_data_->sizes[list] > 0)
    801     control_data_->sizes[list]--;
    802 }
    803 
    804 }  // namespace disk_cache
    805