1 // Copyright (c) 2011 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 13 using base::Time; 14 using base::TimeTicks; 15 16 // This is used by crash_cache.exe to generate unit test files. 17 disk_cache::RankCrashes g_rankings_crash = disk_cache::NO_CRASH; 18 19 namespace { 20 21 enum 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. 31 class 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 45 Transaction::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 55 Transaction::~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. 63 enum 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 69 void 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. 83 void 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|. 178 void 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 187 namespace disk_cache { 188 189 Rankings::Iterator::Iterator(Rankings* rankings) { 190 memset(this, 0, sizeof(Iterator)); 191 my_rankings = rankings; 192 } 193 194 Rankings::Iterator::~Iterator() { 195 for (int i = 0; i < 3; i++) 196 ScopedRankingsBlock(my_rankings, nodes[i]); 197 } 198 199 Rankings::Rankings() : init_(false) {} 200 201 Rankings::~Rankings() {} 202 203 bool 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 222 void 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 231 void 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() 301 void 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. 380 void 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 394 CacheRankingsBlock* 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 427 CacheRankingsBlock* 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 460 void Rankings::FreeRankingsBlock(CacheRankingsBlock* node) { 461 TrackRankingsBlock(node, false); 462 } 463 464 void 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 477 int 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 488 bool 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 if (!data->next && !data->prev) 512 return true; 513 514 Addr next_addr(data->next); 515 Addr prev_addr(data->prev); 516 if (!next_addr.SanityCheck() || next_addr.file_type() != RANKINGS || 517 !prev_addr.SanityCheck() || prev_addr.file_type() != RANKINGS) 518 return false; 519 520 return true; 521 } 522 523 void Rankings::ReadHeads() { 524 for (int i = 0; i < LAST_ELEMENT; i++) 525 heads_[i] = Addr(control_data_->heads[i]); 526 } 527 528 void Rankings::ReadTails() { 529 for (int i = 0; i < LAST_ELEMENT; i++) 530 tails_[i] = Addr(control_data_->tails[i]); 531 } 532 533 void Rankings::WriteHead(List list) { 534 control_data_->heads[list] = heads_[list].value(); 535 } 536 537 void Rankings::WriteTail(List list) { 538 control_data_->tails[list] = tails_[list].value(); 539 } 540 541 bool Rankings::GetRanking(CacheRankingsBlock* rankings) { 542 if (!rankings->address().is_initialized()) 543 return false; 544 545 TimeTicks start = TimeTicks::Now(); 546 if (!rankings->Load()) 547 return false; 548 549 if (!SanityCheck(rankings, true)) { 550 backend_->CriticalError(ERR_INVALID_LINKS); 551 return false; 552 } 553 554 backend_->OnEvent(Stats::OPEN_RANKINGS); 555 556 // "dummy" is the old "pointer" value, so it has to be 0. 557 if (!rankings->Data()->dirty && !rankings->Data()->dummy) 558 return true; 559 560 EntryImpl* entry = backend_->GetOpenEntry(rankings); 561 if (!entry) { 562 // We cannot trust this entry, but we cannot initiate a cleanup from this 563 // point (we may be in the middle of a cleanup already). Just get rid of 564 // the invalid pointer and continue; the entry will be deleted when detected 565 // from a regular open/create path. 566 rankings->Data()->dummy = 0; 567 rankings->Data()->dirty = backend_->GetCurrentEntryId() - 1; 568 if (!rankings->Data()->dirty) 569 rankings->Data()->dirty--; 570 return true; 571 } 572 573 // Note that we should not leave this module without deleting rankings first. 574 rankings->SetData(entry->rankings()->Data()); 575 576 CACHE_UMA(AGE_MS, "GetRankings", 0, start); 577 return true; 578 } 579 580 void Rankings::ConvertToLongLived(CacheRankingsBlock* rankings) { 581 if (rankings->own_data()) 582 return; 583 584 // We cannot return a shared node because we are not keeping a reference 585 // to the entry that owns the buffer. Make this node a copy of the one that 586 // we have, and let the iterator logic update it when the entry changes. 587 CacheRankingsBlock temp(NULL, Addr(0)); 588 *temp.Data() = *rankings->Data(); 589 rankings->StopSharingData(); 590 *rankings->Data() = *temp.Data(); 591 } 592 593 void Rankings::CompleteTransaction() { 594 Addr node_addr(static_cast<CacheAddr>(control_data_->transaction)); 595 if (!node_addr.is_initialized() || node_addr.is_separate_file()) { 596 NOTREACHED(); 597 LOG(ERROR) << "Invalid rankings info."; 598 return; 599 } 600 601 Trace("CompleteTransaction 0x%x", node_addr.value()); 602 603 CacheRankingsBlock node(backend_->File(node_addr), node_addr); 604 if (!node.Load()) 605 return; 606 607 node.Data()->dummy = 0; 608 node.Store(); 609 610 Addr& my_head = heads_[control_data_->operation_list]; 611 Addr& my_tail = tails_[control_data_->operation_list]; 612 613 // We want to leave the node inside the list. The entry must me marked as 614 // dirty, and will be removed later. Otherwise, we'll get assertions when 615 // attempting to remove the dirty entry. 616 if (INSERT == control_data_->operation) { 617 Trace("FinishInsert h:0x%x t:0x%x", my_head.value(), my_tail.value()); 618 FinishInsert(&node); 619 } else if (REMOVE == control_data_->operation) { 620 Trace("RevertRemove h:0x%x t:0x%x", my_head.value(), my_tail.value()); 621 RevertRemove(&node); 622 } else { 623 NOTREACHED(); 624 LOG(ERROR) << "Invalid operation to recover."; 625 } 626 } 627 628 void Rankings::FinishInsert(CacheRankingsBlock* node) { 629 control_data_->transaction = 0; 630 control_data_->operation = 0; 631 Addr& my_head = heads_[control_data_->operation_list]; 632 Addr& my_tail = tails_[control_data_->operation_list]; 633 if (my_head.value() != node->address().value()) { 634 if (my_tail.value() == node->address().value()) { 635 // This part will be skipped by the logic of Insert. 636 node->Data()->next = my_tail.value(); 637 } 638 639 Insert(node, true, static_cast<List>(control_data_->operation_list)); 640 } 641 642 // Tell the backend about this entry. 643 backend_->RecoveredEntry(node); 644 } 645 646 void Rankings::RevertRemove(CacheRankingsBlock* node) { 647 Addr next_addr(node->Data()->next); 648 Addr prev_addr(node->Data()->prev); 649 if (!next_addr.is_initialized() || !prev_addr.is_initialized()) { 650 // The operation actually finished. Nothing to do. 651 control_data_->transaction = 0; 652 return; 653 } 654 if (next_addr.is_separate_file() || prev_addr.is_separate_file()) { 655 NOTREACHED(); 656 LOG(WARNING) << "Invalid rankings info."; 657 control_data_->transaction = 0; 658 return; 659 } 660 661 CacheRankingsBlock next(backend_->File(next_addr), next_addr); 662 CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr); 663 if (!next.Load() || !prev.Load()) 664 return; 665 666 CacheAddr node_value = node->address().value(); 667 DCHECK(prev.Data()->next == node_value || 668 prev.Data()->next == prev_addr.value() || 669 prev.Data()->next == next.address().value()); 670 DCHECK(next.Data()->prev == node_value || 671 next.Data()->prev == next_addr.value() || 672 next.Data()->prev == prev.address().value()); 673 674 if (node_value != prev_addr.value()) 675 prev.Data()->next = node_value; 676 if (node_value != next_addr.value()) 677 next.Data()->prev = node_value; 678 679 List my_list = static_cast<List>(control_data_->operation_list); 680 Addr& my_head = heads_[my_list]; 681 Addr& my_tail = tails_[my_list]; 682 if (!my_head.is_initialized() || !my_tail.is_initialized()) { 683 my_head.set_value(node_value); 684 my_tail.set_value(node_value); 685 WriteHead(my_list); 686 WriteTail(my_list); 687 } else if (my_head.value() == next.address().value()) { 688 my_head.set_value(node_value); 689 prev.Data()->next = next.address().value(); 690 WriteHead(my_list); 691 } else if (my_tail.value() == prev.address().value()) { 692 my_tail.set_value(node_value); 693 next.Data()->prev = prev.address().value(); 694 WriteTail(my_list); 695 } 696 697 next.Store(); 698 prev.Store(); 699 control_data_->transaction = 0; 700 control_data_->operation = 0; 701 } 702 703 bool Rankings::CheckEntry(CacheRankingsBlock* rankings) { 704 if (!rankings->Data()->dummy) 705 return true; 706 707 // If this entry is not dirty, it is a serious problem. 708 return backend_->GetCurrentEntryId() != rankings->Data()->dirty; 709 } 710 711 bool Rankings::CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev, 712 CacheRankingsBlock* next, List* list) { 713 CacheAddr node_addr = node->address().value(); 714 if (prev->Data()->next == node_addr && 715 next->Data()->prev == node_addr) { 716 // A regular linked node. 717 return true; 718 } 719 720 Trace("CheckLinks 0x%x (0x%x 0x%x)", node_addr, 721 prev->Data()->next, next->Data()->prev); 722 723 if (node_addr != prev->address().value() && 724 node_addr != next->address().value() && 725 prev->Data()->next == next->address().value() && 726 next->Data()->prev == prev->address().value()) { 727 // The list is actually ok, node is wrong. 728 Trace("node 0x%x out of list %d", node_addr, list); 729 node->Data()->next = 0; 730 node->Data()->prev = 0; 731 node->Store(); 732 return false; 733 } 734 735 if (prev->Data()->next == node_addr || 736 next->Data()->prev == node_addr) { 737 // Only one link is weird, lets double check. 738 if (prev->Data()->next != node_addr && IsHead(node_addr, list)) 739 return true; 740 741 if (next->Data()->prev != node_addr && IsTail(node_addr, list)) 742 return true; 743 } 744 745 LOG(ERROR) << "Inconsistent LRU."; 746 backend_->CriticalError(ERR_INVALID_LINKS); 747 return false; 748 } 749 750 bool Rankings::CheckSingleLink(CacheRankingsBlock* prev, 751 CacheRankingsBlock* next) { 752 if (prev->Data()->next != next->address().value() || 753 next->Data()->prev != prev->address().value()) { 754 LOG(ERROR) << "Inconsistent LRU."; 755 756 backend_->CriticalError(ERR_INVALID_LINKS); 757 return false; 758 } 759 760 return true; 761 } 762 763 int Rankings::CheckList(List list) { 764 Addr& my_head = heads_[list]; 765 Addr& my_tail = tails_[list]; 766 if (!my_head.is_initialized()) { 767 if (!my_tail.is_initialized()) 768 return 0; 769 // If there is no head, having a tail is an error. 770 return ERR_INVALID_TAIL; 771 } 772 // If there is no tail, having a head is an error. 773 if (!my_tail.is_initialized()) 774 return ERR_INVALID_HEAD; 775 776 if (my_tail.is_separate_file()) 777 return ERR_INVALID_TAIL; 778 779 if (my_head.is_separate_file()) 780 return ERR_INVALID_HEAD; 781 782 int num_items = 0; 783 Addr address(my_head.value()); 784 Addr prev(my_head.value()); 785 scoped_ptr<CacheRankingsBlock> node; 786 do { 787 node.reset(new CacheRankingsBlock(backend_->File(address), address)); 788 node->Load(); 789 if (node->Data()->prev != prev.value()) 790 return ERR_INVALID_PREV; 791 if (!CheckEntry(node.get())) 792 return ERR_INVALID_ENTRY; 793 794 prev.set_value(address.value()); 795 address.set_value(node->Data()->next); 796 if (!address.is_initialized() || address.is_separate_file()) 797 return ERR_INVALID_NEXT; 798 799 num_items++; 800 } while (node->address().value() != address.value()); 801 return num_items; 802 } 803 804 bool Rankings::IsHead(CacheAddr addr, List* list) { 805 for (int i = 0; i < LAST_ELEMENT; i++) { 806 if (addr == heads_[i].value()) { 807 if (*list != i) 808 Trace("Changing list %d to %d", *list, i); 809 *list = static_cast<List>(i); 810 return true; 811 } 812 } 813 return false; 814 } 815 816 bool Rankings::IsTail(CacheAddr addr, List* list) { 817 for (int i = 0; i < LAST_ELEMENT; i++) { 818 if (addr == tails_[i].value()) { 819 if (*list != i) 820 Trace("Changing list %d to %d", *list, i); 821 *list = static_cast<List>(i); 822 return true; 823 } 824 } 825 return false; 826 } 827 828 // We expect to have just a few iterators at any given time, maybe two or three, 829 // But we could have more than one pointing at the same mode. We walk the list 830 // of cache iterators and update all that are pointing to the given node. 831 void Rankings::UpdateIterators(CacheRankingsBlock* node) { 832 CacheAddr address = node->address().value(); 833 for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end(); 834 ++it) { 835 if (it->first == address && it->second->HasData()) { 836 CacheRankingsBlock* other = it->second; 837 *other->Data() = *node->Data(); 838 } 839 } 840 } 841 842 void Rankings::InvalidateIterators(CacheRankingsBlock* node) { 843 CacheAddr address = node->address().value(); 844 for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end(); 845 ++it) { 846 if (it->first == address) { 847 #ifndef ANDROID 848 // Confirmed with chromium developers that this is normal, and removing from 849 // Android to close bug 3239659 850 LOG(WARNING) << "Invalidating iterator at 0x" << std::hex << address; 851 #endif 852 it->second->Discard(); 853 } 854 } 855 } 856 857 void Rankings::IncrementCounter(List list) { 858 if (!count_lists_) 859 return; 860 861 DCHECK(control_data_->sizes[list] < kint32max); 862 if (control_data_->sizes[list] < kint32max) 863 control_data_->sizes[list]++; 864 } 865 866 void Rankings::DecrementCounter(List list) { 867 if (!count_lists_) 868 return; 869 870 DCHECK(control_data_->sizes[list] > 0); 871 if (control_data_->sizes[list] > 0) 872 control_data_->sizes[list]--; 873 } 874 875 } // namespace disk_cache 876