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