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/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.h"
14 #include "net/disk_cache/cache_util.h"
15 #include "net/disk_cache/file_lock.h"
16 #include "net/disk_cache/trace.h"
18 using base::TimeTicks
;
22 const char* kBlockName
= "data_";
24 // This array is used to perform a fast lookup of the nibble bit pattern to the
25 // type of entry that can be stored there (number of consecutive blocks).
26 const char s_types
[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
28 // Returns the type of block (number of consecutive blocks that can be stored)
29 // for a given nibble of the bitmap.
30 inline int GetMapBlockType(uint8 value
) {
32 return s_types
[value
];
37 namespace disk_cache
{
39 BlockHeader::BlockHeader() : header_(NULL
) {
42 BlockHeader::BlockHeader(BlockFileHeader
* header
) : header_(header
) {
45 BlockHeader::BlockHeader(MappedFile
* file
)
46 : header_(reinterpret_cast<BlockFileHeader
*>(file
->buffer())) {
49 BlockHeader::BlockHeader(const BlockHeader
& other
) : header_(other
.header_
) {
52 BlockHeader::~BlockHeader() {
55 bool BlockHeader::CreateMapBlock(int target
, int size
, int* index
) {
56 if (target
<= 0 || target
> kMaxNumBlocks
||
57 size
<= 0 || size
> kMaxNumBlocks
) {
62 TimeTicks start
= TimeTicks::Now();
63 // We are going to process the map on 32-block chunks (32 bits), and on every
64 // chunk, iterate through the 8 nibbles where the new block can be located.
65 int current
= header_
->hints
[target
- 1];
66 for (int i
= 0; i
< header_
->max_entries
/ 32; i
++, current
++) {
67 if (current
== header_
->max_entries
/ 32)
69 uint32 map_block
= header_
->allocation_map
[current
];
71 for (int j
= 0; j
< 8; j
++, map_block
>>= 4) {
72 if (GetMapBlockType(map_block
) != target
)
75 disk_cache::FileLock
lock(header_
);
76 int index_offset
= j
* 4 + 4 - target
;
77 *index
= current
* 32 + index_offset
;
78 DCHECK_EQ(*index
/ 4, (*index
+ size
- 1) / 4);
79 uint32 to_add
= ((1 << size
) - 1) << index_offset
;
80 header_
->num_entries
++;
82 // Note that there is no race in the normal sense here, but if we enforce
83 // the order of memory accesses between num_entries and allocation_map, we
84 // can assert that even if we crash here, num_entries will never be less
85 // than the actual number of used blocks.
86 base::subtle::MemoryBarrier();
87 header_
->allocation_map
[current
] |= to_add
;
89 header_
->hints
[target
- 1] = current
;
90 header_
->empty
[target
- 1]--;
91 DCHECK_GE(header_
->empty
[target
- 1], 0);
93 header_
->empty
[target
- size
- 1]++;
95 HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start
);
100 // It is possible to have an undetected corruption (for example when the OS
101 // crashes), fix it here.
102 LOG(ERROR
) << "Failing CreateMapBlock";
103 FixAllocationCounters();
107 void BlockHeader::DeleteMapBlock(int index
, int size
) {
108 if (size
< 0 || size
> kMaxNumBlocks
) {
112 TimeTicks start
= TimeTicks::Now();
113 int byte_index
= index
/ 8;
114 uint8
* byte_map
= reinterpret_cast<uint8
*>(header_
->allocation_map
);
115 uint8 map_block
= byte_map
[byte_index
];
120 // See what type of block will be available after we delete this one.
121 int bits_at_end
= 4 - size
- index
% 4;
122 uint8 end_mask
= (0xf << (4 - bits_at_end
)) & 0xf;
123 bool update_counters
= (map_block
& end_mask
) == 0;
124 uint8 new_value
= map_block
& ~(((1 << size
) - 1) << (index
% 4));
125 int new_type
= GetMapBlockType(new_value
);
127 disk_cache::FileLock
lock(header_
);
128 DCHECK((((1 << size
) - 1) << (index
% 8)) < 0x100);
129 uint8 to_clear
= ((1 << size
) - 1) << (index
% 8);
130 DCHECK((byte_map
[byte_index
] & to_clear
) == to_clear
);
131 byte_map
[byte_index
] &= ~to_clear
;
133 if (update_counters
) {
135 header_
->empty
[bits_at_end
- 1]--;
136 header_
->empty
[new_type
- 1]++;
137 DCHECK_GE(header_
->empty
[bits_at_end
- 1], 0);
139 base::subtle::MemoryBarrier();
140 header_
->num_entries
--;
141 DCHECK_GE(header_
->num_entries
, 0);
142 HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start
);
145 // Note that this is a simplified version of DeleteMapBlock().
146 bool BlockHeader::UsedMapBlock(int index
, int size
) {
147 if (size
< 0 || size
> kMaxNumBlocks
) {
151 int byte_index
= index
/ 8;
152 uint8
* byte_map
= reinterpret_cast<uint8
*>(header_
->allocation_map
);
153 uint8 map_block
= byte_map
[byte_index
];
158 DCHECK((((1 << size
) - 1) << (index
% 8)) < 0x100);
159 uint8 to_clear
= ((1 << size
) - 1) << (index
% 8);
160 return ((byte_map
[byte_index
] & to_clear
) == to_clear
);
163 void BlockHeader::FixAllocationCounters() {
164 for (int i
= 0; i
< kMaxNumBlocks
; i
++) {
165 header_
->hints
[i
] = 0;
166 header_
->empty
[i
] = 0;
169 for (int i
= 0; i
< header_
->max_entries
/ 32; i
++) {
170 uint32 map_block
= header_
->allocation_map
[i
];
172 for (int j
= 0; j
< 8; j
++, map_block
>>= 4) {
173 int type
= GetMapBlockType(map_block
);
175 header_
->empty
[type
-1]++;
180 bool BlockHeader::NeedToGrowBlockFile(int block_count
) {
181 bool have_space
= false;
182 int empty_blocks
= 0;
183 for (int i
= 0; i
< kMaxNumBlocks
; i
++) {
184 empty_blocks
+= header_
->empty
[i
] * (i
+ 1);
185 if (i
>= block_count
- 1 && header_
->empty
[i
])
189 if (header_
->next_file
&& (empty_blocks
< kMaxBlocks
/ 10)) {
190 // This file is almost full but we already created another one, don't use
191 // this file yet so that it is easier to find empty blocks when we start
192 // using this file again.
198 int BlockHeader::EmptyBlocks() const {
199 int empty_blocks
= 0;
200 for (int i
= 0; i
< disk_cache::kMaxNumBlocks
; i
++) {
201 empty_blocks
+= header_
->empty
[i
] * (i
+ 1);
202 if (header_
->empty
[i
] < 0)
208 bool BlockHeader::ValidateCounters() const {
209 if (header_
->max_entries
< 0 || header_
->max_entries
> kMaxBlocks
||
210 header_
->num_entries
< 0)
213 int empty_blocks
= EmptyBlocks();
214 if (empty_blocks
+ header_
->num_entries
> header_
->max_entries
)
220 int BlockHeader::Size() const {
221 return static_cast<int>(sizeof(*header_
));
224 // ------------------------------------------------------------------------
226 BlockFiles::BlockFiles(const base::FilePath
& path
)
227 : init_(false), zero_buffer_(NULL
), path_(path
) {
230 BlockFiles::~BlockFiles() {
232 delete[] zero_buffer_
;
236 bool BlockFiles::Init(bool create_files
) {
241 thread_checker_
.reset(new base::ThreadChecker
);
243 block_files_
.resize(kFirstAdditionalBlockFile
);
244 for (int i
= 0; i
< kFirstAdditionalBlockFile
; i
++) {
246 if (!CreateBlockFile(i
, static_cast<FileType
>(i
+ 1), true))
249 if (!OpenBlockFile(i
))
252 // Walk this chain of files removing empty ones.
253 if (!RemoveEmptyFile(static_cast<FileType
>(i
+ 1)))
261 MappedFile
* BlockFiles::GetFile(Addr address
) {
262 DCHECK(thread_checker_
->CalledOnValidThread());
263 DCHECK(block_files_
.size() >= 4);
264 DCHECK(address
.is_block_file() || !address
.is_initialized());
265 if (!address
.is_initialized())
268 int file_index
= address
.FileNumber();
269 if (static_cast<unsigned int>(file_index
) >= block_files_
.size() ||
270 !block_files_
[file_index
]) {
271 // We need to open the file
272 if (!OpenBlockFile(file_index
))
275 DCHECK(block_files_
.size() >= static_cast<unsigned int>(file_index
));
276 return block_files_
[file_index
];
279 bool BlockFiles::CreateBlock(FileType block_type
, int block_count
,
280 Addr
* block_address
) {
281 DCHECK(thread_checker_
->CalledOnValidThread());
282 if (block_type
< RANKINGS
|| block_type
> BLOCK_4K
||
283 block_count
< 1 || block_count
> 4)
288 MappedFile
* file
= FileForNewBlock(block_type
, block_count
);
292 ScopedFlush
flush(file
);
293 BlockHeader
header(file
);
296 for (int i
= block_count
; i
<= 4; i
++) {
297 if (header
->empty
[i
- 1]) {
305 if (!header
.CreateMapBlock(target_size
, block_count
, &index
))
308 Addr
address(block_type
, block_count
, header
->this_file
, index
);
309 block_address
->set_value(address
.value());
310 Trace("CreateBlock 0x%x", address
.value());
314 void BlockFiles::DeleteBlock(Addr address
, bool deep
) {
315 DCHECK(thread_checker_
->CalledOnValidThread());
316 if (!address
.is_initialized() || address
.is_separate_file())
320 zero_buffer_
= new char[Addr::BlockSizeForFileType(BLOCK_4K
) * 4];
321 memset(zero_buffer_
, 0, Addr::BlockSizeForFileType(BLOCK_4K
) * 4);
323 MappedFile
* file
= GetFile(address
);
327 Trace("DeleteBlock 0x%x", address
.value());
329 size_t size
= address
.BlockSize() * address
.num_blocks();
330 size_t offset
= address
.start_block() * address
.BlockSize() +
333 file
->Write(zero_buffer_
, size
, offset
);
335 BlockHeader
header(file
);
336 header
.DeleteMapBlock(address
.start_block(), address
.num_blocks());
339 if (!header
->num_entries
) {
340 // This file is now empty. Let's try to delete it.
341 FileType type
= Addr::RequiredFileType(header
->entry_size
);
342 if (Addr::BlockSizeForFileType(RANKINGS
) == header
->entry_size
)
344 RemoveEmptyFile(type
); // Ignore failures.
348 void BlockFiles::CloseFiles() {
350 DCHECK(thread_checker_
->CalledOnValidThread());
353 for (unsigned int i
= 0; i
< block_files_
.size(); i
++) {
354 if (block_files_
[i
]) {
355 block_files_
[i
]->Release();
356 block_files_
[i
] = NULL
;
359 block_files_
.clear();
362 void BlockFiles::ReportStats() {
363 DCHECK(thread_checker_
->CalledOnValidThread());
364 int used_blocks
[kFirstAdditionalBlockFile
];
365 int load
[kFirstAdditionalBlockFile
];
366 for (int i
= 0; i
< kFirstAdditionalBlockFile
; i
++) {
367 GetFileStats(i
, &used_blocks
[i
], &load
[i
]);
369 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks
[0]);
370 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks
[1]);
371 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks
[2]);
372 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks
[3]);
374 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load
[0], 101);
375 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load
[1], 101);
376 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load
[2], 101);
377 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load
[3], 101);
380 bool BlockFiles::IsValid(Addr address
) {
384 if (!address
.is_initialized() || address
.is_separate_file())
387 MappedFile
* file
= GetFile(address
);
391 BlockHeader
header(file
);
392 bool rv
= header
.UsedMapBlock(address
.start_block(), address
.num_blocks());
395 static bool read_contents
= false;
397 scoped_ptr
<char[]> buffer
;
398 buffer
.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K
) * 4]);
399 size_t size
= address
.BlockSize() * address
.num_blocks();
400 size_t offset
= address
.start_block() * address
.BlockSize() +
402 bool ok
= file
->Read(buffer
.get(), size
, offset
);
410 bool BlockFiles::CreateBlockFile(int index
, FileType file_type
, bool force
) {
411 base::FilePath name
= Name(index
);
413 force
? base::PLATFORM_FILE_CREATE_ALWAYS
: base::PLATFORM_FILE_CREATE
;
414 flags
|= base::PLATFORM_FILE_WRITE
| base::PLATFORM_FILE_EXCLUSIVE_WRITE
;
416 scoped_refptr
<File
> file(new File(
417 base::CreatePlatformFile(name
, flags
, NULL
, NULL
)));
418 if (!file
->IsValid())
421 BlockFileHeader header
;
422 memset(&header
, 0, sizeof(header
));
423 header
.magic
= kBlockMagic
;
424 header
.version
= kBlockVersion2
;
425 header
.entry_size
= Addr::BlockSizeForFileType(file_type
);
426 header
.this_file
= static_cast<int16
>(index
);
427 DCHECK(index
<= kint16max
&& index
>= 0);
429 return file
->Write(&header
, sizeof(header
), 0);
432 bool BlockFiles::OpenBlockFile(int index
) {
433 if (block_files_
.size() - 1 < static_cast<unsigned int>(index
)) {
435 int to_add
= index
- static_cast<int>(block_files_
.size()) + 1;
436 block_files_
.resize(block_files_
.size() + to_add
);
439 base::FilePath name
= Name(index
);
440 scoped_refptr
<MappedFile
> file(new MappedFile());
442 if (!file
->Init(name
, kBlockHeaderSize
)) {
443 LOG(ERROR
) << "Failed to open " << name
.value();
447 size_t file_len
= file
->GetLength();
448 if (file_len
< static_cast<size_t>(kBlockHeaderSize
)) {
449 LOG(ERROR
) << "File too small " << name
.value();
453 BlockHeader
header(file
);
454 if (kBlockMagic
!= header
->magic
|| kBlockVersion2
!= header
->version
) {
455 LOG(ERROR
) << "Invalid file version or magic " << name
.value();
459 if (header
->updating
|| !header
.ValidateCounters()) {
460 // Last instance was not properly shutdown, or counters are out of sync.
461 if (!FixBlockFileHeader(file
.get())) {
462 LOG(ERROR
) << "Unable to fix block file " << name
.value();
467 if (static_cast<int>(file_len
) <
468 header
->max_entries
* header
->entry_size
+ kBlockHeaderSize
) {
469 LOG(ERROR
) << "File too small " << name
.value();
474 // Load the links file into memory with a single read.
475 scoped_ptr
<char[]> buf(new char[file_len
]);
476 if (!file
->Read(buf
.get(), file_len
, 0))
480 ScopedFlush
flush(file
.get());
481 DCHECK(!block_files_
[index
]);
482 file
.swap(&block_files_
[index
]);
486 bool BlockFiles::GrowBlockFile(MappedFile
* file
, BlockFileHeader
* header
) {
487 if (kMaxBlocks
== header
->max_entries
)
490 ScopedFlush
flush(file
);
491 DCHECK(!header
->empty
[3]);
492 int new_size
= header
->max_entries
+ 1024;
493 if (new_size
> kMaxBlocks
)
494 new_size
= kMaxBlocks
;
496 int new_size_bytes
= new_size
* header
->entry_size
+ sizeof(*header
);
498 if (!file
->SetLength(new_size_bytes
)) {
499 // Most likely we are trying to truncate the file, so the header is wrong.
500 if (header
->updating
< 10 && !FixBlockFileHeader(file
)) {
501 // If we can't fix the file increase the lock guard so we'll pick it on
502 // the next start and replace it.
503 header
->updating
= 100;
506 return (header
->max_entries
>= new_size
);
509 FileLock
lock(header
);
510 header
->empty
[3] = (new_size
- header
->max_entries
) / 4; // 4 blocks entries
511 header
->max_entries
= new_size
;
516 MappedFile
* BlockFiles::FileForNewBlock(FileType block_type
, int block_count
) {
517 COMPILE_ASSERT(RANKINGS
== 1, invalid_file_type
);
518 MappedFile
* file
= block_files_
[block_type
- 1];
519 BlockHeader
header(file
);
521 TimeTicks start
= TimeTicks::Now();
522 while (header
.NeedToGrowBlockFile(block_count
)) {
523 if (kMaxBlocks
== header
->max_entries
) {
524 file
= NextFile(file
);
527 header
= BlockHeader(file
);
531 if (!GrowBlockFile(file
, header
.Get()))
535 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start
);
539 MappedFile
* BlockFiles::NextFile(MappedFile
* file
) {
540 ScopedFlush
flush(file
);
541 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
542 int new_file
= header
->next_file
;
544 // RANKINGS is not reported as a type for small entries, but we may be
545 // extending the rankings block file.
546 FileType type
= Addr::RequiredFileType(header
->entry_size
);
547 if (header
->entry_size
== Addr::BlockSizeForFileType(RANKINGS
))
550 new_file
= CreateNextBlockFile(type
);
554 FileLock
lock(header
);
555 header
->next_file
= new_file
;
558 // Only the block_file argument is relevant for what we want.
559 Addr
address(BLOCK_256
, 1, new_file
, 0);
560 return GetFile(address
);
563 int BlockFiles::CreateNextBlockFile(FileType block_type
) {
564 for (int i
= kFirstAdditionalBlockFile
; i
<= kMaxBlockFile
; i
++) {
565 if (CreateBlockFile(i
, block_type
, false))
571 // We walk the list of files for this particular block type, deleting the ones
573 bool BlockFiles::RemoveEmptyFile(FileType block_type
) {
574 MappedFile
* file
= block_files_
[block_type
- 1];
575 BlockFileHeader
* header
= reinterpret_cast<BlockFileHeader
*>(file
->buffer());
577 while (header
->next_file
) {
578 // Only the block_file argument is relevant for what we want.
579 Addr
address(BLOCK_256
, 1, header
->next_file
, 0);
580 MappedFile
* next_file
= GetFile(address
);
584 BlockFileHeader
* next_header
=
585 reinterpret_cast<BlockFileHeader
*>(next_file
->buffer());
586 if (!next_header
->num_entries
) {
587 DCHECK_EQ(next_header
->entry_size
, header
->entry_size
);
588 // Delete next_file and remove it from the chain.
589 int file_index
= header
->next_file
;
590 header
->next_file
= next_header
->next_file
;
591 DCHECK(block_files_
.size() >= static_cast<unsigned int>(file_index
));
594 // We get a new handle to the file and release the old one so that the
595 // file gets unmmaped... so we can delete it.
596 base::FilePath name
= Name(file_index
);
597 scoped_refptr
<File
> this_file(new File(false));
598 this_file
->Init(name
);
599 block_files_
[file_index
]->Release();
600 block_files_
[file_index
] = NULL
;
602 int failure
= DeleteCacheFile(name
) ? 0 : 1;
603 UMA_HISTOGRAM_COUNTS("DiskCache.DeleteFailed2", failure
);
605 LOG(ERROR
) << "Failed to delete " << name
.value() << " from the cache.";
609 header
= next_header
;
615 // Note that we expect to be called outside of a FileLock... however, we cannot
616 // DCHECK on header->updating because we may be fixing a crash.
617 bool BlockFiles::FixBlockFileHeader(MappedFile
* file
) {
618 ScopedFlush
flush(file
);
619 BlockHeader
header(file
);
620 int file_size
= static_cast<int>(file
->GetLength());
621 if (file_size
< header
.Size())
622 return false; // file_size > 2GB is also an error.
624 const int kMinBlockSize
= 36;
625 const int kMaxBlockSize
= 4096;
626 if (header
->entry_size
< kMinBlockSize
||
627 header
->entry_size
> kMaxBlockSize
|| header
->num_entries
< 0)
630 // Make sure that we survive crashes.
631 header
->updating
= 1;
632 int expected
= header
->entry_size
* header
->max_entries
+ header
.Size();
633 if (file_size
!= expected
) {
634 int max_expected
= header
->entry_size
* kMaxBlocks
+ header
.Size();
635 if (file_size
< expected
|| header
->empty
[3] || file_size
> max_expected
) {
637 LOG(ERROR
) << "Unexpected file size";
640 // We were in the middle of growing the file.
641 int num_entries
= (file_size
- header
.Size()) / header
->entry_size
;
642 header
->max_entries
= num_entries
;
645 header
.FixAllocationCounters();
646 int empty_blocks
= header
.EmptyBlocks();
647 if (empty_blocks
+ header
->num_entries
> header
->max_entries
)
648 header
->num_entries
= header
->max_entries
- empty_blocks
;
650 if (!header
.ValidateCounters())
653 header
->updating
= 0;
657 // We are interested in the total number of blocks used by this file type, and
658 // the max number of blocks that we can store (reported as the percentage of
659 // used blocks). In order to find out the number of used blocks, we have to
660 // substract the empty blocks from the total blocks for each file in the chain.
661 void BlockFiles::GetFileStats(int index
, int* used_count
, int* load
) {
666 if (!block_files_
[index
] && !OpenBlockFile(index
))
669 BlockFileHeader
* header
=
670 reinterpret_cast<BlockFileHeader
*>(block_files_
[index
]->buffer());
672 max_blocks
+= header
->max_entries
;
673 int used
= header
->max_entries
;
674 for (int i
= 0; i
< 4; i
++) {
675 used
-= header
->empty
[i
] * (i
+ 1);
680 if (!header
->next_file
)
682 index
= header
->next_file
;
685 *load
= *used_count
* 100 / max_blocks
;
688 base::FilePath
BlockFiles::Name(int index
) {
689 // The file format allows for 256 files.
690 DCHECK(index
< 256 || index
>= 0);
691 std::string tmp
= base::StringPrintf("%s%d", kBlockName
, index
);
692 return path_
.AppendASCII(tmp
);
695 } // namespace disk_cache