Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(71)

Side by Side Diff: net/disk_cache/v3/index_table.cc

Issue 53313004: Disk cache v3: The main index table. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: rename address and hash Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « net/disk_cache/v3/index_table.h ('k') | net/disk_cache/v3/index_table_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2013 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/v3/index_table.h"
6
7 #include <algorithm>
8 #include <set>
9 #include <utility>
10
11 #include "base/bits.h"
12 #include "net/base/io_buffer.h"
13 #include "net/base/net_errors.h"
14 #include "net/disk_cache/disk_cache.h"
15
16 using base::Time;
17 using base::TimeDelta;
18 using disk_cache::CellInfo;
19 using disk_cache::CellList;
20 using disk_cache::IndexCell;
21 using disk_cache::IndexIterator;
22
23 namespace {
24
25 const int kCellIdOffset = 22;
Randy Smith (Not in Mondays) 2014/01/06 22:28:35 How about a comment before this group of constants
rvargas (doing something else) 2014/01/08 01:29:12 Done.
26 const int kCellSmallTableIdOffset = 16;
27 const int kCellTimestampOffset = 40;
28 const int kCellReuseOffset = 60;
29 const int kCellGroupOffset = 3;
30 const int kCellSumOffset = 6;
31
32 const uint64 kCellLocationMask = 0x3FFFFF;
Randy Smith (Not in Mondays) 2014/01/06 22:28:35 Suggestion/nit: I'd personally find these easier t
Randy Smith (Not in Mondays) 2014/01/06 22:28:35 Suggestion/nit: As sorta implied by the above comm
rvargas (doing something else) 2014/01/08 01:29:12 I tried intermixing the values, but I found that t
33 const uint64 kCellSmallTableLocationMask = 0xFFFF;
34 const uint64 kCellIdMask = 0x3FFFF;
35 const uint64 kCellSmallTableIdMask = 0xFFFFFF;
36 const uint64 kCellTimestampMask = 0xFFFFF;
37 const uint64 kCellReuseMask = 0xF;
38 const uint8 kCellStateMask = 0x7;
39 const uint8 kCellGroupMask = 0x7;
40 const uint8 kCellSumMask = 0x3;
41
42 const int kHashShift = 14;
Randy Smith (Not in Mondays) 2014/01/06 22:28:35 Comment documenting that these are to turn a hash
rvargas (doing something else) 2014/01/08 01:29:12 Done.
43 const int kSmallTableHashShift = 8;
44
45 // Unfortunately we have to break the abstaction a little here: the file number
46 // where entries are stored is outside of the control of this code, and it is
47 // usually part of the stored address. However, for small tables we only store
48 // 16 bits of the address so the file number is never stored on a cell. We have
49 // to infere the file number from the type of entry (normal vs evicted), and
50 // the knowledge that given that the table will not keep more than 64k entries,
51 // a single file of each type is enough.
52 const int kEntriesFile = disk_cache::BLOCK_ENTRIES - 1;
53 const int kEvictedEntriesFile = disk_cache::BLOCK_EVICTED - 1;
54 const int kMaxLocation = 1 << 22;
55 const int kMinFileNumber = 1 << 16;
56
57 uint32 GetCellLocation(const IndexCell& cell) {
58 return cell.first_part & kCellLocationMask;
59 }
60
61 uint32 GetCellSmallTableLocation(const IndexCell& cell) {
62 return cell.first_part & kCellSmallTableLocationMask;
63 }
64
65 uint32 GetCellId(const IndexCell& cell) {
66 return (cell.first_part >> kCellIdOffset) & kCellIdMask;
67 }
68
69 uint32 GetCellSmallTableId(const IndexCell& cell) {
70 return (cell.first_part >> kCellSmallTableIdOffset) &
71 kCellSmallTableIdMask;
72 }
73
74 int GetCellTimestamp(const IndexCell& cell) {
75 return (cell.first_part >> kCellTimestampOffset) & kCellTimestampMask;
76 }
77
78 int GetCellReuse(const IndexCell& cell) {
79 return (cell.first_part >> kCellReuseOffset) & kCellReuseMask;
80 }
81
82 int GetCellState(const IndexCell& cell) {
83 return cell.last_part & kCellStateMask;
84 }
85
86 int GetCellGroup(const IndexCell& cell) {
87 return (cell.last_part >> kCellGroupOffset) & kCellGroupMask;
88 }
89
90 int GetCellSum(const IndexCell& cell) {
91 return (cell.last_part >> kCellSumOffset) & kCellSumMask;
92 }
93
94 void SetCellLocation(IndexCell* cell, uint32 address) {
95 DCHECK_LE(address, static_cast<uint32>(kCellLocationMask));
96 cell->first_part &= ~kCellLocationMask;
97 cell->first_part |= address;
98 }
99
100 void SetCellSmallTableLocation(IndexCell* cell, uint32 address) {
101 DCHECK_LE(address, static_cast<uint32>(kCellSmallTableLocationMask));
102 cell->first_part &= ~kCellSmallTableLocationMask;
103 cell->first_part |= address;
104 }
105
106 void SetCellId(IndexCell* cell, uint32 hash) {
107 DCHECK_LE(hash, static_cast<uint32>(kCellIdMask));
108 cell->first_part &= ~(kCellIdMask << kCellIdOffset);
109 cell->first_part |= static_cast<int64>(hash) << kCellIdOffset;
110 }
111
112 void SetCellSmallTableId(IndexCell* cell, uint32 hash) {
113 DCHECK_LE(hash, static_cast<uint32>(kCellSmallTableIdMask));
114 cell->first_part &= ~(kCellSmallTableIdMask << kCellSmallTableIdOffset);
115 cell->first_part |= static_cast<int64>(hash) << kCellSmallTableIdOffset;
116 }
117
118 void SetCellTimestamp(IndexCell* cell, int timestamp) {
119 DCHECK_LT(timestamp, 1 << 20);
120 DCHECK_GE(timestamp, 0);
121 cell->first_part &= ~(kCellTimestampMask << kCellTimestampOffset);
122 cell->first_part |= static_cast<int64>(timestamp) << kCellTimestampOffset;
123 }
124
125 void SetCellReuse(IndexCell* cell, int count) {
126 DCHECK_LT(count, 16);
127 DCHECK_GE(count, 0);
128 cell->first_part &= ~(kCellReuseMask << kCellReuseOffset);
129 cell->first_part |= static_cast<int64>(count) << kCellReuseOffset;
130 }
131
132 void SetCellState(IndexCell* cell, disk_cache::EntryState state) {
133 cell->last_part &= ~kCellStateMask;
134 cell->last_part |= state;
135 }
136
137 void SetCellGroup(IndexCell* cell, disk_cache::EntryGroup group) {
138 cell->last_part &= ~(kCellGroupMask << kCellGroupOffset);
139 cell->last_part |= group << kCellGroupOffset;
140 }
141
142 void SetCellSum(IndexCell* cell, int sum) {
143 DCHECK_LT(sum, 4);
144 DCHECK_GE(sum, 0);
145 cell->last_part &= ~(kCellSumMask << kCellSumOffset);
146 cell->last_part |= sum << kCellSumOffset;
147 }
148
149 // This is a very particular way to calculate the sum, so it will not match if
150 // compared a gainst a pure 2 bit, modulo 2 sum.
151 int CalculateCellSum(const IndexCell& cell) {
152 uint32* words = bit_cast<uint32*>(&cell);
153 uint8* bytes = bit_cast<uint8*>(&cell);
154 uint32 result = words[0] + words[1];
155 result += result >> 16;
156 result += (result >> 8) + (bytes[8] & 0x3f);
157 result += result >> 4;
158 result += result >> 2;
159 return result & 3;
160 }
161
162 bool SanityCheck(const IndexCell& cell) {
163 if (GetCellSum(cell) != CalculateCellSum(cell))
164 return false;
165
166 if (GetCellState(cell) > disk_cache::ENTRY_USED ||
167 GetCellGroup(cell) == disk_cache::ENTRY_RESERVED ||
168 GetCellGroup(cell) > disk_cache::ENTRY_EVICTED) {
169 return false;
170 }
171
172 return true;
173 }
174
175 int FileNumberFromLocation(int location) {
176 return location / kMinFileNumber;
177 }
178
179 int StartBlockFromLocation(int location) {
180 return location % kMinFileNumber;
181 }
182
183 bool IsValidAddress(disk_cache::Addr address) {
184 if (!address.is_initialized() ||
185 (address.file_type() != disk_cache::BLOCK_EVICTED &&
186 address.file_type() != disk_cache::BLOCK_ENTRIES)) {
187 return false;
188 }
189
190 return address.FileNumber() < FileNumberFromLocation(kMaxLocation);
191 }
192
193 bool IsNormalState(const IndexCell& cell) {
194 disk_cache::EntryState state =
195 static_cast<disk_cache::EntryState>(GetCellState(cell));
196 DCHECK_NE(state, disk_cache::ENTRY_FREE);
197 return state != disk_cache::ENTRY_DELETED &&
198 state != disk_cache::ENTRY_FIXING;
199 }
200
201 inline int GetNextBucket(int min_bucket_num, int max_bucket_num,
202 disk_cache::IndexBucket* table,
203 disk_cache::IndexBucket** bucket) {
204 if (!(*bucket)->next)
205 return 0;
206
207 int bucket_num = (*bucket)->next / disk_cache::kCellsPerBucket;
208 if (bucket_num < min_bucket_num || bucket_num > max_bucket_num) {
209 // The next bucket must fall within the extra table. That include buckets
210 // that are relinked when growing the table.
Randy Smith (Not in Mondays) 2014/01/06 22:28:35 Would you be willing to say something like "When t
rvargas (doing something else) 2014/01/08 01:29:12 Extended the comment. I'm not sure what would be t
211 (*bucket)->next = 0;
212 return 0;
213 }
214 *bucket = &table[bucket_num - min_bucket_num];
215 return bucket_num;
216 }
217
218 // Updates the |iterator| with the current |cell|. This cell may cause all
219 // previous cells to be deleted (when a new target timestamp is found), the cell
220 // may be added to the list (if it matches the target timestamp), or may it be
221 // ignored.
222 void UpdateIterator(const disk_cache::EntryCell& cell,
223 int limit_time,
224 IndexIterator* iterator) {
225 int time = cell.GetTimestamp();
226 // Look for not interesting times.
227 if (iterator->forward && time <= limit_time)
228 return;
229 if (!iterator->forward && time >= limit_time)
230 return;
231
232 if ((iterator->forward && time < iterator->timestamp) ||
233 (!iterator->forward && time > iterator->timestamp)) {
234 // This timestamp is better than the one we had.
235 iterator->timestamp = time;
236 iterator->cells.clear();
237 }
238 if (time == iterator->timestamp) {
239 CellInfo cell_info = { cell.hash(), cell.GetAddress() };
240 iterator->cells.push_back(cell_info);
241 }
242 }
243
244 void InitIterator(IndexIterator* iterator) {
245 iterator->cells.clear();
246 iterator->timestamp = iterator->forward ? kint32max : 0;
247 }
248
249 } // namespace
250
251 namespace disk_cache {
252
253 EntryCell::~EntryCell() {
254 }
255
256 bool EntryCell::IsValid() const {
257 return GetCellLocation(cell_) != 0;
258 }
259
260 // This code has to map the cell address (up to 22 bits) to a general cache Addr
261 // (up to 24 bits of general addressing). It also set the implied file_number
262 // in the case of small tables. See also the comment by the definition of
263 // kEntriesFile.
264 Addr EntryCell::GetAddress() const {
265 uint32 location = GetLocation();
266 int file_number = FileNumberFromLocation(location);
267 if (small_table_) {
268 DCHECK_EQ(0, file_number);
269 file_number = (GetGroup() == ENTRY_EVICTED) ? kEvictedEntriesFile :
270 kEntriesFile;
271 }
272 DCHECK_NE(0, file_number);
273 FileType file_type = (GetGroup() == ENTRY_EVICTED) ? BLOCK_EVICTED :
274 BLOCK_ENTRIES;
275 return Addr(file_type, 1, file_number, StartBlockFromLocation(location));
276 }
277
278 EntryState EntryCell::GetState() const {
279 return static_cast<EntryState>(cell_.last_part & kCellStateMask);
280 }
281
282 EntryGroup EntryCell::GetGroup() const {
283 return static_cast<EntryGroup>((cell_.last_part >> kCellGroupOffset) &
284 kCellGroupMask);
285 }
286
287 int EntryCell::GetReuse() const {
288 return (cell_.first_part >> kCellReuseOffset) & kCellReuseMask;
289 }
290
291 int EntryCell::GetTimestamp() const {
292 return GetCellTimestamp(cell_);
293 }
294
295 void EntryCell::SetState(EntryState state) {
296 SetCellState(&cell_, state);
297 }
298
299 void EntryCell::SetGroup(EntryGroup group) {
300 SetCellGroup(&cell_, group);
301 }
302
303 void EntryCell::SetReuse(int count) {
304 SetCellReuse(&cell_, count);
305 }
306
307 void EntryCell::SetTimestamp(int timestamp) {
308 SetCellTimestamp(&cell_, timestamp);
309 }
310
311 // Static.
312 EntryCell EntryCell::GetEntryCellForTest(int32 cell_num,
313 uint32 hash,
314 Addr address,
315 IndexCell* cell,
316 bool small_table) {
317 if (cell) {
318 EntryCell entry_cell(cell_num, hash, *cell, small_table);
319 return entry_cell;
320 }
321
322 return EntryCell(cell_num, hash, address, small_table);
323 }
324
325 void EntryCell::SerializaForTest(IndexCell* destination) {
326 FixSum();
327 Serialize(destination);
328 }
329
330 EntryCell::EntryCell() : cell_num_(0), hash_(0), small_table_(false) {
331 cell_.Clear();
332 }
333
334 EntryCell::EntryCell(int32 cell_num,
335 uint32 hash,
336 Addr address,
337 bool small_table)
338 : cell_num_(cell_num),
339 hash_(hash),
340 small_table_(small_table) {
341 DCHECK(IsValidAddress(address) || !address.value());
342
343 cell_.Clear();
344 SetCellState(&cell_, ENTRY_NEW);
345 SetCellGroup(&cell_, ENTRY_NO_USE);
346 if (small_table) {
347 DCHECK(address.FileNumber() == kEntriesFile ||
348 address.FileNumber() == kEvictedEntriesFile);
349 SetCellSmallTableLocation(&cell_, address.start_block());
350 SetCellSmallTableId(&cell_, hash >> kSmallTableHashShift);
351 } else {
352 uint32 location = address.FileNumber() << 16 | address.start_block();
353 SetCellLocation(&cell_, location);
354 SetCellId(&cell_, hash >> kHashShift);
355 }
356 }
357
358 EntryCell::EntryCell(int32 cell_num,
359 uint32 hash,
360 const IndexCell& cell,
361 bool small_table)
362 : cell_num_(cell_num),
363 hash_(hash),
364 cell_(cell),
365 small_table_(small_table) {
366 }
367
368 void EntryCell::FixSum() {
369 SetCellSum(&cell_, CalculateCellSum(cell_));
370 }
371
372 uint32 EntryCell::GetLocation() const {
373 if (small_table_)
374 return GetCellSmallTableLocation(cell_);
375
376 return GetCellLocation(cell_);
377 }
378
379 uint32 EntryCell::RecomputeHash() {
380 if (small_table_) {
381 hash_ &= (1 << kSmallTableHashShift) - 1;
382 hash_ |= GetCellSmallTableId(cell_) << kSmallTableHashShift;
383 return hash_;
384 }
385
386 hash_ &= (1 << kHashShift) - 1;
387 hash_ |= GetCellId(cell_) << kHashShift;
388 return hash_;
389 }
390
391 void EntryCell::Serialize(IndexCell* destination) const {
392 *destination = cell_;
393 }
394
395 EntrySet::EntrySet() : evicted_count(0), current(0) {
396 }
397
398 EntrySet::~EntrySet() {
399 }
400
401 IndexIterator::IndexIterator() {
402 }
403
404 IndexIterator::~IndexIterator() {
405 }
406
407 IndexTableInitData::IndexTableInitData() {
408 }
409
410 IndexTableInitData::~IndexTableInitData() {
411 }
412
413 // -----------------------------------------------------------------------
414
415 IndexTable::IndexTable(IndexTableBackend* backend)
416 : backend_(backend),
417 header_(NULL),
418 main_table_(NULL),
419 extra_table_(NULL),
420 modified_(false),
421 small_table_(false) {
422 }
423
424 IndexTable::~IndexTable() {
425 }
426
427 // For a general description of the index tables see:
428 // http://www.chromium.org/developers/design-documents/network-stack/disk-cache/ disk-cache-v3#TOC-Index
429 //
430 // The index is split between two tables: the main_table_ and the extra_table_.
431 // The main table can grow only by doubling its number of cells, while the
432 // extra table can grow slowly, because it only contain cells that overflow
433 // from the main table. In order to locate a given cell, part of the hash is
434 // used directly as an index into the main table; once that bucket is located,
435 // all cells with that partial hash (i.e., belonging to that bucket) are
436 // inspected, and if present, the next bucket (located on the extra table) is
437 // then located. For more information on bucket chaining see:
438 // http://www.chromium.org/developers/design-documents/network-stack/disk-cache/ disk-cache-v3#TOC-Buckets
439 //
440 // There are two cases when increasing the size:
441 // - Doubling the size of the main table
442 // - Adding more entries to the extra table
443 //
444 // For example, consider a 64k main table with 8k cells on the extra table (for
445 // a total of 72k cells). Init can be called to add another 8k cells at the end
446 // (grow to 80k cells). When the size of the extra table approaches 64k, Init
447 // can be called to double the main table (to 128k) and go back to a small extra
448 // table.
449 void IndexTable::Init(IndexTableInitData* params) {
450 bool growing = header_ != NULL;
451 scoped_ptr<IndexBucket[]> old_extra_table;
452 header_ = &params->index_bitmap->header;
453
454 if (params->main_table) {
455 if (main_table_) {
456 // This is doubling the size of main table.
457 DCHECK_EQ(base::bits::Log2Floor(header_->table_len),
458 base::bits::Log2Floor(backup_header_->table_len) + 1);
459 int extra_size = (header()->max_bucket - mask_) * kCellsPerBucket;
460 DCHECK_GE(extra_size, 0);
461
462 // Doubling the size implies deleting the extra table and moving as many
463 // cells as we can to the main table, so we first copy the old one. This
464 // is not required when just growing the extra table because we don't
465 // move any cell in that case.
466 old_extra_table.reset(new IndexBucket[extra_size]);
467 memcpy(old_extra_table.get(), extra_table_,
468 extra_size * sizeof(IndexBucket));
469 memset(params->extra_table, 0, extra_size * sizeof(IndexBucket));
470 }
471 main_table_ = params->main_table;
472 }
473 DCHECK(main_table_);
474 extra_table_ = params->extra_table;
475
476 // extra_bits_ is really measured against table-size specific values.
477 const int kMaxAbsoluteExtraBits = 12; // From smallest to largest table.
478 const int kMaxExtraBitsSmallTable = 6; // From smallest to 64K table.
479
480 extra_bits_ = base::bits::Log2Floor(header_->table_len) -
481 base::bits::Log2Floor(kBaseTableLen);
482 DCHECK_GE(extra_bits_, 0);
483 DCHECK_LT(extra_bits_, kMaxAbsoluteExtraBits);
484
485 // Note that following the previous code the constants could be derived as
486 // kMaxAbsoluteExtraBits = base::bits::Log2Floor(max table len) -
487 // base::bits::Log2Floor(kBaseTableLen);
488 // = 22 - base::bits::Log2Floor(1024) = 22 - 10;
489 // kMaxExtraBitsSmallTable = base::bits::Log2Floor(max 16 bit table) - 10.
Randy Smith (Not in Mondays) 2014/01/06 22:28:35 Thank you for these comments.
490
491 mask_ = ((kBaseTableLen / kCellsPerBucket) << extra_bits_) - 1;
492 small_table_ = extra_bits_ < kMaxExtraBitsSmallTable;
493 if (!small_table_)
494 extra_bits_ -= kMaxExtraBitsSmallTable;
495
496 // table_len keeps the max number of cells stored by the index. We need a
497 // bitmap with 1 bit per cell, and that bitmap has num_words 32-bit words.
498 int num_words = (header_->table_len + 31) / 32;
499
500 if (old_extra_table) {
501 // All the cells from the extra table are moving to the new tables so before
502 // creating the bitmaps, clear the part of the bitmap referring to the extra
503 // table.
504 int old_main_table_bit_words = ((mask_ >> 1) + 1) * kCellsPerBucket / 32;
505 DCHECK_GT(num_words, old_main_table_bit_words);
506 memset(params->index_bitmap->bitmap + old_main_table_bit_words, 0,
507 (num_words - old_main_table_bit_words) * sizeof(int32));
508
509 DCHECK(growing);
510 int old_num_words = (backup_header_.get()->table_len + 31) / 32;
511 DCHECK_GT(old_num_words, old_main_table_bit_words);
512 memset(backup_bitmap_storage_.get() + old_main_table_bit_words, 0,
513 (old_num_words - old_main_table_bit_words) * sizeof(int32));
514 }
515 bitmap_.reset(new Bitmap(params->index_bitmap->bitmap, header_->table_len,
516 num_words));
517
518 if (growing) {
519 int old_num_words = (backup_header_.get()->table_len + 31) / 32;
520 DCHECK_GE(num_words, old_num_words);
521 scoped_ptr<uint32[]> storage(new uint32[num_words]);
522 memcpy(storage.get(), backup_bitmap_storage_.get(),
523 old_num_words * sizeof(int32));
524 memset(storage.get() + old_num_words, 0,
525 (num_words - old_num_words) * sizeof(int32));
526
527 backup_bitmap_storage_.swap(storage);
528 backup_header_->table_len = header_->table_len;
529 } else {
530 backup_bitmap_storage_.reset(params->backup_bitmap.release());
531 backup_header_.reset(params->backup_header.release());
532 }
533
534 num_words = (backup_header_->table_len + 31) / 32;
535 backup_bitmap_.reset(new Bitmap(backup_bitmap_storage_.get(),
536 backup_header_->table_len, num_words));
537 if (old_extra_table)
538 MoveCells(old_extra_table.get());
539
540 if (small_table_)
541 DCHECK(header_->flags & SMALL_CACHE);
542
543 // All tables and backups are needed for operation.
544 DCHECK(main_table_);
545 DCHECK(extra_table_);
546 DCHECK(bitmap_.get());
547 }
548
549 void IndexTable::Shutdown() {
550 header_ = NULL;
551 main_table_ = NULL;
552 extra_table_ = NULL;
553 bitmap_.reset();
554 backup_bitmap_.reset();
555 backup_header_.reset();
556 backup_bitmap_storage_.reset();
557 modified_ = false;
558 }
559
560 // The general method for locating cells is to:
561 // 1. Get the first bucket. This usually means directly indexing the table (as
562 // this method does), or iterating through all possible buckets.
563 // 2. Iterate through all the cells in that first bucket.
564 // 3. If there is a linked bucket, locate it directly in the extra table.
565 // 4. Go back to 2, as needed.
566 //
567 // One consequence of this pattern is that we never start looking at buckets in
568 // the extra table, unless we are following a link from the main table.
569 EntrySet IndexTable::LookupEntries(uint32 hash) {
570 EntrySet entries;
571 int bucket_num = static_cast<int>(hash & mask_);
572 IndexBucket* bucket = &main_table_[bucket_num];
573 do {
574 for (int i = 0; i < kCellsPerBucket; i++) {
575 IndexCell* current_cell = &bucket->cells[i];
576 if (!GetLocation(*current_cell))
577 continue;
578 if (!SanityCheck(*current_cell)) {
579 NOTREACHED();
580 int cell_num = bucket_num * kCellsPerBucket + i;
581 current_cell->Clear();
582 bitmap_->Set(cell_num, false);
583 backup_bitmap_->Set(cell_num, false);
584 modified_ = true;
585 continue;
586 }
587 int cell_num = bucket_num * kCellsPerBucket + i;
588 if (MisplacedHash(*current_cell, hash)) {
589 HandleMisplacedCell(current_cell, cell_num, hash & mask_);
590 } else if (IsHashMatch(*current_cell, hash)) {
591 EntryCell entry_cell(cell_num, hash, *current_cell, small_table_);
592 CheckState(entry_cell);
593 if (entry_cell.GetState() != ENTRY_DELETED) {
594 entries.cells.push_back(entry_cell);
595 if (entry_cell.GetGroup() == ENTRY_EVICTED)
596 entries.evicted_count++;
597 }
598 }
599 }
600 bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
601 &bucket);
602 } while (bucket_num);
603 return entries;
604 }
605
606 EntryCell IndexTable::CreateEntryCell(uint32 hash, Addr address) {
607 DCHECK(IsValidAddress(address));
608 DCHECK(address.FileNumber() || address.start_block());
609
610 int bucket_num = static_cast<int>(hash & mask_);
611 int cell_num = 0;
612 IndexBucket* bucket = &main_table_[bucket_num];
613 IndexCell* current_cell = NULL;
614 bool found = false;
615 do {
616 for (int i = 0; i < kCellsPerBucket && !found; i++) {
617 current_cell = &bucket->cells[i];
618 if (!GetLocation(*current_cell)) {
619 cell_num = bucket_num * kCellsPerBucket + i;
620 found = true;
621 }
622 }
623 if (found)
624 break;
625 bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
626 &bucket);
627 } while (bucket_num);
628
629 if (!found) {
630 bucket_num = NewExtraBucket();
631 if (bucket_num) {
632 cell_num = bucket_num * kCellsPerBucket;
633 bucket->next = cell_num;
634 bucket = &extra_table_[bucket_num - (mask_ + 1)];
635 bucket->hash = hash & mask_;
636 found = true;
637 } else {
638 // address 0 is a reserved value, and the caller interprets it as invalid.
639 address.set_value(0);
640 }
641 }
642
643 EntryCell entry_cell(cell_num, hash, address, small_table_);
644 if (address.file_type() == BLOCK_EVICTED)
645 entry_cell.SetGroup(ENTRY_EVICTED);
646 else
647 entry_cell.SetGroup(ENTRY_NO_USE);
648 Save(&entry_cell);
649
650 if (found) {
651 bitmap_->Set(cell_num, true);
652 backup_bitmap_->Set(cell_num, true);
653 header()->used_cells++;
654 modified_ = true;
655 }
656
657 return entry_cell;
658 }
659
660 EntryCell IndexTable::FindEntryCell(uint32 hash, Addr address) {
661 return FindEntryCellImpl(hash, address, false);
662 }
663
664 int IndexTable::CalculateTimestamp(Time time) {
665 TimeDelta delta = time - Time::FromInternalValue(header_->base_time);
666 return std::max(delta.InMinutes(), 0);
667 }
668
669 base::Time IndexTable::TimeFromTimestamp(int timestamp) {
670 return Time::FromInternalValue(header_->base_time) +
671 TimeDelta::FromMinutes(timestamp);
672 }
673
674 void IndexTable::SetSate(uint32 hash, Addr address, EntryState state) {
675 EntryCell cell = FindEntryCellImpl(hash, address, state == ENTRY_FREE);
676 if (!cell.IsValid()) {
677 NOTREACHED();
678 return;
679 }
680
681 EntryState old_state = cell.GetState();
682 switch (state) {
683 case ENTRY_FREE:
684 DCHECK_EQ(old_state, ENTRY_DELETED);
685 break;
686 case ENTRY_NEW:
687 DCHECK_EQ(old_state, ENTRY_FREE);
688 break;
689 case ENTRY_OPEN:
690 DCHECK_EQ(old_state, ENTRY_USED);
691 break;
692 case ENTRY_MODIFIED:
693 DCHECK_EQ(old_state, ENTRY_OPEN);
694 break;
695 case ENTRY_DELETED:
696 DCHECK(old_state == ENTRY_NEW || old_state == ENTRY_OPEN ||
697 old_state == ENTRY_MODIFIED);
698 break;
699 case ENTRY_USED:
700 DCHECK(old_state == ENTRY_NEW || old_state == ENTRY_OPEN ||
701 old_state == ENTRY_MODIFIED);
702 break;
703 case ENTRY_FIXING:
704 break;
705 };
706
707 modified_ = true;
708 if (state == ENTRY_DELETED) {
709 bitmap_->Set(cell.cell_num(), false);
710 backup_bitmap_->Set(cell.cell_num(), false);
711 } else if (state == ENTRY_FREE) {
712 cell.Clear();
713 Write(cell);
714 header()->used_cells--;
715 return;
716 }
717 cell.SetState(state);
718
719 Save(&cell);
720 }
721
722 void IndexTable::UpdateTime(uint32 hash, Addr address, base::Time current) {
723 EntryCell cell = FindEntryCell(hash, address);
724 if (!cell.IsValid())
725 return;
726
727 int minutes = CalculateTimestamp(current);
728
729 // Keep about 3 months of headroom.
730 const int kMaxTimestamp = (1 << 20) - 60 * 24 * 90;
731 if (minutes > kMaxTimestamp) {
732 // TODO(rvargas):
733 // Update header->old_time and trigger a timer
734 // Rebaseline timestamps and don't update sums
735 // Start a timer (about 2 backups)
736 // fix all ckecksums and trigger another timer
737 // update header->old_time because rebaseline is done.
738 minutes = std::min(minutes, (1 << 20) - 1);
739 }
740
741 cell.SetTimestamp(minutes);
742 Save(&cell);
743 }
744
745 void IndexTable::Save(EntryCell* cell) {
746 cell->FixSum();
747 Write(*cell);
748 }
749
750 void IndexTable::GetOldest(IndexIterator* no_use,
751 IndexIterator* low_use,
752 IndexIterator* high_use) {
753 no_use->forward = true;
754 low_use->forward = true;
755 high_use->forward = true;
756 InitIterator(no_use);
757 InitIterator(low_use);
758 InitIterator(high_use);
759
760 WalkTables(-1, no_use, low_use, high_use);
761 }
762
763 bool IndexTable::GetNextCells(IndexIterator* iterator) {
764 int current_time = iterator->timestamp;
765 InitIterator(iterator);
766
767 WalkTables(current_time, iterator, iterator, iterator);
768 return !iterator->cells.empty();
769 }
770
771 void IndexTable::OnBackupTimer() {
772 if (!modified_)
773 return;
774
775 int num_words = (header_->table_len + 31) / 32;
776 int num_bytes = num_words * 4 + static_cast<int>(sizeof(*header_));
777 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(num_bytes));
778 memcpy(buffer->data(), header_, sizeof(*header_));
779 memcpy(buffer->data() + sizeof(*header_), backup_bitmap_storage_.get(),
780 num_words * 4);
781 backend_->SaveIndex(buffer, num_bytes);
782 modified_ = false;
783 }
784
785 // -----------------------------------------------------------------------
786
787 EntryCell IndexTable::FindEntryCellImpl(uint32 hash, Addr address,
788 bool allow_deleted) {
789 int bucket_num = static_cast<int>(hash & mask_);
790 IndexBucket* bucket = &main_table_[bucket_num];
791 do {
792 for (int i = 0; i < kCellsPerBucket; i++) {
793 IndexCell* current_cell = &bucket->cells[i];
794 if (!GetLocation(*current_cell))
795 continue;
796 DCHECK(SanityCheck(*current_cell));
797 if (IsHashMatch(*current_cell, hash)) {
798 // We have a match.
799 int cell_num = bucket_num * kCellsPerBucket + i;
800 EntryCell entry_cell(cell_num, hash, *current_cell, small_table_);
801 if (entry_cell.GetAddress() != address)
802 continue;
803
804 if (!allow_deleted && entry_cell.GetState() == ENTRY_DELETED)
805 continue;
806
807 return entry_cell;
808 }
809 }
810 bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
811 &bucket);
812 } while (bucket_num);
813 return EntryCell();
814 }
815
816 void IndexTable::CheckState(const EntryCell& cell) {
817 int current_state = cell.GetState();
818 if (current_state != ENTRY_FIXING) {
819 bool present = ((current_state & 3) != 0); // Look at the last two bits.
820 if (present != bitmap_->Get(cell.cell_num()) ||
821 present != backup_bitmap_->Get(cell.cell_num())) {
822 // There's a mismatch.
823 if (current_state == ENTRY_DELETED) {
824 // We were in the process of deleting this entry. Finish now.
825 backend_->DeleteCell(cell);
826 } else {
827 current_state = ENTRY_FIXING;
828 EntryCell bad_cell(cell);
829 bad_cell.SetState(ENTRY_FIXING);
830 Save(&bad_cell);
831 }
832 }
833 }
834
835 if (current_state == ENTRY_FIXING)
836 backend_->FixCell(cell);
837 }
838
839 void IndexTable::Write(const EntryCell& cell) {
840 IndexBucket* bucket = NULL;
841 int bucket_num = cell.cell_num() / kCellsPerBucket;
842 if (bucket_num < static_cast<int32>(mask_ + 1)) {
843 bucket = &main_table_[bucket_num];
844 } else {
845 DCHECK_LE(bucket_num, header()->max_bucket);
846 bucket = &extra_table_[bucket_num - (mask_ + 1)];
847 }
848
849 int cell_number = cell.cell_num() % kCellsPerBucket;
850 if (GetLocation(bucket->cells[cell_number]) && cell.GetLocation()) {
851 DCHECK_EQ(cell.GetLocation(),
852 GetLocation(bucket->cells[cell_number]));
853 }
854 cell.Serialize(&bucket->cells[cell_number]);
855 }
856
857 int IndexTable::NewExtraBucket() {
858 int safe_window = (header()->table_len < kNumExtraBlocks * 2) ?
859 kNumExtraBlocks / 4 : kNumExtraBlocks;
860 if (header()->table_len - header()->max_bucket * kCellsPerBucket <
861 safe_window) {
862 backend_->GrowIndex();
863 }
864
865 if (header()->max_bucket * kCellsPerBucket ==
866 header()->table_len - kCellsPerBucket) {
867 return 0;
868 }
869
870 header()->max_bucket++;
871 return header()->max_bucket;
872 }
873
874 void IndexTable::WalkTables(int limit_time,
875 IndexIterator* no_use,
876 IndexIterator* low_use,
877 IndexIterator* high_use) {
878 header_->num_no_use_entries = 0;
879 header_->num_low_use_entries = 0;
880 header_->num_high_use_entries = 0;
881 header_->num_evicted_entries = 0;
882
883 for (int i = 0; i < static_cast<int32>(mask_ + 1); i++) {
884 int bucket_num = i;
885 IndexBucket* bucket = &main_table_[i];
886 do {
887 UpdateFromBucket(bucket, i, limit_time, no_use, low_use, high_use);
888
889 bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
890 &bucket);
891 } while (bucket_num);
892 }
893 header_->num_entries = header_->num_no_use_entries +
894 header_->num_low_use_entries +
895 header_->num_high_use_entries +
896 header_->num_evicted_entries;
897 modified_ = true;
898 }
899
900 void IndexTable::UpdateFromBucket(IndexBucket* bucket, int bucket_hash,
901 int limit_time,
902 IndexIterator* no_use,
903 IndexIterator* low_use,
904 IndexIterator* high_use) {
905 for (int i = 0; i < kCellsPerBucket; i++) {
906 IndexCell& current_cell = bucket->cells[i];
907 if (!GetLocation(current_cell))
908 continue;
909 DCHECK(SanityCheck(current_cell));
910 if (!IsNormalState(current_cell))
911 continue;
912
913 EntryCell entry_cell(0, GetFullHash(current_cell, bucket_hash),
914 current_cell, small_table_);
915 switch (GetCellGroup(current_cell)) {
916 case ENTRY_NO_USE:
917 UpdateIterator(entry_cell, limit_time, no_use);
918 header_->num_no_use_entries++;
919 break;
920 case ENTRY_LOW_USE:
921 UpdateIterator(entry_cell, limit_time, low_use);
922 header_->num_low_use_entries++;
923 break;
924 case ENTRY_HIGH_USE:
925 UpdateIterator(entry_cell, limit_time, high_use);
926 header_->num_high_use_entries++;
927 break;
928 case ENTRY_EVICTED:
929 header_->num_evicted_entries++;
930 break;
931 default:
932 NOTREACHED();
933 }
934 }
935 }
936
937 // This code is only called from Init() so the internal state of this object is
938 // in flux (this method is performing the last steps of re-initialization). As
939 // such, random methods are not supposed to work at this point, so whatever this
940 // method calls should be relatively well controlled and it may require some
941 // degree of "stable state faking".
942 void IndexTable::MoveCells(IndexBucket* old_extra_table) {
943 int max_hash = (mask_ + 1) / 2;
944 int max_bucket = header()->max_bucket;
945 header()->max_bucket = mask_;
946 int used_cells = header()->used_cells;
947
948 // Consider a large cache: a cell stores the upper 18 bits of the hash
949 // (h >> 14). If the table is say 8 times the original size (growing from 4x),
950 // the bit that we are interested in would be the 3rd bit of the stored value,
951 // in other words 'multiplier' >> 1.
952 uint32 new_bit = (1 << extra_bits_) >> 1;
953
954 scoped_ptr<IndexBucket[]> old_main_table;
955 IndexBucket* source_table = main_table_;
956 bool upgrade_format = !extra_bits_;
957 if (upgrade_format) {
958 // This method should deal with migrating a small table to a big one. Given
959 // that the first thing to do is read the old table, set small_table_ for
960 // the size of the old table. Now, when moving a cell, the result cannot be
961 // placed in the old table or we will end up reading it again and attempting
962 // to move it, so we have to copy the whole table at once.
963 DCHECK(!small_table_);
964 small_table_ = true;
965 old_main_table.reset(new IndexBucket[max_hash]);
966 memcpy(old_main_table.get(), main_table_, max_hash * sizeof(IndexBucket));
967 memset(main_table_, 0, max_hash * sizeof(IndexBucket));
968 source_table = old_main_table.get();
969 }
970
971 for (int i = 0; i < max_hash; i++) {
972 int bucket_num = i;
973 IndexBucket* bucket = &source_table[i];
974 do {
975 for (int j = 0; j < kCellsPerBucket; j++) {
976 IndexCell& current_cell = bucket->cells[j];
977 if (!GetLocation(current_cell))
978 continue;
979 DCHECK(SanityCheck(current_cell));
980 if (bucket_num == i) {
981 if (upgrade_format || (GetHashValue(current_cell) & new_bit)) {
982 // Move this cell to the upper half of the table.
983 MoveSingleCell(&current_cell, bucket_num * kCellsPerBucket + j, i,
984 true);
985 }
986 } else {
987 // All cells on extra buckets have to move.
988 MoveSingleCell(&current_cell, bucket_num * kCellsPerBucket + j, i,
989 true);
990 }
991 }
992
993 // There is no need to clear the old bucket->next value because if falls
994 // within the main table so it will be fixed when attempting to follow
995 // the link.
996 bucket_num = GetNextBucket(max_hash, max_bucket, old_extra_table,
997 &bucket);
998 } while (bucket_num);
999 }
1000
1001 DCHECK_EQ(header()->used_cells, used_cells);
1002
1003 if (upgrade_format) {
1004 small_table_ = false;
1005 header()->flags &= ~SMALL_CACHE;
1006 }
1007 }
1008
1009 void IndexTable::MoveSingleCell(IndexCell* current_cell, int cell_num,
1010 int main_table_index, bool growing) {
1011 uint32 hash = GetFullHash(*current_cell, main_table_index);
1012 EntryCell old_cell(cell_num, hash, *current_cell, small_table_);
1013
1014 // This method may be called when moving entries from a small table to a
1015 // normal table. In that case, the caller (MoveCells) has to read the old
1016 // table, so it needs small_table_ set to true, but this method needs to
1017 // write to the new table so small_table_ has to be set to false, and the
1018 // value restored to true before returning.
1019 bool upgrade_format = !extra_bits_ && growing;
1020 if (upgrade_format)
1021 small_table_ = false;
1022 EntryCell new_cell = CreateEntryCell(hash, old_cell.GetAddress());
1023
1024 if (!new_cell.IsValid()) {
1025 // We'll deal with this entry later.
1026 if (upgrade_format)
1027 small_table_ = true;
1028 return;
1029 }
1030
1031 new_cell.SetState(old_cell.GetState());
1032 new_cell.SetGroup(old_cell.GetGroup());
1033 new_cell.SetReuse(old_cell.GetReuse());
1034 new_cell.SetTimestamp(old_cell.GetTimestamp());
1035 Save(&new_cell);
1036 modified_ = true;
1037 if (upgrade_format)
1038 small_table_ = true;
1039
1040 if (old_cell.GetState() == ENTRY_DELETED) {
1041 bitmap_->Set(new_cell.cell_num(), false);
1042 backup_bitmap_->Set(new_cell.cell_num(), false);
1043 }
1044
1045 if (!growing || cell_num / kCellsPerBucket == main_table_index) {
1046 // Only delete entries that live on the main table.
1047 if (!upgrade_format) {
1048 old_cell.Clear();
1049 Write(old_cell);
1050 }
1051
1052 if (cell_num != new_cell.cell_num()) {
1053 bitmap_->Set(old_cell.cell_num(), false);
1054 backup_bitmap_->Set(old_cell.cell_num(), false);
1055 }
1056 }
1057 header()->used_cells--;
1058 }
1059
1060 void IndexTable::HandleMisplacedCell(IndexCell* current_cell, int cell_num,
1061 int main_table_index) {
1062 NOTREACHED(); // No unit tests yet.
1063
1064 // The cell may be misplaced, or a duplicate cell exists with this data.
1065 uint32 hash = GetFullHash(*current_cell, main_table_index);
1066 MoveSingleCell(current_cell, cell_num, main_table_index, false);
1067
1068 // Now look for a duplicate cell.
1069 CheckBucketList(hash & mask_);
1070 }
1071
1072 void IndexTable::CheckBucketList(int bucket_num) {
1073 typedef std::pair<int, EntryGroup> AddressAndGroup;
1074 std::set<AddressAndGroup> entries;
1075 IndexBucket* bucket = &main_table_[bucket_num];
1076 int bucket_hash = bucket_num;
1077 do {
1078 for (int i = 0; i < kCellsPerBucket; i++) {
1079 IndexCell* current_cell = &bucket->cells[i];
1080 if (!GetLocation(*current_cell))
1081 continue;
1082 if (!SanityCheck(*current_cell)) {
1083 NOTREACHED();
1084 current_cell->Clear();
1085 continue;
1086 }
1087 int cell_num = bucket_num * kCellsPerBucket + i;
1088 EntryCell cell(cell_num, GetFullHash(*current_cell, bucket_hash),
1089 *current_cell, small_table_);
1090 if (!entries.insert(std::make_pair(cell.GetAddress().value(),
1091 cell.GetGroup())).second) {
1092 current_cell->Clear();
1093 continue;
1094 }
1095 CheckState(cell);
1096 }
1097
1098 bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
1099 &bucket);
1100 } while (bucket_num);
1101 }
1102
1103 uint32 IndexTable::GetLocation(const IndexCell& cell) {
1104 if (small_table_)
1105 return GetCellSmallTableLocation(cell);
1106
1107 return GetCellLocation(cell);
1108 }
1109
1110 uint32 IndexTable::GetHashValue(const IndexCell& cell) {
1111 if (small_table_)
1112 return GetCellSmallTableId(cell);
1113
1114 return GetCellId(cell);
1115 }
1116
1117 uint32 IndexTable::GetFullHash(const IndexCell& cell, uint32 lower_part) {
1118 // It is OK for the high order bits of lower_part to overlap with the stored
1119 // part of the hash.
1120 if (small_table_)
1121 return (GetCellSmallTableId(cell) << kSmallTableHashShift) | lower_part;
1122
1123 return (GetCellId(cell) << kHashShift) | lower_part;
1124 }
1125
1126 // All the bits stored in the cell should match the provided hash.
1127 bool IndexTable::IsHashMatch(const IndexCell& cell, uint32 hash) {
1128 hash = small_table_ ? hash >> kSmallTableHashShift : hash >> kHashShift;
1129 return GetHashValue(cell) == hash;
1130 }
1131
1132 bool IndexTable::MisplacedHash(const IndexCell& cell, uint32 hash) {
1133 if (!extra_bits_)
1134 return false;
1135
1136 uint32 mask = (1 << extra_bits_) - 1;
1137 hash = small_table_ ? hash >> kSmallTableHashShift : hash >> kHashShift;
1138 return (GetHashValue(cell) & mask) != (hash & mask);
1139 }
1140
1141 } // namespace disk_cache
OLDNEW
« no previous file with comments | « net/disk_cache/v3/index_table.h ('k') | net/disk_cache/v3/index_table_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698