1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sw=4 sts=4 cin et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsDiskCacheMap.h"
9 #include "nsDiskCacheBinding.h"
10 #include "nsDiskCacheEntry.h"
11 #include "nsDiskCacheDevice.h"
12 #include "nsCacheService.h"
15 #include "nsPrintfCString.h"
17 #include "nsISerializable.h"
18 #include "nsSerializationHelper.h"
20 #include "mozilla/MemoryReporting.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/VisualEventTracer.h"
25 using namespace mozilla
;
27 /******************************************************************************
29 *****************************************************************************/
36 nsDiskCacheMap::Open(nsIFile
* cacheDirectory
,
37 nsDiskCache::CorruptCacheInfo
* corruptInfo
,
38 bool reportCacheCleanTelemetryData
)
40 NS_ENSURE_ARG_POINTER(corruptInfo
);
42 // Assume we have an unexpected error until we find otherwise.
43 *corruptInfo
= nsDiskCache::kUnexpectedError
;
44 NS_ENSURE_ARG_POINTER(cacheDirectory
);
45 if (mMapFD
) return NS_ERROR_ALREADY_INITIALIZED
;
47 mCacheDirectory
= cacheDirectory
; // save a reference for ourselves
49 // create nsIFile for _CACHE_MAP_
51 nsCOMPtr
<nsIFile
> file
;
52 rv
= cacheDirectory
->Clone(getter_AddRefs(file
));
53 rv
= file
->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
54 NS_ENSURE_SUCCESS(rv
, rv
);
56 // open the file - restricted to user, the data could be confidential
57 rv
= file
->OpenNSPRFileDesc(PR_RDWR
| PR_CREATE_FILE
, 00600, &mMapFD
);
59 *corruptInfo
= nsDiskCache::kOpenCacheMapError
;
60 NS_WARNING("Could not open cache map file");
61 return NS_ERROR_FILE_CORRUPTED
;
64 bool cacheFilesExist
= CacheFilesExist();
65 rv
= NS_ERROR_FILE_CORRUPTED
; // presume the worst
66 uint32_t mapSize
= PR_Available(mMapFD
);
68 if (NS_FAILED(InitCacheClean(cacheDirectory
,
70 reportCacheCleanTelemetryData
))) {
71 // corruptInfo is set in the call to InitCacheClean
75 // check size of map file
76 if (mapSize
== 0) { // creating a new _CACHE_MAP_
78 // block files shouldn't exist if we're creating the _CACHE_MAP_
79 if (cacheFilesExist
) {
80 *corruptInfo
= nsDiskCache::kBlockFilesShouldNotExist
;
84 if (NS_FAILED(CreateCacheSubDirectories())) {
85 *corruptInfo
= nsDiskCache::kCreateCacheSubdirectories
;
89 // create the file - initialize in memory
90 memset(&mHeader
, 0, sizeof(nsDiskCacheHeader
));
91 mHeader
.mVersion
= nsDiskCache::kCurrentVersion
;
92 mHeader
.mRecordCount
= kMinRecordCount
;
93 mRecordArray
= (nsDiskCacheRecord
*)
94 PR_CALLOC(mHeader
.mRecordCount
* sizeof(nsDiskCacheRecord
));
96 *corruptInfo
= nsDiskCache::kOutOfMemory
;
97 rv
= NS_ERROR_OUT_OF_MEMORY
;
100 } else if (mapSize
>= sizeof(nsDiskCacheHeader
)) { // read existing _CACHE_MAP_
102 // if _CACHE_MAP_ exists, so should the block files
103 if (!cacheFilesExist
) {
104 *corruptInfo
= nsDiskCache::kBlockFilesShouldExist
;
108 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
111 uint32_t bytesRead
= PR_Read(mMapFD
, &mHeader
, sizeof(nsDiskCacheHeader
));
112 if (sizeof(nsDiskCacheHeader
) != bytesRead
) {
113 *corruptInfo
= nsDiskCache::kHeaderSizeNotRead
;
118 if (mHeader
.mIsDirty
) {
119 *corruptInfo
= nsDiskCache::kHeaderIsDirty
;
123 if (mHeader
.mVersion
!= nsDiskCache::kCurrentVersion
) {
124 *corruptInfo
= nsDiskCache::kVersionMismatch
;
128 uint32_t recordArraySize
=
129 mHeader
.mRecordCount
* sizeof(nsDiskCacheRecord
);
130 if (mapSize
< recordArraySize
+ sizeof(nsDiskCacheHeader
)) {
131 *corruptInfo
= nsDiskCache::kRecordsIncomplete
;
135 // Get the space for the records
136 mRecordArray
= (nsDiskCacheRecord
*) PR_MALLOC(recordArraySize
);
138 *corruptInfo
= nsDiskCache::kOutOfMemory
;
139 rv
= NS_ERROR_OUT_OF_MEMORY
;
144 bytesRead
= PR_Read(mMapFD
, mRecordArray
, recordArraySize
);
145 if (bytesRead
< recordArraySize
) {
146 *corruptInfo
= nsDiskCache::kNotEnoughToRead
;
150 // Unswap each record
152 for (int32_t i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
153 if (mRecordArray
[i
].HashNumber()) {
154 #if defined(IS_LITTLE_ENDIAN)
155 mRecordArray
[i
].Unswap();
161 // verify entry count
162 if (total
!= mHeader
.mEntryCount
) {
163 *corruptInfo
= nsDiskCache::kEntryCountIncorrect
;
168 *corruptInfo
= nsDiskCache::kHeaderIncomplete
;
172 rv
= OpenBlockFiles(corruptInfo
);
174 // corruptInfo is set in the call to OpenBlockFiles
178 // set dirty bit and flush header
179 mHeader
.mIsDirty
= true;
182 *corruptInfo
= nsDiskCache::kFlushHeaderError
;
186 Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD
,
187 (uint32_t)SizeOfExcludingThis(moz_malloc_size_of
));
189 *corruptInfo
= nsDiskCache::kNotCorrupt
;
200 nsDiskCacheMap::Close(bool flush
)
202 nsCacheService::AssertOwnsLock();
205 // Cancel any pending cache validation event, the FlushRecords call below
206 // will validate the cache.
207 if (mCleanCacheTimer
) {
208 mCleanCacheTimer
->Cancel();
211 // If cache map file and its block files are still open, close them
214 rv
= CloseBlockFiles(flush
);
215 if (NS_SUCCEEDED(rv
) && flush
&& mRecordArray
) {
216 // write the map records
217 rv
= FlushRecords(false); // don't bother swapping buckets back
218 if (NS_SUCCEEDED(rv
)) {
220 mHeader
.mIsDirty
= false;
224 if ((PR_Close(mMapFD
) != PR_SUCCESS
) && (NS_SUCCEEDED(rv
)))
225 rv
= NS_ERROR_UNEXPECTED
;
235 PR_FREEIF(mRecordArray
);
243 nsDiskCacheMap::Trim()
245 nsresult rv
, rv2
= NS_OK
;
246 for (int i
=0; i
< kNumBlockFiles
; ++i
) {
247 rv
= mBlockFile
[i
].Trim();
248 if (NS_FAILED(rv
)) rv2
= rv
; // if one or more errors, report at least one
250 // Try to shrink the records array
251 rv
= ShrinkRecords();
252 if (NS_FAILED(rv
)) rv2
= rv
; // if one or more errors, report at least one
258 nsDiskCacheMap::FlushHeader()
260 if (!mMapFD
) return NS_ERROR_NOT_AVAILABLE
;
262 // seek to beginning of cache map
263 int32_t filePos
= PR_Seek(mMapFD
, 0, PR_SEEK_SET
);
264 if (filePos
!= 0) return NS_ERROR_UNEXPECTED
;
268 int32_t bytesWritten
= PR_Write(mMapFD
, &mHeader
, sizeof(nsDiskCacheHeader
));
270 if (sizeof(nsDiskCacheHeader
) != bytesWritten
) {
271 return NS_ERROR_UNEXPECTED
;
274 PRStatus err
= PR_Sync(mMapFD
);
275 if (err
!= PR_SUCCESS
) return NS_ERROR_UNEXPECTED
;
277 // If we have a clean header then revalidate the cache clean file
278 if (!mHeader
.mIsDirty
) {
287 nsDiskCacheMap::FlushRecords(bool unswap
)
289 if (!mMapFD
) return NS_ERROR_NOT_AVAILABLE
;
291 // seek to beginning of buckets
292 int32_t filePos
= PR_Seek(mMapFD
, sizeof(nsDiskCacheHeader
), PR_SEEK_SET
);
293 if (filePos
!= sizeof(nsDiskCacheHeader
))
294 return NS_ERROR_UNEXPECTED
;
296 #if defined(IS_LITTLE_ENDIAN)
298 for (int32_t i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
299 if (mRecordArray
[i
].HashNumber())
300 mRecordArray
[i
].Swap();
304 int32_t recordArraySize
= sizeof(nsDiskCacheRecord
) * mHeader
.mRecordCount
;
306 int32_t bytesWritten
= PR_Write(mMapFD
, mRecordArray
, recordArraySize
);
307 if (bytesWritten
!= recordArraySize
)
308 return NS_ERROR_UNEXPECTED
;
310 #if defined(IS_LITTLE_ENDIAN)
312 // Unswap each record
313 for (int32_t i
= 0; i
< mHeader
.mRecordCount
; ++i
) {
314 if (mRecordArray
[i
].HashNumber())
315 mRecordArray
[i
].Unswap();
329 nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex
, uint32_t targetRank
)
331 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
334 for (int i
= mHeader
.mBucketUsage
[bucketIndex
]-1; i
>= 0; i
--) {
335 if ((rank
< records
[i
].EvictionRank()) &&
336 ((targetRank
== 0) || (records
[i
].EvictionRank() < targetRank
)))
337 rank
= records
[i
].EvictionRank();
343 nsDiskCacheMap::GrowRecords()
345 if (mHeader
.mRecordCount
>= mMaxRecordCount
)
347 CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
349 // Resize the record array
350 int32_t newCount
= mHeader
.mRecordCount
<< 1;
351 if (newCount
> mMaxRecordCount
)
352 newCount
= mMaxRecordCount
;
353 nsDiskCacheRecord
*newArray
= (nsDiskCacheRecord
*)
354 PR_REALLOC(mRecordArray
, newCount
* sizeof(nsDiskCacheRecord
));
356 return NS_ERROR_OUT_OF_MEMORY
;
358 // Space out the buckets
359 uint32_t oldRecordsPerBucket
= GetRecordsPerBucket();
360 uint32_t newRecordsPerBucket
= newCount
/ kBuckets
;
361 // Work from back to space out each bucket to the new array
362 for (int bucketIndex
= kBuckets
- 1; bucketIndex
>= 0; --bucketIndex
) {
364 nsDiskCacheRecord
*newRecords
= newArray
+ bucketIndex
* newRecordsPerBucket
;
365 const uint32_t count
= mHeader
.mBucketUsage
[bucketIndex
];
367 newArray
+ bucketIndex
* oldRecordsPerBucket
,
368 count
* sizeof(nsDiskCacheRecord
));
369 // clear unused records
370 memset(newRecords
+ count
, 0,
371 (newRecordsPerBucket
- count
) * sizeof(nsDiskCacheRecord
));
374 // Set as the new record array
375 mRecordArray
= newArray
;
376 mHeader
.mRecordCount
= newCount
;
384 nsDiskCacheMap::ShrinkRecords()
386 if (mHeader
.mRecordCount
<= kMinRecordCount
)
388 CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
390 // Verify if we can shrink the record array: all buckets must be less than
392 uint32_t maxUsage
= 0, bucketIndex
;
393 for (bucketIndex
= 0; bucketIndex
< kBuckets
; ++bucketIndex
) {
394 if (maxUsage
< mHeader
.mBucketUsage
[bucketIndex
])
395 maxUsage
= mHeader
.mBucketUsage
[bucketIndex
];
397 // Determine new bucket size, halve size until maxUsage
398 uint32_t oldRecordsPerBucket
= GetRecordsPerBucket();
399 uint32_t newRecordsPerBucket
= oldRecordsPerBucket
;
400 while (maxUsage
< (newRecordsPerBucket
>> 1))
401 newRecordsPerBucket
>>= 1;
402 if (newRecordsPerBucket
< (kMinRecordCount
/ kBuckets
))
403 newRecordsPerBucket
= (kMinRecordCount
/ kBuckets
);
404 NS_ASSERTION(newRecordsPerBucket
<= oldRecordsPerBucket
,
405 "ShrinkRecords() can't grow records!");
406 if (newRecordsPerBucket
== oldRecordsPerBucket
)
408 // Move the buckets close to each other
409 for (bucketIndex
= 1; bucketIndex
< kBuckets
; ++bucketIndex
) {
411 memmove(mRecordArray
+ bucketIndex
* newRecordsPerBucket
,
412 mRecordArray
+ bucketIndex
* oldRecordsPerBucket
,
413 newRecordsPerBucket
* sizeof(nsDiskCacheRecord
));
416 // Shrink the record array memory block itself
417 uint32_t newCount
= newRecordsPerBucket
* kBuckets
;
418 nsDiskCacheRecord
* newArray
= (nsDiskCacheRecord
*)
419 PR_REALLOC(mRecordArray
, newCount
* sizeof(nsDiskCacheRecord
));
421 return NS_ERROR_OUT_OF_MEMORY
;
423 // Set as the new record array
424 mRecordArray
= newArray
;
425 mHeader
.mRecordCount
= newCount
;
433 nsDiskCacheMap::AddRecord( nsDiskCacheRecord
* mapRecord
,
434 nsDiskCacheRecord
* oldRecord
)
436 CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord
->HashNumber()));
438 const uint32_t hashNumber
= mapRecord
->HashNumber();
439 const uint32_t bucketIndex
= GetBucketIndex(hashNumber
);
440 const uint32_t count
= mHeader
.mBucketUsage
[bucketIndex
];
442 oldRecord
->SetHashNumber(0); // signify no record
444 if (count
== GetRecordsPerBucket()) {
445 // Ignore failure to grow the record space, we will then reuse old records
449 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
450 if (count
< GetRecordsPerBucket()) {
451 // stick the new record at the end
452 records
[count
] = *mapRecord
;
453 mHeader
.mEntryCount
++;
454 mHeader
.mBucketUsage
[bucketIndex
]++;
455 if (mHeader
.mEvictionRank
[bucketIndex
] < mapRecord
->EvictionRank())
456 mHeader
.mEvictionRank
[bucketIndex
] = mapRecord
->EvictionRank();
459 // Find the record with the highest eviction rank
460 nsDiskCacheRecord
* mostEvictable
= &records
[0];
461 for (int i
= count
-1; i
> 0; i
--) {
462 if (records
[i
].EvictionRank() > mostEvictable
->EvictionRank())
463 mostEvictable
= &records
[i
];
465 *oldRecord
= *mostEvictable
; // i == GetRecordsPerBucket(), so
466 // evict the mostEvictable
467 *mostEvictable
= *mapRecord
; // replace it with the new record
468 // check if we need to update mostEvictable entry in header
469 if (mHeader
.mEvictionRank
[bucketIndex
] < mapRecord
->EvictionRank())
470 mHeader
.mEvictionRank
[bucketIndex
] = mapRecord
->EvictionRank();
471 if (oldRecord
->EvictionRank() >= mHeader
.mEvictionRank
[bucketIndex
])
472 mHeader
.mEvictionRank
[bucketIndex
] = GetBucketRank(bucketIndex
, 0);
476 NS_ASSERTION(mHeader
.mEvictionRank
[bucketIndex
] == GetBucketRank(bucketIndex
, 0),
477 "eviction rank out of sync");
483 nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord
* mapRecord
)
485 CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord
->HashNumber()));
487 const uint32_t hashNumber
= mapRecord
->HashNumber();
488 const uint32_t bucketIndex
= GetBucketIndex(hashNumber
);
489 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
491 for (int i
= mHeader
.mBucketUsage
[bucketIndex
]-1; i
>= 0; i
--) {
492 if (records
[i
].HashNumber() == hashNumber
) {
493 const uint32_t oldRank
= records
[i
].EvictionRank();
495 // stick the new record here
496 records
[i
] = *mapRecord
;
498 // update eviction rank in header if necessary
499 if (mHeader
.mEvictionRank
[bucketIndex
] < mapRecord
->EvictionRank())
500 mHeader
.mEvictionRank
[bucketIndex
] = mapRecord
->EvictionRank();
501 else if (mHeader
.mEvictionRank
[bucketIndex
] == oldRank
)
502 mHeader
.mEvictionRank
[bucketIndex
] = GetBucketRank(bucketIndex
, 0);
506 NS_ASSERTION(mHeader
.mEvictionRank
[bucketIndex
] == GetBucketRank(bucketIndex
, 0),
507 "eviction rank out of sync");
511 NS_NOTREACHED("record not found");
512 return NS_ERROR_UNEXPECTED
;
517 nsDiskCacheMap::FindRecord( uint32_t hashNumber
, nsDiskCacheRecord
* result
)
519 const uint32_t bucketIndex
= GetBucketIndex(hashNumber
);
520 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
522 for (int i
= mHeader
.mBucketUsage
[bucketIndex
]-1; i
>= 0; i
--) {
523 if (records
[i
].HashNumber() == hashNumber
) {
524 *result
= records
[i
]; // copy the record
525 NS_ASSERTION(result
->ValidRecord(), "bad cache map record");
529 return NS_ERROR_CACHE_KEY_NOT_FOUND
;
534 nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord
* mapRecord
)
536 CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord
->HashNumber()));
538 const uint32_t hashNumber
= mapRecord
->HashNumber();
539 const uint32_t bucketIndex
= GetBucketIndex(hashNumber
);
540 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
541 uint32_t last
= mHeader
.mBucketUsage
[bucketIndex
]-1;
543 for (int i
= last
; i
>= 0; i
--) {
544 if (records
[i
].HashNumber() == hashNumber
) {
545 // found it, now delete it.
546 uint32_t evictionRank
= records
[i
].EvictionRank();
547 NS_ASSERTION(evictionRank
== mapRecord
->EvictionRank(),
548 "evictionRank out of sync");
549 // if not the last record, shift last record into opening
550 records
[i
] = records
[last
];
551 records
[last
].SetHashNumber(0); // clear last record
552 mHeader
.mBucketUsage
[bucketIndex
] = last
;
553 mHeader
.mEntryCount
--;
555 // update eviction rank
556 uint32_t bucketIndex
= GetBucketIndex(mapRecord
->HashNumber());
557 if (mHeader
.mEvictionRank
[bucketIndex
] <= evictionRank
) {
558 mHeader
.mEvictionRank
[bucketIndex
] = GetBucketRank(bucketIndex
, 0);
563 NS_ASSERTION(mHeader
.mEvictionRank
[bucketIndex
] ==
564 GetBucketRank(bucketIndex
, 0), "eviction rank out of sync");
568 return NS_ERROR_UNEXPECTED
;
573 nsDiskCacheMap::VisitEachRecord(uint32_t bucketIndex
,
574 nsDiskCacheRecordVisitor
* visitor
,
575 uint32_t evictionRank
)
577 int32_t rv
= kVisitNextRecord
;
578 uint32_t count
= mHeader
.mBucketUsage
[bucketIndex
];
579 nsDiskCacheRecord
* records
= GetFirstRecordInBucket(bucketIndex
);
581 // call visitor for each entry (matching any eviction rank)
582 for (int i
= count
-1; i
>= 0; i
--) {
583 if (evictionRank
> records
[i
].EvictionRank()) continue;
585 rv
= visitor
->VisitRecord(&records
[i
]);
586 if (rv
== kStopVisitingRecords
)
587 break; // Stop visiting records
589 if (rv
== kDeleteRecordAndContinue
) {
591 records
[i
] = records
[count
];
592 records
[count
].SetHashNumber(0);
597 if (mHeader
.mBucketUsage
[bucketIndex
] - count
!= 0) {
598 mHeader
.mEntryCount
-= mHeader
.mBucketUsage
[bucketIndex
] - count
;
599 mHeader
.mBucketUsage
[bucketIndex
] = count
;
600 // recalc eviction rank
601 mHeader
.mEvictionRank
[bucketIndex
] = GetBucketRank(bucketIndex
, 0);
603 NS_ASSERTION(mHeader
.mEvictionRank
[bucketIndex
] ==
604 GetBucketRank(bucketIndex
, 0), "eviction rank out of sync");
613 * Visit every record in cache map in the most convenient order
616 nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor
* visitor
)
618 for (int bucketIndex
= 0; bucketIndex
< kBuckets
; ++bucketIndex
) {
619 if (VisitEachRecord(bucketIndex
, visitor
, 0) == kStopVisitingRecords
)
629 * Just like VisitRecords, but visits the records in order of their eviction rank
632 nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor
* visitor
)
634 uint32_t tempRank
[kBuckets
];
637 // copy eviction rank array
638 for (bucketIndex
= 0; bucketIndex
< kBuckets
; ++bucketIndex
)
639 tempRank
[bucketIndex
] = mHeader
.mEvictionRank
[bucketIndex
];
641 // Maximum number of iterations determined by number of records
642 // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
643 // the value could decrease if some entry is evicted.
644 int32_t entryCount
= mHeader
.mEntryCount
;
645 for (int n
= 0; n
< entryCount
; ++n
) {
647 // find bucket with highest eviction rank
649 for (int i
= 0; i
< kBuckets
; ++i
) {
650 if (rank
< tempRank
[i
]) {
656 if (rank
== 0) break; // we've examined all the records
658 // visit records in bucket with eviction ranks >= target eviction rank
659 if (VisitEachRecord(bucketIndex
, visitor
, rank
) == kStopVisitingRecords
)
662 // find greatest rank less than 'rank'
663 tempRank
[bucketIndex
] = GetBucketRank(bucketIndex
, rank
);
671 nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo
* corruptInfo
)
673 NS_ENSURE_ARG_POINTER(corruptInfo
);
675 // create nsIFile for block file
676 nsCOMPtr
<nsIFile
> blockFile
;
678 *corruptInfo
= nsDiskCache::kUnexpectedError
;
680 for (int i
= 0; i
< kNumBlockFiles
; ++i
) {
681 rv
= GetBlockFileForIndex(i
, getter_AddRefs(blockFile
));
683 *corruptInfo
= nsDiskCache::kCouldNotGetBlockFileForIndex
;
687 uint32_t blockSize
= GetBlockSizeForIndex(i
+1); // +1 to match file selectors 1,2,3
688 uint32_t bitMapSize
= GetBitMapSizeForIndex(i
+1);
689 rv
= mBlockFile
[i
].Open(blockFile
, blockSize
, bitMapSize
, corruptInfo
);
691 // corruptInfo was set inside the call to mBlockFile[i].Open
695 // close all files in case of any error
697 (void)CloseBlockFiles(false); // we already have an error to report
704 nsDiskCacheMap::CloseBlockFiles(bool flush
)
706 nsresult rv
, rv2
= NS_OK
;
707 for (int i
=0; i
< kNumBlockFiles
; ++i
) {
708 rv
= mBlockFile
[i
].Close(flush
);
709 if (NS_FAILED(rv
)) rv2
= rv
; // if one or more errors, report at least one
716 nsDiskCacheMap::CacheFilesExist()
718 nsCOMPtr
<nsIFile
> blockFile
;
721 for (int i
= 0; i
< kNumBlockFiles
; ++i
) {
723 rv
= GetBlockFileForIndex(i
, getter_AddRefs(blockFile
));
724 if (NS_FAILED(rv
)) return false;
726 rv
= blockFile
->Exists(&exists
);
727 if (NS_FAILED(rv
) || !exists
) return false;
735 nsDiskCacheMap::CreateCacheSubDirectories()
737 if (!mCacheDirectory
)
738 return NS_ERROR_UNEXPECTED
;
740 for (int32_t index
= 0 ; index
< 16 ; index
++) {
741 nsCOMPtr
<nsIFile
> file
;
742 nsresult rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
746 rv
= file
->AppendNative(nsPrintfCString("%X", index
));
750 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
760 nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord
* record
)
762 CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record
->HashNumber()));
764 nsresult rv
= NS_ERROR_UNEXPECTED
;
765 nsDiskCacheEntry
* diskEntry
= nullptr;
766 uint32_t metaFile
= record
->MetaFile();
767 int32_t bytesRead
= 0;
769 if (!record
->MetaLocationInitialized()) return nullptr;
771 if (metaFile
== 0) { // entry/metadata stored in separate file
772 // open and read the file
773 nsCOMPtr
<nsIFile
> file
;
774 rv
= GetLocalFileForDiskCacheRecord(record
,
775 nsDiskCache::kMetaData
,
777 getter_AddRefs(file
));
778 NS_ENSURE_SUCCESS(rv
, nullptr);
780 CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
781 "[this=%p] reading disk cache entry", this));
783 PRFileDesc
* fd
= nullptr;
785 // open the file - restricted to user, the data could be confidential
786 rv
= file
->OpenNSPRFileDesc(PR_RDONLY
, 00600, &fd
);
787 NS_ENSURE_SUCCESS(rv
, nullptr);
789 int32_t fileSize
= PR_Available(fd
);
791 // an error occurred. We could call PR_GetError(), but how would that help?
792 rv
= NS_ERROR_UNEXPECTED
;
794 rv
= EnsureBuffer(fileSize
);
795 if (NS_SUCCEEDED(rv
)) {
796 bytesRead
= PR_Read(fd
, mBuffer
, fileSize
);
797 if (bytesRead
< fileSize
) {
798 rv
= NS_ERROR_UNEXPECTED
;
803 NS_ENSURE_SUCCESS(rv
, nullptr);
805 } else if (metaFile
< (kNumBlockFiles
+ 1)) {
806 // entry/metadata stored in cache block file
809 uint32_t blockCount
= record
->MetaBlockCount();
810 bytesRead
= blockCount
* GetBlockSizeForIndex(metaFile
);
812 rv
= EnsureBuffer(bytesRead
);
813 NS_ENSURE_SUCCESS(rv
, nullptr);
815 // read diskEntry, note when the blocks are at the end of file,
816 // bytesRead may be less than blockSize*blockCount.
817 // But the bytesRead should at least agree with the real disk entry size.
818 rv
= mBlockFile
[metaFile
- 1].ReadBlocks(mBuffer
,
819 record
->MetaStartBlock(),
822 NS_ENSURE_SUCCESS(rv
, nullptr);
824 diskEntry
= (nsDiskCacheEntry
*)mBuffer
;
825 diskEntry
->Unswap(); // disk to memory
826 // Check if calculated size agrees with bytesRead
827 if (bytesRead
< 0 || (uint32_t)bytesRead
< diskEntry
->Size())
830 // Return the buffer containing the diskEntry structure
836 * CreateDiskCacheEntry(nsCacheEntry * entry)
838 * Prepare an nsCacheEntry for writing to disk
841 nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding
* binding
,
844 nsCacheEntry
* entry
= binding
->mCacheEntry
;
845 if (!entry
) return nullptr;
847 // Store security info, if it is serializable
848 nsCOMPtr
<nsISupports
> infoObj
= entry
->SecurityInfo();
849 nsCOMPtr
<nsISerializable
> serializable
= do_QueryInterface(infoObj
);
850 if (infoObj
&& !serializable
) return nullptr;
853 nsresult rv
= NS_SerializeToString(serializable
, info
);
854 if (NS_FAILED(rv
)) return nullptr;
855 rv
= entry
->SetMetaDataElement("security-info", info
.get());
856 if (NS_FAILED(rv
)) return nullptr;
859 uint32_t keySize
= entry
->Key()->Length() + 1;
860 uint32_t metaSize
= entry
->MetaDataSize();
861 uint32_t size
= sizeof(nsDiskCacheEntry
) + keySize
+ metaSize
;
863 if (aSize
) *aSize
= size
;
865 nsresult rv
= EnsureBuffer(size
);
866 if (NS_FAILED(rv
)) return nullptr;
868 nsDiskCacheEntry
*diskEntry
= (nsDiskCacheEntry
*)mBuffer
;
869 diskEntry
->mHeaderVersion
= nsDiskCache::kCurrentVersion
;
870 diskEntry
->mMetaLocation
= binding
->mRecord
.MetaLocation();
871 diskEntry
->mFetchCount
= entry
->FetchCount();
872 diskEntry
->mLastFetched
= entry
->LastFetched();
873 diskEntry
->mLastModified
= entry
->LastModified();
874 diskEntry
->mExpirationTime
= entry
->ExpirationTime();
875 diskEntry
->mDataSize
= entry
->DataSize();
876 diskEntry
->mKeySize
= keySize
;
877 diskEntry
->mMetaDataSize
= metaSize
;
879 memcpy(diskEntry
->Key(), entry
->Key()->get(), keySize
);
881 rv
= entry
->FlattenMetaData(diskEntry
->MetaData(), metaSize
);
882 if (NS_FAILED(rv
)) return nullptr;
889 nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding
* binding
)
891 CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
892 binding
->mRecord
.HashNumber()));
894 mozilla::eventtracer::AutoEventTracer
writeDiskCacheEntry(
895 binding
->mCacheEntry
,
896 mozilla::eventtracer::eExec
,
897 mozilla::eventtracer::eDone
,
898 "net::cache::WriteDiskCacheEntry");
902 nsDiskCacheEntry
* diskEntry
= CreateDiskCacheEntry(binding
, &size
);
903 if (!diskEntry
) return NS_ERROR_UNEXPECTED
;
905 uint32_t fileIndex
= CalculateFileIndex(size
);
907 // Deallocate old storage if necessary
908 if (binding
->mRecord
.MetaLocationInitialized()) {
909 // we have existing storage
911 if ((binding
->mRecord
.MetaFile() == 0) &&
912 (fileIndex
== 0)) { // keeping the separate file
913 // just decrement total
914 DecrementTotalSize(binding
->mRecord
.MetaFileSize());
915 NS_ASSERTION(binding
->mRecord
.MetaFileGeneration() == binding
->mGeneration
,
916 "generations out of sync");
918 rv
= DeleteStorage(&binding
->mRecord
, nsDiskCache::kMetaData
);
919 NS_ENSURE_SUCCESS(rv
, rv
);
923 binding
->mRecord
.SetEvictionRank(ULONG_MAX
- SecondsFromPRTime(PR_Now()));
924 // write entry data to disk cache block file
927 if (fileIndex
!= 0) {
929 uint32_t blockSize
= GetBlockSizeForIndex(fileIndex
);
930 uint32_t blocks
= ((size
- 1) / blockSize
) + 1;
933 rv
= mBlockFile
[fileIndex
- 1].WriteBlocks(diskEntry
, size
, blocks
,
935 if (NS_SUCCEEDED(rv
)) {
936 // update binding and cache map record
937 binding
->mRecord
.SetMetaBlocks(fileIndex
, startBlock
, blocks
);
939 rv
= UpdateRecord(&binding
->mRecord
);
940 NS_ENSURE_SUCCESS(rv
, rv
);
942 // XXX we should probably write out bucket ourselves
944 IncrementTotalSize(blocks
, blockSize
);
948 if (fileIndex
== kNumBlockFiles
) {
949 fileIndex
= 0; // write data to separate file
953 // try next block file
958 if (fileIndex
== 0) {
959 // Write entry data to separate file
960 uint32_t metaFileSizeK
= ((size
+ 0x03FF) >> 10); // round up to nearest 1k
961 if (metaFileSizeK
> kMaxDataSizeK
)
962 metaFileSizeK
= kMaxDataSizeK
;
964 binding
->mRecord
.SetMetaFileGeneration(binding
->mGeneration
);
965 binding
->mRecord
.SetMetaFileSize(metaFileSizeK
);
966 rv
= UpdateRecord(&binding
->mRecord
);
967 NS_ENSURE_SUCCESS(rv
, rv
);
969 nsCOMPtr
<nsIFile
> localFile
;
970 rv
= GetLocalFileForDiskCacheRecord(&binding
->mRecord
,
971 nsDiskCache::kMetaData
,
973 getter_AddRefs(localFile
));
974 NS_ENSURE_SUCCESS(rv
, rv
);
978 // open the file - restricted to user, the data could be confidential
979 rv
= localFile
->OpenNSPRFileDesc(PR_RDWR
| PR_TRUNCATE
| PR_CREATE_FILE
, 00600, &fd
);
980 NS_ENSURE_SUCCESS(rv
, rv
);
983 int32_t bytesWritten
= PR_Write(fd
, diskEntry
, size
);
985 PRStatus err
= PR_Close(fd
);
986 if ((bytesWritten
!= (int32_t)size
) || (err
!= PR_SUCCESS
)) {
987 return NS_ERROR_UNEXPECTED
;
990 IncrementTotalSize(metaFileSizeK
);
998 nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding
* binding
, char * buffer
, uint32_t size
)
1000 CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
1001 binding
->mRecord
.HashNumber(), size
));
1003 uint32_t fileIndex
= binding
->mRecord
.DataFile();
1004 int32_t readSize
= size
;
1006 nsresult rv
= mBlockFile
[fileIndex
- 1].ReadBlocks(buffer
,
1007 binding
->mRecord
.DataStartBlock(),
1008 binding
->mRecord
.DataBlockCount(),
1010 NS_ENSURE_SUCCESS(rv
, rv
);
1011 if (readSize
< (int32_t)size
) {
1012 rv
= NS_ERROR_UNEXPECTED
;
1019 nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding
* binding
, char * buffer
, uint32_t size
)
1021 CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
1022 binding
->mRecord
.HashNumber(), size
));
1024 mozilla::eventtracer::AutoEventTracer
writeDataCacheBlocks(
1025 binding
->mCacheEntry
,
1026 mozilla::eventtracer::eExec
,
1027 mozilla::eventtracer::eDone
,
1028 "net::cache::WriteDataCacheBlocks");
1030 nsresult rv
= NS_OK
;
1032 // determine block file & number of blocks
1033 uint32_t fileIndex
= CalculateFileIndex(size
);
1034 uint32_t blockCount
= 0;
1035 int32_t startBlock
= 0;
1038 // if fileIndex is 0, bad things happen below, which makes gcc 4.7
1039 // complain, but it's not supposed to happen. See bug 854105.
1040 MOZ_ASSERT(fileIndex
);
1042 uint32_t blockSize
= GetBlockSizeForIndex(fileIndex
);
1043 blockCount
= ((size
- 1) / blockSize
) + 1;
1045 rv
= mBlockFile
[fileIndex
- 1].WriteBlocks(buffer
, size
, blockCount
,
1047 if (NS_SUCCEEDED(rv
)) {
1048 IncrementTotalSize(blockCount
, blockSize
);
1052 if (fileIndex
== kNumBlockFiles
)
1059 // update binding and cache map record
1060 binding
->mRecord
.SetDataBlocks(fileIndex
, startBlock
, blockCount
);
1061 if (!binding
->mDoomed
) {
1062 rv
= UpdateRecord(&binding
->mRecord
);
1069 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord
* record
)
1071 nsresult rv1
= DeleteStorage(record
, nsDiskCache::kData
);
1072 nsresult rv2
= DeleteStorage(record
, nsDiskCache::kMetaData
);
1073 return NS_FAILED(rv1
) ? rv1
: rv2
;
1078 nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord
* record
, bool metaData
)
1080 CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record
->HashNumber(),
1083 nsresult rv
= NS_ERROR_UNEXPECTED
;
1084 uint32_t fileIndex
= metaData
? record
->MetaFile() : record
->DataFile();
1085 nsCOMPtr
<nsIFile
> file
;
1087 if (fileIndex
== 0) {
1089 uint32_t sizeK
= metaData
? record
->MetaFileSize() : record
->DataFileSize();
1090 // XXX if sizeK == USHRT_MAX, stat file for actual size
1092 rv
= GetFileForDiskCacheRecord(record
, metaData
, false, getter_AddRefs(file
));
1093 if (NS_SUCCEEDED(rv
)) {
1094 rv
= file
->Remove(false); // false == non-recursive
1096 DecrementTotalSize(sizeK
);
1098 } else if (fileIndex
< (kNumBlockFiles
+ 1)) {
1099 // deallocate blocks
1100 uint32_t startBlock
= metaData
? record
->MetaStartBlock() : record
->DataStartBlock();
1101 uint32_t blockCount
= metaData
? record
->MetaBlockCount() : record
->DataBlockCount();
1103 rv
= mBlockFile
[fileIndex
- 1].DeallocateBlocks(startBlock
, blockCount
);
1104 DecrementTotalSize(blockCount
, GetBlockSizeForIndex(fileIndex
));
1106 if (metaData
) record
->ClearMetaLocation();
1107 else record
->ClearDataLocation();
1114 nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord
* record
,
1119 if (!mCacheDirectory
) return NS_ERROR_NOT_AVAILABLE
;
1121 nsCOMPtr
<nsIFile
> file
;
1122 nsresult rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
1123 if (NS_FAILED(rv
)) return rv
;
1125 uint32_t hash
= record
->HashNumber();
1127 // The file is stored under subdirectories according to the hash number:
1128 // 0x01234567 -> 0/12/
1129 rv
= file
->AppendNative(nsPrintfCString("%X", hash
>> 28));
1130 if (NS_FAILED(rv
)) return rv
;
1131 rv
= file
->AppendNative(nsPrintfCString("%02X", (hash
>> 20) & 0xFF));
1132 if (NS_FAILED(rv
)) return rv
;
1135 if (createPath
&& (NS_FAILED(file
->Exists(&exists
)) || !exists
)) {
1136 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
1137 if (NS_FAILED(rv
)) return rv
;
1140 int16_t generation
= record
->Generation();
1142 // Cut the beginning of the hash that was used in the path
1143 ::sprintf(name
, "%05X%c%02X", hash
& 0xFFFFF, (meta
? 'm' : 'd'),
1145 rv
= file
->AppendNative(nsDependentCString(name
));
1146 if (NS_FAILED(rv
)) return rv
;
1148 NS_IF_ADDREF(*result
= file
);
1154 nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord
* record
,
1159 nsCOMPtr
<nsIFile
> file
;
1160 nsresult rv
= GetFileForDiskCacheRecord(record
,
1163 getter_AddRefs(file
));
1164 if (NS_FAILED(rv
)) return rv
;
1166 NS_IF_ADDREF(*result
= file
);
1172 nsDiskCacheMap::GetBlockFileForIndex(uint32_t index
, nsIFile
** result
)
1174 if (!mCacheDirectory
) return NS_ERROR_NOT_AVAILABLE
;
1176 nsCOMPtr
<nsIFile
> file
;
1177 nsresult rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
1178 if (NS_FAILED(rv
)) return rv
;
1181 ::sprintf(name
, "_CACHE_%03d_", index
+ 1);
1182 rv
= file
->AppendNative(nsDependentCString(name
));
1183 if (NS_FAILED(rv
)) return rv
;
1185 NS_IF_ADDREF(*result
= file
);
1192 nsDiskCacheMap::CalculateFileIndex(uint32_t size
)
1194 // We prefer to use block file with larger block if the wasted space would
1195 // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
1196 // instead of in 4 1K-blocks.
1198 if (size
<= 3 * BLOCK_SIZE_FOR_INDEX(1)) return 1;
1199 if (size
<= 3 * BLOCK_SIZE_FOR_INDEX(2)) return 2;
1200 if (size
<= 4 * BLOCK_SIZE_FOR_INDEX(3)) return 3;
1205 nsDiskCacheMap::EnsureBuffer(uint32_t bufSize
)
1207 if (mBufferSize
< bufSize
) {
1208 char * buf
= (char *)PR_REALLOC(mBuffer
, bufSize
);
1211 return NS_ERROR_OUT_OF_MEMORY
;
1214 mBufferSize
= bufSize
;
1220 nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity
)
1222 // Heuristic 1. average cache entry size is probably around 1KB
1223 // Heuristic 2. we don't want more than 32MB reserved to store the record
1225 const int32_t RECORD_COUNT_LIMIT
= 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord
);
1226 int32_t maxRecordCount
= std::min(int32_t(capacity
), RECORD_COUNT_LIMIT
);
1227 if (mMaxRecordCount
< maxRecordCount
) {
1229 mMaxRecordCount
= maxRecordCount
;
1234 nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
)
1236 size_t usage
= aMallocSizeOf(mRecordArray
);
1238 usage
+= aMallocSizeOf(mBuffer
);
1239 usage
+= aMallocSizeOf(mMapFD
);
1240 usage
+= aMallocSizeOf(mCleanFD
);
1241 usage
+= aMallocSizeOf(mCacheDirectory
);
1242 usage
+= aMallocSizeOf(mCleanCacheTimer
);
1244 for (int i
= 0; i
< kNumBlockFiles
; i
++) {
1245 usage
+= mBlockFile
[i
].SizeOfExcludingThis(aMallocSizeOf
);
1252 nsDiskCacheMap::InitCacheClean(nsIFile
* cacheDirectory
,
1253 nsDiskCache::CorruptCacheInfo
* corruptInfo
,
1254 bool reportCacheCleanTelemetryData
)
1256 // The _CACHE_CLEAN_ file will be used in the future to determine
1257 // if the cache is clean or not.
1258 bool cacheCleanFileExists
= false;
1259 nsCOMPtr
<nsIFile
> cacheCleanFile
;
1260 nsresult rv
= cacheDirectory
->GetParent(getter_AddRefs(cacheCleanFile
));
1261 if (NS_SUCCEEDED(rv
)) {
1262 rv
= cacheCleanFile
->AppendNative(
1263 NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
1264 if (NS_SUCCEEDED(rv
)) {
1265 // Check if the file already exists, if it does, we will later read the
1266 // value and report it to telemetry.
1267 cacheCleanFile
->Exists(&cacheCleanFileExists
);
1270 if (NS_FAILED(rv
)) {
1271 NS_WARNING("Could not build cache clean file path");
1272 *corruptInfo
= nsDiskCache::kCacheCleanFilePathError
;
1276 // Make sure the _CACHE_CLEAN_ file exists
1277 rv
= cacheCleanFile
->OpenNSPRFileDesc(PR_RDWR
| PR_CREATE_FILE
,
1279 if (NS_FAILED(rv
)) {
1280 NS_WARNING("Could not open cache clean file");
1281 *corruptInfo
= nsDiskCache::kCacheCleanOpenFileError
;
1285 if (cacheCleanFileExists
) {
1287 int32_t bytesRead
= PR_Read(mCleanFD
, &clean
, 1);
1288 if (bytesRead
!= 1) {
1289 NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
1290 } else if (reportCacheCleanTelemetryData
) {
1291 Telemetry::Accumulate(Telemetry::DISK_CACHE_REDUCTION_TRIAL
,
1292 clean
== '1' ? 1 : 0);
1296 // Create a timer that will be used to validate the cache
1297 // as long as an activity threshold was met
1298 mCleanCacheTimer
= do_CreateInstance("@mozilla.org/timer;1", &rv
);
1299 if (NS_SUCCEEDED(rv
)) {
1300 mCleanCacheTimer
->SetTarget(nsCacheService::GlobalInstance()->mCacheIOThread
);
1301 rv
= ResetCacheTimer();
1304 if (NS_FAILED(rv
)) {
1305 NS_WARNING("Could not create cache clean timer");
1306 mCleanCacheTimer
= nullptr;
1307 *corruptInfo
= nsDiskCache::kCacheCleanTimerError
;
1315 nsDiskCacheMap::WriteCacheClean(bool clean
)
1317 nsCacheService::AssertOwnsLock();
1319 NS_WARNING("Cache clean file is not open!");
1320 return NS_ERROR_FAILURE
;
1323 CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean
? 1 : 0));
1324 // I'm using a simple '1' or '0' to denote cache clean
1325 // since it can be edited easily by any text editor for testing.
1326 char data
= clean
? '1' : '0';
1327 int32_t filePos
= PR_Seek(mCleanFD
, 0, PR_SEEK_SET
);
1329 NS_WARNING("Could not seek in cache clean file!");
1330 return NS_ERROR_FAILURE
;
1332 int32_t bytesWritten
= PR_Write(mCleanFD
, &data
, 1);
1333 if (bytesWritten
!= 1) {
1334 NS_WARNING("Could not write cache clean file!");
1335 return NS_ERROR_FAILURE
;
1337 PRStatus err
= PR_Sync(mCleanFD
);
1338 if (err
!= PR_SUCCESS
) {
1339 NS_WARNING("Could not flush cache clean file!");
1346 nsDiskCacheMap::InvalidateCache()
1348 nsCacheService::AssertOwnsLock();
1349 CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
1352 if (!mIsDirtyCacheFlushed
) {
1353 rv
= WriteCacheClean(false);
1354 if (NS_FAILED(rv
)) {
1355 Telemetry::Accumulate(Telemetry::DISK_CACHE_INVALIDATION_SUCCESS
, 0);
1359 Telemetry::Accumulate(Telemetry::DISK_CACHE_INVALIDATION_SUCCESS
, 1);
1360 mIsDirtyCacheFlushed
= true;
1363 rv
= ResetCacheTimer();
1364 NS_ENSURE_SUCCESS(rv
, rv
);
1370 nsDiskCacheMap::ResetCacheTimer(int32_t timeout
)
1372 mCleanCacheTimer
->Cancel();
1374 mCleanCacheTimer
->InitWithFuncCallback(RevalidateTimerCallback
,
1376 nsITimer::TYPE_ONE_SHOT
);
1377 NS_ENSURE_SUCCESS(rv
, rv
);
1378 mLastInvalidateTime
= PR_IntervalNow();
1384 nsDiskCacheMap::RevalidateTimerCallback(nsITimer
*aTimer
, void *arg
)
1386 nsCacheServiceAutoLock
lock(LOCK_TELEM(NSDISKCACHEMAP_REVALIDATION
));
1387 if (!nsCacheService::gService
->mDiskDevice
||
1388 !nsCacheService::gService
->mDiskDevice
->Initialized()) {
1392 nsDiskCacheMap
*diskCacheMap
=
1393 &nsCacheService::gService
->mDiskDevice
->mCacheMap
;
1395 // If we have less than kRevalidateCacheTimeout since the last timer was
1396 // issued then another thread called InvalidateCache. This won't catch
1397 // all cases where we wanted to cancel the timer, but under the lock it
1398 // is always OK to revalidate as long as IsCacheInSafeState() returns
1399 // true. We just want to avoid revalidating when we can to reduce IO
1400 // and this check will do that.
1402 PR_IntervalToMilliseconds(PR_IntervalNow() -
1403 diskCacheMap
->mLastInvalidateTime
) +
1404 kRevalidateCacheTimeoutTolerance
;
1405 if (delta
< kRevalidateCacheTimeout
) {
1406 diskCacheMap
->ResetCacheTimer();
1410 nsresult rv
= diskCacheMap
->RevalidateCache();
1411 if (NS_FAILED(rv
)) {
1412 diskCacheMap
->ResetCacheTimer(kRevalidateCacheErrorTimeout
);
1417 nsDiskCacheMap::IsCacheInSafeState()
1419 return nsCacheService::GlobalInstance()->IsDoomListEmpty();
1423 nsDiskCacheMap::RevalidateCache()
1425 CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
1428 if (!IsCacheInSafeState()) {
1429 Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SAFE
, 0);
1430 CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
1431 "cache not in a safe state\n"));
1432 // Normally we would return an error here, but there is a bug where
1433 // the doom list sometimes gets an entry 'stuck' and doens't clear it
1434 // until browser shutdown. So we allow revalidation for the time being
1435 // to get proper telemetry data of how much the cache corruption plan
1438 Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SAFE
, 1);
1441 // We want this after the lock to prove that flushing a file isn't that expensive
1442 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_REVALIDATION
> totalTimer
;
1444 // If telemetry data shows it is worth it, we'll be flushing headers and
1445 // records before flushing the clean cache file.
1447 // Write out the _CACHE_CLEAN_ file with '1'
1448 rv
= WriteCacheClean(true);
1449 if (NS_FAILED(rv
)) {
1450 Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SUCCESS
, 0);
1454 Telemetry::Accumulate(Telemetry::DISK_CACHE_REVALIDATION_SUCCESS
, 1);
1455 mIsDirtyCacheFlushed
= false;