1 // Copyright (c) 2012 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.
5 #include "net/disk_cache/blockfile/block_files.h"
7 #include "base/atomicops.h"
8 #include "base/files/file_path.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/threading/thread_checker.h"
13 #include "base/time/time.h"
14 #include "net/disk_cache/blockfile/file_lock.h"
15 #include "net/disk_cache/blockfile/stress_support.h"
16 #include "net/disk_cache/blockfile/trace.h"
17 #include "net/disk_cache/cache_util.h"
19 using base::TimeTicks
;
23 const char kBlockName
[] = "data_";
25 // This array is used to perform a fast lookup of the nibble bit pattern to the
26 // type of entry that can be stored there (number of consecutive blocks).
27 const char s_types
[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
29 // Returns the type of block (number of consecutive blocks that can be stored)
30 // for a given nibble of the bitmap.
31 inline int GetMapBlockType(uint32 value
) {
33 return s_types
[value
];
38 namespace disk_cache
{
40 BlockHeader::BlockHeader() : header_(NULL
) {
43 BlockHeader::BlockHeader(BlockFileHeader
* header
) : header_(header
) {
46 BlockHeader::BlockHeader(MappedFile
* file
)
47 : header_(reinterpret_cast<BlockFileHeader
*>(file
->buffer())) {
50 BlockHeader::BlockHeader(const BlockHeader
& other
) : header_(other
.header_
) {
53 BlockHeader::~BlockHeader() {
56 bool BlockHeader::CreateMapBlock(int size
, int* index
) {
57 DCHECK(size
> 0 && size
<= kMaxNumBlocks
);
59 for (int i
= size
; i
<= kMaxNumBlocks
; i
++) {
60 if (header_
->empty
[i
- 1]) {
71 TimeTicks start
= TimeTicks::Now();
72 // We are going to process the map on 32-block chunks (32 bits), and on every
73 // chunk, iterate through the 8 nibbles where the new block can be located.
74 int current
= header_
->hints
[target
- 1];
75 for (int i
= 0; i
< header_
->max_entries
/ 32; i
++, current
++) {
76 if (current
== header_
->max_entries
/ 32)
78 uint32 map_block
= header_
->allocation_map
[current
];
80 for (int j
= 0; j
< 8; j
++, map_block
>>= 4) {
81 if (GetMapBlockType(map_block
) != target
)
84 disk_cache::FileLock
lock(header_
);
85 int index_offset
= j
* 4 + 4 - target
;
86 *index
= current
* 32 + index_offset
;
87 STRESS_DCHECK(*index
/ 4 == (*index
+ size
- 1) / 4);
88 uint32 to_add
= ((1 << size
) - 1) << index_offset
;
89 header_
->num_entries
++;
91 // Note that there is no race in the normal sense here, but if we enforce
92 // the order of memory accesses between num_entries and allocation_map, we
93 // can assert that even if we crash here, num_entries will never be less
94 // than the actual number of used blocks.
95 base::subtle::MemoryBarrier();
96 header_
->allocation_map
[current
] |= to_add
;
98 header_
->hints
[target
- 1] = current
;
99 header_
->empty
[target
- 1]--;
100 STRESS_DCHECK(header_
->empty
[target
- 1] >= 0);
101 if (target
!= size
) {
102 header_
->empty
[target
- size
- 1]++;
104 LOCAL_HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start
);
109 // It is possible to have an undetected corruption (for example when the OS
110 // crashes), fix it here.
111 LOG(ERROR
) << "Failing CreateMapBlock";
112 FixAllocationCounters();
116 void BlockHeader::DeleteMapBlock(int index
, int size
) {
117 if (size
< 0 || size
> kMaxNumBlocks
) {
121 TimeTicks start
= TimeTicks::Now();
122 int byte_index
= index
/ 8;
123 uint8
* byte_map
= reinterpret_cast<uint8
*>(header_
->allocation_map
);
124 uint8 map_block
= byte_map
[byte_index
];
129 // See what type of block will be available after we delete this one.
130 int bits_at_end
= 4 - size
- index
% 4;
131 uint8 end_mask
= (0xf << (4 - bits_at_end
)) & 0xf;
132 bool update_counters
= (map_block
& end_mask
) == 0;
133 uint8 new_value
= map_block
& ~(((1 << size
) - 1) << (index
% 4));
134 int new_type
= GetMapBlockType(new_value
);
136 disk_cache::FileLock
lock(header_
);
137 STRESS_DCHECK((((1 << size
) - 1) << (index
% 8)) < 0x100);
138 uint8 to_clear
= ((1 << size
) - 1) << (index
% 8);
139 STRESS_DCHECK((byte_map
[byte_index
] & to_clear
) == to_clear
);
140 byte_map
[byte_index
] &= ~to_clear
;
142 if (update_counters
) {
144 header_
->empty
[bits_at_end
- 1]--;
145 header_
->empty
[new_type
- 1]++;
146 STRESS_DCHECK(header_
->empty
[bits_at_end
- 1] >= 0);
148 base::subtle::MemoryBarrier();
149 header_
->num_entries
--;
150 STRESS_DCHECK(header_
->num_entries
>= 0);
151 LOCAL_HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start
);
154 // Note that this is a simplified version of DeleteMapBlock().
155 bool BlockHeader::UsedMapBlock(int index
, int size
) {
156 if (size
< 0 || size
> kMaxNumBlocks
)
159 int byte_index
= index
/ 8;
160 uint8
* byte_map
= reinterpret_cast<uint8
*>(header_
->allocation_map
);
161 uint8 map_block
= byte_map
[byte_index
];
166 STRESS_DCHECK((((1 << size
) - 1) << (index
% 8)) < 0x100);
167 uint8 to_clear
= ((1 << size
) - 1) << (index
% 8);
168 return ((byte_map
[byte_index
] & to_clear
) == to_clear
);
171 void BlockHeader::FixAllocationCounters() {
172 for (int i
= 0; i
< kMaxNumBlocks
; i
++) {
173 header_
->hints
[i
] = 0;
174 header_
->empty
[i
] = 0;
177 for (int i
= 0; i
< header_
->max_entries
/ 32; i
++) {
178 uint32 map_block
= header_
->allocation_map
[i
];
180 for (int j
= 0; j
< 8; j
++, map_block
>>= 4) {
181 int type
= GetMapBlockType(map_block
);
183 header_
->empty
[type
-1]++;
188 bool BlockHeader::NeedToGrowBlockFile(int block_count
) const {
189 bool have_space
= false;
190 int empty_blocks
= 0;
191 for (int i
= 0; i
< kMaxNumBlocks
; i
++) {
192 empty_blocks
+= header_
->empty
[i
] * (i
+ 1);
193 if (i
>= block_count
- 1 && header_
->empty
[i
])
197 if (header_
->next_file
&& (empty_blocks
< kMaxBlocks
/ 10)) {
198 // This file is almost full but we already created another one, don't use
199 // this file yet so that it is easier to find empty blocks when we start
200 // using this file again.
206 bool BlockHeader::CanAllocate(int block_count
) const {
207 DCHECK_GT(block_count
, 0);
208 for (int i
= block_count
- 1; i
< kMaxNumBlocks
; i
++) {
209 if (header_
->empty
[i
])
216 int BlockHeader::EmptyBlocks() const {
217 int empty_blocks
= 0;
218 for (int i
= 0; i
< kMaxNumBlocks
; i
++) {
219 empty_blocks
+= header_
->empty
[i
] * (i
+ 1);
220 if (header_
->empty
[i
] < 0)
226 int BlockHeader::MinimumAllocations() const {
227 return header_
->empty
[kMaxNumBlocks
- 1];
230 int BlockHeader::Capacity() const {
231 return header_
->max_entries
;
234 bool BlockHeader::ValidateCounters() const {
235 if (header_
->max_entries
< 0 || header_
->max_entries
> kMaxBlocks
||
236 header_
->num_entries
< 0)
239 int empty_blocks
= EmptyBlocks();
240 if (empty_blocks
+ header_
->num_entries
> header_
->max_entries
)
246 int BlockHeader::FileId() const {
247 return header_
->this_file
;
250 int BlockHeader::NextFileId() const {
251 return header_
->next_file
;
254 int BlockHeader::Size() const {
255 return static_cast<int>(sizeof(*header_
));
258 BlockFileHeader
* BlockHeader::Header() {
262 // ------------------------------------------------------------------------
264 BlockFiles::BlockFiles(const base::FilePath
& path
)
265 : init_(false), zero_buffer_(NULL
), path_(path
) {
268 BlockFiles::~BlockFiles() {
270 delete[] zero_buffer_
;
274 bool BlockFiles::Init(bool create_files
) {
279 thread_checker_
.reset(new base::ThreadChecker
);
281 block_files_
.resize(kFirstAdditionalBlockFile
);
282 for (int16 i
= 0; i
< kFirstAdditionalBlockFile
; i
++) {
284 if (!CreateBlockFile(i
, static_cast<FileType
>(i
+ 1), true))
287 if (!OpenBlockFile(i
))
290 // Walk this chain of files removing empty ones.
291 if (!RemoveEmptyFile(static_cast<FileType
>(i
+ 1)))
299 MappedFile
* BlockFiles::GetFile(Addr address
) {
300 DCHECK(thread_checker_
->CalledOnValidThread());
301 DCHECK_GE(block_files_
.size(),
302 static_cast<size_t>(kFirstAdditionalBlockFile
));
303 DCHECK(address
.is_block_file() || !address
.is_initialized());
304 if (!address
.is_initialized())
307 int file_index
= address
.FileNumber();
308 if (static_cast<unsigned int>(file_index
) >= block_files_
.size() ||
309 !block_files_
[file_index
]) {
310 // We need to open the file
311 if (!OpenBlockFile(file_index
))
314 DCHECK_GE(block_files_
.size(), static_cast<unsigned int>(file_index
));
315 return block_files_
[file_index
];
318 bool BlockFiles::CreateBlock(FileType block_type
, int block_count
,
319 Addr
* block_address
) {
320 DCHECK(thread_checker_
->CalledOnValidThread());
321 DCHECK_NE(block_type
, EXTERNAL
);
322 DCHECK_NE(block_type
, BLOCK_FILES
);
323 DCHECK_NE(block_type
, BLOCK_ENTRIES
);
324 DCHECK_NE(block_type
, BLOCK_EVICTED
);
325 if (block_count
< 1 || block_count
> kMaxNumBlocks
)
331 MappedFile
* file
= FileForNewBlock(block_type
, block_count
);
335 ScopedFlush
flush(file
);
336 BlockHeader
file_header(file
);
339 if (!file_header
.CreateMapBlock(block_count
, &index
))
342 Addr
address(block_type
, block_count
, file_header
.FileId(), index
);
343 block_address
->set_value(address
.value());
344 Trace("CreateBlock 0x%x", address
.value());
348 void BlockFiles::DeleteBlock(Addr address
, bool deep
) {
349 DCHECK(thread_checker_
->CalledOnValidThread());
350 if (!address
.is_initialized() || address
.is_separate_file())
354 zero_buffer_
= new char[Addr::BlockSizeForFileType(BLOCK_4K
) * 4];
355 memset(zero_buffer_
, 0, Addr::BlockSizeForFileType(BLOCK_4K
) * 4);
357 MappedFile
* file
= GetFile(address
);
361 Trace("DeleteBlock 0x%x", address
.value());
363 size_t size
= address
.BlockSize() * address
.num_blocks();
364 size_t offset
= address
.start_block() * address
.BlockSize() +
367 file
->Write(zero_buffer_
, size
, offset
);
369 BlockHeader
file_header(file
);
370 file_header
.DeleteMapBlock(address
.start_block(), address
.num_blocks());
373 if (!file_header
.Header()->num_entries
) {
374 // This file is now empty. Let's try to delete it.
375 FileType type
= Addr::RequiredFileType(file_header
.Header()->entry_size
);
376 if (Addr::BlockSizeForFileType(RANKINGS
) ==
377 file_header
.Header()->entry_size
) {
380 RemoveEmptyFile(type
); // Ignore failures.
384 void BlockFiles::CloseFiles() {
386 DCHECK(thread_checker_
->CalledOnValidThread());
389 for (unsigned int i
= 0; i
< block_files_
.size(); i
++) {
390 if (block_files_
[i
]) {
391 block_files_
[i
]->Release();
392 block_files_
[i
] = NULL
;
395 block_files_
.clear();
398 void BlockFiles::ReportStats() {
399 DCHECK(thread_checker_
->CalledOnValidThread());
400 int used_blocks
[kFirstAdditionalBlockFile
];
401 int load
[kFirstAdditionalBlockFile
];
402 for (int i
= 0; i
< kFirstAdditionalBlockFile
; i
++) {
403 GetFileStats(i
, &used_blocks
[i
], &load
[i
]);
405 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks
[0]);
406 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks
[1]);
407 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks
[2]);
408 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks
[3]);
410 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load
[0], 101);
411 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load
[1], 101);
412 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load
[2], 101);
413 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load
[3], 101);
416 bool BlockFiles::IsValid(Addr address
) {
420 if (!address
.is_initialized() || address
.is_separate_file())
423 MappedFile
* file
= GetFile(address
);
427 BlockHeader
header(file
);
428 bool rv
= header
.UsedMapBlock(address
.start_block(), address
.num_blocks());
431 static bool read_contents
= false;
433 scoped_ptr
<char[]> buffer
;
434 buffer
.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K
) * 4]);
435 size_t size
= address
.BlockSize() * address
.num_blocks();
436 size_t offset
= address
.start_block() * address
.BlockSize() +
438 bool ok
= file
->Read(buffer
.get(), size
, offset
);
446 bool BlockFiles::CreateBlockFile(int index
, FileType file_type
, bool force
) {
447 base::FilePath name
= Name(index
);
448 int flags
= force
? base::File::FLAG_CREATE_ALWAYS
: base::File::FLAG_CREATE
;
449 flags
|= base::File::FLAG_WRITE
| base::File::FLAG_EXCLUSIVE_WRITE
;
451 scoped_refptr
<File
> file(new File(base::File(name
, flags
)));
452 if (!file
->IsValid())
455 BlockFileHeader header
;
456 memset(&header
, 0, sizeof(header
));
457 header
.magic
= kBlockMagic
;
458 header
.version
= kBlockVersion2
;
459 header
.entry_size
= Addr::BlockSizeForFileType(file_type
);
460 header
.this_file
= static_cast<int16
>(index
);
461 DCHECK(index
<= kint16max
&& index
>= 0);
463 return file
->Write(&header
, sizeof(header
), 0);
466 bool BlockFiles::OpenBlockFile(int index
) {
467 if (block_files_
.size() - 1 < static_cast<unsigned int>(index
)) {
469 int to_add
= index
- static_cast<int>(block_files_
.size()) + 1;
470 block_files_
.resize(block_files_
.size() + to_add
);
473 base::FilePath name
= Name(index
);
474 scoped_refptr
<MappedFile
> file(new MappedFile());
476 if (!file
->Init(name
, kBlockHeaderSize
)) {
477 LOG(ERROR
) << "Failed to open " << name
.value();
481 size_t file_len
= file
->GetLength();
482 if (file_len
< static_cast<size_t>(kBlockHeaderSize
)) {
483 LOG(ERROR
) << "File too small " << name
.value();
487 BlockHeader
file_header(file
.get());
488 BlockFileHeader
* header
= file_header
.Header();
489 if (kBlockMagic
!= header
->magic
|| kBlockVersion2
!= header
->version
) {
490 LOG(ERROR
) << "Invalid file version or magic " << name
.value();
494 if (header
->updating
|| !file_header
.ValidateCounters()) {
495 // Last instance was not properly shutdown, or counters are out of sync.
496 if (!FixBlockFileHeader(file
.get())) {
497 LOG(ERROR
) << "Unable to fix block file " << name
.value();
502 if (static_cast<int>(file_len
) <
503 header
->max_entries
* header
->entry_size
+ kBlockHeaderSize
) {
504 LOG(ERROR
) << "File too small " << name
.value();
509 // Load the links file into memory.
510 if (!file
->Preload())
514 ScopedFlush
flush(file
.get());
515 DCHECK(!block_files_
[index
]);
516 file
.swap(&block_files_
[index
]);
520 bool BlockFiles::GrowBlockFile(MappedFile
* file
, BlockFileHeader
* header
) {
521 if (kMaxBlocks
== header
->max_entries
)
524 ScopedFlush
flush(file
);
525 DCHECK(!header
->empty
[3]);
526 int new_size
= header
->max_entries
+ 1024;
527 if (new_size
> kMaxBlocks
)
528 new_size
= kMaxBlocks
;
530 int new_size_bytes
= new_size
* header
->entry_size
+ sizeof(*header
);
532 if (!file
->SetLength(new_size_bytes
)) {
533 // Most likely we are trying to truncate the file, so the header is wrong.
534 if (header
->updating
< 10 && !FixBlockFileHeader(file
)) {
535 // If we can't fix the file increase the lock guard so we'll pick it on
536 // the next start and replace it.
537 header
->updating
= 100;
540 return (header
->max_entries
>= new_size
);
543 FileLock
lock(header
);
544 header
->empty
[3] = (new_size
- header
->max_entries
) / 4; // 4 blocks entries
545 header
->max_entries
= new_size
;
550 MappedFile
* BlockFiles::FileForNewBlock(FileType block_type
, int block_count
) {
551 static_assert(RANKINGS
== 1, "invalid file type");
552 MappedFile
* file
= block_files_
[block_type
- 1];
553 BlockHeader
file_header(file
);
555 TimeTicks start
= TimeTicks::Now();
556 while (file_header
.NeedToGrowBlockFile(block_count
)) {
557 if (kMaxBlocks
== file_header
.Header()->max_entries
) {
558 file
= NextFile(file
);
561 file_header
= BlockHeader(file
);
565 if (!GrowBlockFile(file
, file_header
.Header()))
569 LOCAL_HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock",
570 TimeTicks::Now() - start
);
574 MappedFile
* BlockFiles::NextFile(MappedFile
* file
) {
575 ScopedFlush
flush(file
);
576 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
577 int16 new_file
= header
->next_file
;
579 // RANKINGS is not reported as a type for small entries, but we may be
580 // extending the rankings block file.
581 FileType type
= Addr::RequiredFileType(header
->entry_size
);
582 if (header
->entry_size
== Addr::BlockSizeForFileType(RANKINGS
))
585 new_file
= CreateNextBlockFile(type
);
589 FileLock
lock(header
);
590 header
->next_file
= new_file
;
593 // Only the block_file argument is relevant for what we want.
594 Addr
address(BLOCK_256
, 1, new_file
, 0);
595 return GetFile(address
);
598 int16
BlockFiles::CreateNextBlockFile(FileType block_type
) {
599 for (int16 i
= kFirstAdditionalBlockFile
; i
<= kMaxBlockFile
; i
++) {
600 if (CreateBlockFile(i
, block_type
, false))
606 // We walk the list of files for this particular block type, deleting the ones
608 bool BlockFiles::RemoveEmptyFile(FileType block_type
) {
609 MappedFile
* file
= block_files_
[block_type
- 1];
610 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
612 while (header
->next_file
) {
613 // Only the block_file argument is relevant for what we want.
614 Addr
address(BLOCK_256
, 1, header
->next_file
, 0);
615 MappedFile
* next_file
= GetFile(address
);
619 BlockFileHeader
* next_header
=
620 reinterpret_cast<BlockFileHeader
*>(next_file
->buffer());
621 if (!next_header
->num_entries
) {
622 DCHECK_EQ(next_header
->entry_size
, header
->entry_size
);
623 // Delete next_file and remove it from the chain.
624 int file_index
= header
->next_file
;
625 header
->next_file
= next_header
->next_file
;
626 DCHECK(block_files_
.size() >= static_cast<unsigned int>(file_index
));
629 // We get a new handle to the file and release the old one so that the
630 // file gets unmmaped... so we can delete it.
631 base::FilePath name
= Name(file_index
);
632 scoped_refptr
<File
> this_file(new File(false));
633 this_file
->Init(name
);
634 block_files_
[file_index
]->Release();
635 block_files_
[file_index
] = NULL
;
637 int failure
= DeleteCacheFile(name
) ? 0 : 1;
638 UMA_HISTOGRAM_COUNTS("DiskCache.DeleteFailed2", failure
);
640 LOG(ERROR
) << "Failed to delete " << name
.value() << " from the cache.";
644 header
= next_header
;
650 // Note that we expect to be called outside of a FileLock... however, we cannot
651 // DCHECK on header->updating because we may be fixing a crash.
652 bool BlockFiles::FixBlockFileHeader(MappedFile
* file
) {
653 ScopedFlush
flush(file
);
654 BlockHeader
file_header(file
);
655 int file_size
= static_cast<int>(file
->GetLength());
656 if (file_size
< file_header
.Size())
657 return false; // file_size > 2GB is also an error.
659 const int kMinHeaderBlockSize
= 36;
660 const int kMaxHeaderBlockSize
= 4096;
661 BlockFileHeader
* header
= file_header
.Header();
662 if (header
->entry_size
< kMinHeaderBlockSize
||
663 header
->entry_size
> kMaxHeaderBlockSize
|| header
->num_entries
< 0)
666 // Make sure that we survive crashes.
667 header
->updating
= 1;
668 int expected
= header
->entry_size
* header
->max_entries
+ file_header
.Size();
669 if (file_size
!= expected
) {
670 int max_expected
= header
->entry_size
* kMaxBlocks
+ file_header
.Size();
671 if (file_size
< expected
|| header
->empty
[3] || file_size
> max_expected
) {
673 LOG(ERROR
) << "Unexpected file size";
676 // We were in the middle of growing the file.
677 int num_entries
= (file_size
- file_header
.Size()) / header
->entry_size
;
678 header
->max_entries
= num_entries
;
681 file_header
.FixAllocationCounters();
682 int empty_blocks
= file_header
.EmptyBlocks();
683 if (empty_blocks
+ header
->num_entries
> header
->max_entries
)
684 header
->num_entries
= header
->max_entries
- empty_blocks
;
686 if (!file_header
.ValidateCounters())
689 header
->updating
= 0;
693 // We are interested in the total number of blocks used by this file type, and
694 // the max number of blocks that we can store (reported as the percentage of
695 // used blocks). In order to find out the number of used blocks, we have to
696 // substract the empty blocks from the total blocks for each file in the chain.
697 void BlockFiles::GetFileStats(int index
, int* used_count
, int* load
) {
702 if (!block_files_
[index
] && !OpenBlockFile(index
))
705 BlockFileHeader
* header
=
706 reinterpret_cast<BlockFileHeader
*>(block_files_
[index
]->buffer());
708 max_blocks
+= header
->max_entries
;
709 int used
= header
->max_entries
;
710 for (int i
= 0; i
< kMaxNumBlocks
; i
++) {
711 used
-= header
->empty
[i
] * (i
+ 1);
716 if (!header
->next_file
)
718 index
= header
->next_file
;
721 *load
= *used_count
* 100 / max_blocks
;
724 base::FilePath
BlockFiles::Name(int index
) {
725 // The file format allows for 256 files.
726 DCHECK(index
< 256 && index
>= 0);
727 std::string tmp
= base::StringPrintf("%s%d", kBlockName
, index
);
728 return path_
.AppendASCII(tmp
);
731 } // namespace disk_cache