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