| OLD | NEW |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/disk_cache/rankings.h" | 5 #include "net/disk_cache/rankings.h" |
| 6 | 6 |
| 7 #include "base/histogram.h" | 7 #include "base/histogram.h" |
| 8 #include "net/disk_cache/backend_impl.h" | 8 #include "net/disk_cache/backend_impl.h" |
| 9 #include "net/disk_cache/entry_impl.h" | 9 #include "net/disk_cache/entry_impl.h" |
| 10 #include "net/disk_cache/errors.h" | 10 #include "net/disk_cache/errors.h" |
| 11 | 11 |
| 12 using base::Time; | 12 using base::Time; |
| 13 | 13 |
| 14 // This is used by crash_cache.exe to generate unit test files. | 14 // This is used by crash_cache.exe to generate unit test files. |
| 15 disk_cache::RankCrashes g_rankings_crash = disk_cache::NO_CRASH; | 15 disk_cache::RankCrashes g_rankings_crash = disk_cache::NO_CRASH; |
| 16 | 16 |
| 17 namespace { | 17 namespace { |
| 18 | 18 |
| 19 const int kHeadIndex = 0; | 19 enum Lists { |
| 20 const int kTailIndex = 1; | 20 NO_USE = 0, // List of entries that have not been reused. |
| 21 const int kTransactionIndex = 2; | 21 LOW_USE, // List of entries with low reuse. |
| 22 const int kOperationIndex = 3; | 22 HIGH_USE, // List of entries with high reuse. |
| 23 DELETED, // List of recently deleted or doomed entries. |
| 24 LAST_ELEMENT |
| 25 }; |
| 23 | 26 |
| 24 enum Operation { | 27 enum Operation { |
| 25 INSERT = 1, | 28 INSERT = 1, |
| 26 REMOVE | 29 REMOVE |
| 27 }; | 30 }; |
| 28 | 31 |
| 29 // This class provides a simple lock for the LRU list of rankings. Whenever an | 32 // This class provides a simple lock for the LRU list of rankings. Whenever an |
| 30 // entry is to be inserted or removed from the list, a transaction object should | 33 // entry is to be inserted or removed from the list, a transaction object should |
| 31 // be created to keep track of the operation. If the process crashes before | 34 // be created to keep track of the operation. If the process crashes before |
| 32 // finishing the operation, the transaction record (stored as part of the user | 35 // finishing the operation, the transaction record (stored as part of the user |
| 33 // data on the file header) can be used to finish the operation. | 36 // data on the file header) can be used to finish the operation. |
| 34 class Transaction { | 37 class Transaction { |
| 35 public: | 38 public: |
| 36 // addr is the cache addres of the node being inserted or removed. We want to | 39 // addr is the cache addres of the node being inserted or removed. We want to |
| 37 // avoid having the compiler doing optimizations on when to read or write | 40 // avoid having the compiler doing optimizations on when to read or write |
| 38 // from user_data because it is the basis of the crash detection. Maybe | 41 // from user_data because it is the basis of the crash detection. Maybe |
| 39 // volatile is not enough for that, but it should be a good hint. | 42 // volatile is not enough for that, but it should be a good hint. |
| 40 Transaction(volatile int32* user_data, disk_cache::Addr addr, Operation op); | 43 Transaction(volatile disk_cache::LruData* data, disk_cache::Addr addr, |
| 44 Operation op, int list); |
| 41 ~Transaction(); | 45 ~Transaction(); |
| 42 private: | 46 private: |
| 43 volatile int32* user_data_; | 47 volatile disk_cache::LruData* data_; |
| 44 DISALLOW_EVIL_CONSTRUCTORS(Transaction); | 48 DISALLOW_COPY_AND_ASSIGN(Transaction); |
| 45 }; | 49 }; |
| 46 | 50 |
| 47 Transaction::Transaction(volatile int32* user_data, disk_cache::Addr addr, | 51 Transaction::Transaction(volatile disk_cache::LruData* data, |
| 48 Operation op) : user_data_(user_data) { | 52 disk_cache::Addr addr, Operation op, int list) |
| 49 DCHECK(!user_data_[kTransactionIndex]); | 53 : data_(data) { |
| 54 DCHECK(!data_->transaction); |
| 50 DCHECK(addr.is_initialized()); | 55 DCHECK(addr.is_initialized()); |
| 51 user_data_[kOperationIndex] = op; | 56 data_->operation = op; |
| 52 user_data_[kTransactionIndex] = static_cast<int32>(addr.value()); | 57 data_->operation_list = list; |
| 58 data_->transaction = addr.value(); |
| 53 } | 59 } |
| 54 | 60 |
| 55 Transaction::~Transaction() { | 61 Transaction::~Transaction() { |
| 56 DCHECK(user_data_[kTransactionIndex]); | 62 DCHECK(data_->transaction); |
| 57 user_data_[kTransactionIndex] = 0; | 63 data_->transaction = 0; |
| 58 user_data_[kOperationIndex] = 0; | 64 data_->operation = 0; |
| 65 data_->operation_list = 0; |
| 59 } | 66 } |
| 60 | 67 |
| 61 // Code locations that can generate crashes. | 68 // Code locations that can generate crashes. |
| 62 enum CrashLocation { | 69 enum CrashLocation { |
| 63 ON_INSERT_1, ON_INSERT_2, ON_INSERT_3, ON_INSERT_4, ON_REMOVE_1, ON_REMOVE_2, | 70 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 | 71 ON_REMOVE_3, ON_REMOVE_4, ON_REMOVE_5, ON_REMOVE_6, ON_REMOVE_7, ON_REMOVE_8 |
| 65 }; | 72 }; |
| 66 | 73 |
| 67 // Generates a crash on debug builds, acording to the value of g_rankings_crash. | 74 // Generates a crash on debug builds, acording to the value of g_rankings_crash. |
| 68 // This used by crash_cache.exe to generate unit-test files. | 75 // This used by crash_cache.exe to generate unit-test files. |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 153 } // namespace | 160 } // namespace |
| 154 | 161 |
| 155 namespace disk_cache { | 162 namespace disk_cache { |
| 156 | 163 |
| 157 bool Rankings::Init(BackendImpl* backend) { | 164 bool Rankings::Init(BackendImpl* backend) { |
| 158 DCHECK(!init_); | 165 DCHECK(!init_); |
| 159 if (init_) | 166 if (init_) |
| 160 return false; | 167 return false; |
| 161 | 168 |
| 162 backend_ = backend; | 169 backend_ = backend; |
| 163 MappedFile* file = backend_->File(Addr(RANKINGS, 0, 0, 0)); | |
| 164 | 170 |
| 165 header_ = reinterpret_cast<BlockFileHeader*>(file->buffer()); | 171 control_data_ = backend_->GetLruData(); |
| 166 | 172 |
| 167 head_ = ReadHead(); | 173 head_ = ReadHead(); |
| 168 tail_ = ReadTail(); | 174 tail_ = ReadTail(); |
| 169 | 175 |
| 170 if (header_->user[kTransactionIndex]) | 176 if (control_data_->transaction) |
| 171 CompleteTransaction(); | 177 CompleteTransaction(); |
| 172 | 178 |
| 173 init_ = true; | 179 init_ = true; |
| 174 return true; | 180 return true; |
| 175 } | 181 } |
| 176 | 182 |
| 177 void Rankings::Reset() { | 183 void Rankings::Reset() { |
| 178 init_ = false; | 184 init_ = false; |
| 179 head_.set_value(0); | 185 head_.set_value(0); |
| 180 tail_.set_value(0); | 186 tail_.set_value(0); |
| 181 header_ = NULL; | 187 control_data_ = NULL; |
| 182 } | 188 } |
| 183 | 189 |
| 184 bool Rankings::GetRanking(CacheRankingsBlock* rankings) { | 190 bool Rankings::GetRanking(CacheRankingsBlock* rankings) { |
| 185 Time start = Time::Now(); | 191 Time start = Time::Now(); |
| 186 if (!rankings->address().is_initialized()) | 192 if (!rankings->address().is_initialized()) |
| 187 return false; | 193 return false; |
| 188 | 194 |
| 189 if (!rankings->Load()) | 195 if (!rankings->Load()) |
| 190 return false; | 196 return false; |
| 191 | 197 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 213 EntryImpl* cache_entry = | 219 EntryImpl* cache_entry = |
| 214 reinterpret_cast<EntryImpl*>(rankings->Data()->pointer); | 220 reinterpret_cast<EntryImpl*>(rankings->Data()->pointer); |
| 215 rankings->SetData(cache_entry->rankings()->Data()); | 221 rankings->SetData(cache_entry->rankings()->Data()); |
| 216 UMA_HISTOGRAM_TIMES(L"DiskCache.GetRankings", Time::Now() - start); | 222 UMA_HISTOGRAM_TIMES(L"DiskCache.GetRankings", Time::Now() - start); |
| 217 return true; | 223 return true; |
| 218 } | 224 } |
| 219 | 225 |
| 220 void Rankings::Insert(CacheRankingsBlock* node, bool modified) { | 226 void Rankings::Insert(CacheRankingsBlock* node, bool modified) { |
| 221 Trace("Insert 0x%x", node->address().value()); | 227 Trace("Insert 0x%x", node->address().value()); |
| 222 DCHECK(node->HasData()); | 228 DCHECK(node->HasData()); |
| 223 Transaction lock(header_->user, node->address(), INSERT); | 229 Transaction lock(control_data_, node->address(), INSERT, NO_USE); |
| 224 CacheRankingsBlock head(backend_->File(head_), head_); | 230 CacheRankingsBlock head(backend_->File(head_), head_); |
| 225 if (head_.is_initialized()) { | 231 if (head_.is_initialized()) { |
| 226 if (!GetRanking(&head)) | 232 if (!GetRanking(&head)) |
| 227 return; | 233 return; |
| 228 | 234 |
| 229 if (head.Data()->prev != head_.value() && // Normal path. | 235 if (head.Data()->prev != head_.value() && // Normal path. |
| 230 head.Data()->prev != node->address().value()) { // FinishInsert(). | 236 head.Data()->prev != node->address().value()) { // FinishInsert(). |
| 231 backend_->CriticalError(ERR_INVALID_LINKS); | 237 backend_->CriticalError(ERR_INVALID_LINKS); |
| 232 return; | 238 return; |
| 233 } | 239 } |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 300 } | 306 } |
| 301 | 307 |
| 302 CacheRankingsBlock next(backend_->File(next_addr), next_addr); | 308 CacheRankingsBlock next(backend_->File(next_addr), next_addr); |
| 303 CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr); | 309 CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr); |
| 304 if (!GetRanking(&next) || !GetRanking(&prev)) | 310 if (!GetRanking(&next) || !GetRanking(&prev)) |
| 305 return; | 311 return; |
| 306 | 312 |
| 307 if (!CheckLinks(node, &prev, &next)) | 313 if (!CheckLinks(node, &prev, &next)) |
| 308 return; | 314 return; |
| 309 | 315 |
| 310 Transaction lock(header_->user, node->address(), REMOVE); | 316 Transaction lock(control_data_, node->address(), REMOVE, NO_USE); |
| 311 prev.Data()->next = next.address().value(); | 317 prev.Data()->next = next.address().value(); |
| 312 next.Data()->prev = prev.address().value(); | 318 next.Data()->prev = prev.address().value(); |
| 313 GenerateCrash(ON_REMOVE_1); | 319 GenerateCrash(ON_REMOVE_1); |
| 314 | 320 |
| 315 CacheAddr node_value = node->address().value(); | 321 CacheAddr node_value = node->address().value(); |
| 316 if (node_value == head_.value() || node_value == tail_.value()) { | 322 if (node_value == head_.value() || node_value == tail_.value()) { |
| 317 if (head_.value() == tail_.value()) { | 323 if (head_.value() == tail_.value()) { |
| 318 head_.set_value(0); | 324 head_.set_value(0); |
| 319 tail_.set_value(0); | 325 tail_.set_value(0); |
| 320 | 326 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 361 // but the net effect is just an assert on debug when attempting to remove the | 367 // but the net effect is just an assert on debug when attempting to remove the |
| 362 // entry. Otherwise we'll need reentrant transactions, which is an overkill. | 368 // entry. Otherwise we'll need reentrant transactions, which is an overkill. |
| 363 void Rankings::UpdateRank(CacheRankingsBlock* node, bool modified) { | 369 void Rankings::UpdateRank(CacheRankingsBlock* node, bool modified) { |
| 364 Time start = Time::Now(); | 370 Time start = Time::Now(); |
| 365 Remove(node); | 371 Remove(node); |
| 366 Insert(node, modified); | 372 Insert(node, modified); |
| 367 UMA_HISTOGRAM_TIMES(L"DiskCache.UpdateRank", Time::Now() - start); | 373 UMA_HISTOGRAM_TIMES(L"DiskCache.UpdateRank", Time::Now() - start); |
| 368 } | 374 } |
| 369 | 375 |
| 370 void Rankings::CompleteTransaction() { | 376 void Rankings::CompleteTransaction() { |
| 371 Addr node_addr(static_cast<CacheAddr>(header_->user[kTransactionIndex])); | 377 Addr node_addr(static_cast<CacheAddr>(control_data_->transaction)); |
| 372 if (!node_addr.is_initialized() || node_addr.is_separate_file()) { | 378 if (!node_addr.is_initialized() || node_addr.is_separate_file()) { |
| 373 NOTREACHED(); | 379 NOTREACHED(); |
| 374 LOG(ERROR) << "Invalid rankings info."; | 380 LOG(ERROR) << "Invalid rankings info."; |
| 375 return; | 381 return; |
| 376 } | 382 } |
| 377 | 383 |
| 378 Trace("CompleteTransaction 0x%x", node_addr.value()); | 384 Trace("CompleteTransaction 0x%x", node_addr.value()); |
| 379 | 385 |
| 380 CacheRankingsBlock node(backend_->File(node_addr), node_addr); | 386 CacheRankingsBlock node(backend_->File(node_addr), node_addr); |
| 381 if (!node.Load()) | 387 if (!node.Load()) |
| 382 return; | 388 return; |
| 383 | 389 |
| 384 node.Data()->pointer = NULL; | 390 node.Data()->pointer = NULL; |
| 385 node.Store(); | 391 node.Store(); |
| 386 | 392 |
| 387 // We want to leave the node inside the list. The entry must me marked as | 393 // We want to leave the node inside the list. The entry must me marked as |
| 388 // dirty, and will be removed later. Otherwise, we'll get assertions when | 394 // dirty, and will be removed later. Otherwise, we'll get assertions when |
| 389 // attempting to remove the dirty entry. | 395 // attempting to remove the dirty entry. |
| 390 if (INSERT == header_->user[kOperationIndex]) { | 396 if (INSERT == control_data_->operation) { |
| 391 Trace("FinishInsert h:0x%x t:0x%x", head_.value(), tail_.value()); | 397 Trace("FinishInsert h:0x%x t:0x%x", head_.value(), tail_.value()); |
| 392 FinishInsert(&node); | 398 FinishInsert(&node); |
| 393 } else if (REMOVE == header_->user[kOperationIndex]) { | 399 } else if (REMOVE == control_data_->operation) { |
| 394 Trace("RevertRemove h:0x%x t:0x%x", head_.value(), tail_.value()); | 400 Trace("RevertRemove h:0x%x t:0x%x", head_.value(), tail_.value()); |
| 395 RevertRemove(&node); | 401 RevertRemove(&node); |
| 396 } else { | 402 } else { |
| 397 NOTREACHED(); | 403 NOTREACHED(); |
| 398 LOG(ERROR) << "Invalid operation to recover."; | 404 LOG(ERROR) << "Invalid operation to recover."; |
| 399 } | 405 } |
| 400 } | 406 } |
| 401 | 407 |
| 402 void Rankings::FinishInsert(CacheRankingsBlock* node) { | 408 void Rankings::FinishInsert(CacheRankingsBlock* node) { |
| 403 header_->user[kTransactionIndex] = 0; | 409 control_data_->transaction = 0; |
| 404 header_->user[kOperationIndex] = 0; | 410 control_data_->operation = 0; |
| 405 if (head_.value() != node->address().value()) { | 411 if (head_.value() != node->address().value()) { |
| 406 if (tail_.value() == node->address().value()) { | 412 if (tail_.value() == node->address().value()) { |
| 407 // This part will be skipped by the logic of Insert. | 413 // This part will be skipped by the logic of Insert. |
| 408 node->Data()->next = tail_.value(); | 414 node->Data()->next = tail_.value(); |
| 409 } | 415 } |
| 410 | 416 |
| 411 Insert(node, true); | 417 Insert(node, true); |
| 412 } | 418 } |
| 413 | 419 |
| 414 // Tell the backend about this entry. | 420 // Tell the backend about this entry. |
| 415 backend_->RecoveredEntry(node); | 421 backend_->RecoveredEntry(node); |
| 416 } | 422 } |
| 417 | 423 |
| 418 void Rankings::RevertRemove(CacheRankingsBlock* node) { | 424 void Rankings::RevertRemove(CacheRankingsBlock* node) { |
| 419 Addr next_addr(node->Data()->next); | 425 Addr next_addr(node->Data()->next); |
| 420 Addr prev_addr(node->Data()->prev); | 426 Addr prev_addr(node->Data()->prev); |
| 421 if (!next_addr.is_initialized() || !prev_addr.is_initialized()) { | 427 if (!next_addr.is_initialized() || !prev_addr.is_initialized()) { |
| 422 // The operation actually finished. Nothing to do. | 428 // The operation actually finished. Nothing to do. |
| 423 header_->user[kTransactionIndex] = 0; | 429 control_data_->transaction = 0; |
| 424 return; | 430 return; |
| 425 } | 431 } |
| 426 if (next_addr.is_separate_file() || prev_addr.is_separate_file()) { | 432 if (next_addr.is_separate_file() || prev_addr.is_separate_file()) { |
| 427 NOTREACHED(); | 433 NOTREACHED(); |
| 428 LOG(WARNING) << "Invalid rankings info."; | 434 LOG(WARNING) << "Invalid rankings info."; |
| 429 header_->user[kTransactionIndex] = 0; | 435 control_data_->transaction = 0; |
| 430 return; | 436 return; |
| 431 } | 437 } |
| 432 | 438 |
| 433 CacheRankingsBlock next(backend_->File(next_addr), next_addr); | 439 CacheRankingsBlock next(backend_->File(next_addr), next_addr); |
| 434 CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr); | 440 CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr); |
| 435 if (!next.Load() || !prev.Load()) | 441 if (!next.Load() || !prev.Load()) |
| 436 return; | 442 return; |
| 437 | 443 |
| 438 CacheAddr node_value = node->address().value(); | 444 CacheAddr node_value = node->address().value(); |
| 439 DCHECK(prev.Data()->next == node_value || | 445 DCHECK(prev.Data()->next == node_value || |
| (...skipping 18 matching lines...) Expand all Loading... |
| 458 prev.Data()->next = next.address().value(); | 464 prev.Data()->next = next.address().value(); |
| 459 WriteHead(); | 465 WriteHead(); |
| 460 } else if (tail_.value() == prev.address().value()) { | 466 } else if (tail_.value() == prev.address().value()) { |
| 461 tail_.set_value(node_value); | 467 tail_.set_value(node_value); |
| 462 next.Data()->prev = prev.address().value(); | 468 next.Data()->prev = prev.address().value(); |
| 463 WriteTail(); | 469 WriteTail(); |
| 464 } | 470 } |
| 465 | 471 |
| 466 next.Store(); | 472 next.Store(); |
| 467 prev.Store(); | 473 prev.Store(); |
| 468 header_->user[kTransactionIndex] = 0; | 474 control_data_->transaction = 0; |
| 469 header_->user[kOperationIndex] = 0; | 475 control_data_->operation = 0; |
| 470 } | 476 } |
| 471 | 477 |
| 472 CacheRankingsBlock* Rankings::GetNext(CacheRankingsBlock* node) { | 478 CacheRankingsBlock* Rankings::GetNext(CacheRankingsBlock* node) { |
| 473 ScopedRankingsBlock next(this); | 479 ScopedRankingsBlock next(this); |
| 474 if (!node) { | 480 if (!node) { |
| 475 if (!head_.is_initialized()) | 481 if (!head_.is_initialized()) |
| 476 return NULL; | 482 return NULL; |
| 477 next.reset(new CacheRankingsBlock(backend_->File(head_), head_)); | 483 next.reset(new CacheRankingsBlock(backend_->File(head_), head_)); |
| 478 } else { | 484 } else { |
| 479 if (!tail_.is_initialized()) | 485 if (!tail_.is_initialized()) |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 581 if ((node->address().value() == data->prev) && (head_.value() != data->prev)) | 587 if ((node->address().value() == data->prev) && (head_.value() != data->prev)) |
| 582 return false; | 588 return false; |
| 583 | 589 |
| 584 if ((node->address().value() == data->next) && (tail_.value() != data->next)) | 590 if ((node->address().value() == data->next) && (tail_.value() != data->next)) |
| 585 return false; | 591 return false; |
| 586 | 592 |
| 587 return true; | 593 return true; |
| 588 } | 594 } |
| 589 | 595 |
| 590 Addr Rankings::ReadHead() { | 596 Addr Rankings::ReadHead() { |
| 591 CacheAddr head = static_cast<CacheAddr>(header_->user[kHeadIndex]); | 597 return Addr(control_data_->heads[NO_USE]); |
| 592 return Addr(head); | |
| 593 } | 598 } |
| 594 | 599 |
| 595 Addr Rankings::ReadTail() { | 600 Addr Rankings::ReadTail() { |
| 596 CacheAddr tail = static_cast<CacheAddr>(header_->user[kTailIndex]); | 601 return Addr(control_data_->tails[NO_USE]); |
| 597 return Addr(tail); | |
| 598 } | 602 } |
| 599 | 603 |
| 600 void Rankings::WriteHead() { | 604 void Rankings::WriteHead() { |
| 601 header_->user[kHeadIndex] = static_cast<int32>(head_.value()); | 605 control_data_->heads[NO_USE] = head_.value(); |
| 602 } | 606 } |
| 603 | 607 |
| 604 void Rankings::WriteTail() { | 608 void Rankings::WriteTail() { |
| 605 header_->user[kTailIndex] = static_cast<int32>(tail_.value()); | 609 control_data_->tails[NO_USE] = tail_.value(); |
| 606 } | 610 } |
| 607 | 611 |
| 608 bool Rankings::CheckEntry(CacheRankingsBlock* rankings) { | 612 bool Rankings::CheckEntry(CacheRankingsBlock* rankings) { |
| 609 if (!rankings->Data()->pointer) | 613 if (!rankings->Data()->pointer) |
| 610 return true; | 614 return true; |
| 611 | 615 |
| 612 // If this entry is not dirty, it is a serious problem. | 616 // If this entry is not dirty, it is a serious problem. |
| 613 return backend_->GetCurrentEntryId() != rankings->Data()->dirty; | 617 return backend_->GetCurrentEntryId() != rankings->Data()->dirty; |
| 614 } | 618 } |
| 615 | 619 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 671 ++it) { | 675 ++it) { |
| 672 if (it->first == address) { | 676 if (it->first == address) { |
| 673 CacheRankingsBlock* other = it->second; | 677 CacheRankingsBlock* other = it->second; |
| 674 other->Data()->next = node->Data()->next; | 678 other->Data()->next = node->Data()->next; |
| 675 other->Data()->prev = node->Data()->prev; | 679 other->Data()->prev = node->Data()->prev; |
| 676 } | 680 } |
| 677 } | 681 } |
| 678 } | 682 } |
| 679 | 683 |
| 680 } // namespace disk_cache | 684 } // namespace disk_cache |
| OLD | NEW |