1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/. */
9 #include "mozilla/DebugOnly.h"
12 #include "nsIMemoryReporter.h"
14 // include files for ftruncate (or equivalent)
20 // XXX add necessary include file for ftruncate (or equivalent)
25 #include "private/pprio.h"
27 #include "nsDiskCacheDevice.h"
28 #include "nsDiskCacheEntry.h"
29 #include "nsDiskCacheMap.h"
30 #include "nsDiskCacheStreams.h"
32 #include "nsDiskCache.h"
34 #include "nsCacheService.h"
36 #include "nsDeleteDir.h"
38 #include "nsICacheVisitor.h"
39 #include "nsReadableUtils.h"
40 #include "nsIInputStream.h"
41 #include "nsIOutputStream.h"
43 #include "nsCOMArray.h"
44 #include "nsISimpleEnumerator.h"
46 #include "nsThreadUtils.h"
47 #include "mozilla/MemoryReporting.h"
48 #include "mozilla/Telemetry.h"
50 static const char DISK_CACHE_DEVICE_ID
[] = { "disk" };
51 using namespace mozilla
;
53 class nsDiskCacheDeviceDeactivateEntryEvent
: public Runnable
{
55 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice
* device
,
57 nsDiskCacheBinding
* binding
)
58 : mozilla::Runnable("nsDiskCacheDeviceDeactivateEntryEvent")
66 NS_IMETHOD
Run() override
68 nsCacheServiceAutoLock lock
;
69 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
71 (void) mDevice
->DeactivateEntry_Private(mEntry
, mBinding
);
76 void CancelEvent() { mCanceled
= true; }
80 nsDiskCacheDevice
*mDevice
;
81 nsDiskCacheBinding
*mBinding
;
84 class nsEvictDiskCacheEntriesEvent
: public Runnable
{
86 explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice
* device
)
87 : mozilla::Runnable("nsEvictDiskCacheEntriesEvent")
92 NS_IMETHOD
Run() override
94 nsCacheServiceAutoLock lock
;
95 mDevice
->EvictDiskCacheEntries(mDevice
->mCacheCapacity
);
100 nsDiskCacheDevice
*mDevice
;
103 /******************************************************************************
106 * Helper class for nsDiskCacheDevice.
108 *****************************************************************************/
110 class nsDiskCacheEvictor
: public nsDiskCacheRecordVisitor
113 nsDiskCacheEvictor( nsDiskCacheMap
* cacheMap
,
114 nsDiskCacheBindery
* cacheBindery
,
116 const char * clientID
)
117 : mCacheMap(cacheMap
)
118 , mBindery(cacheBindery
)
119 , mTargetSize(targetSize
)
120 , mClientID(clientID
)
122 mClientIDSize
= clientID
? strlen(clientID
) : 0;
125 virtual int32_t VisitRecord(nsDiskCacheRecord
* mapRecord
) override
;
128 nsDiskCacheMap
* mCacheMap
;
129 nsDiskCacheBindery
* mBindery
;
130 uint32_t mTargetSize
;
131 const char * mClientID
;
132 uint32_t mClientIDSize
;
137 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord
* mapRecord
)
139 if (mCacheMap
->TotalSize() < mTargetSize
)
140 return kStopVisitingRecords
;
143 // we're just evicting records for a specific client
144 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
146 return kVisitNextRecord
; // XXX or delete record?
148 // Compare clientID's without malloc
149 if ((diskEntry
->mKeySize
<= mClientIDSize
) ||
150 (diskEntry
->Key()[mClientIDSize
] != ':') ||
151 (memcmp(diskEntry
->Key(), mClientID
, mClientIDSize
) != 0)) {
152 return kVisitNextRecord
; // clientID doesn't match, skip it
156 nsDiskCacheBinding
* binding
= mBindery
->FindActiveBinding(mapRecord
->HashNumber());
158 // If the entry is pending deactivation, cancel deactivation and doom
160 if (binding
->mDeactivateEvent
) {
161 binding
->mDeactivateEvent
->CancelEvent();
162 binding
->mDeactivateEvent
= nullptr;
164 // We are currently using this entry, so all we can do is doom it.
165 // Since we're enumerating the records, we don't want to call
166 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
167 binding
->mDoomed
= true; // mark binding record as 'deleted'
168 nsCacheService::DoomEntry(binding
->mCacheEntry
);
170 // entry not in use, just delete storage because we're enumerating the records
171 (void) mCacheMap
->DeleteStorage(mapRecord
);
174 return kDeleteRecordAndContinue
; // this will REALLY delete the record
178 /******************************************************************************
179 * nsDiskCacheDeviceInfo
180 *****************************************************************************/
182 class nsDiskCacheDeviceInfo
: public nsICacheDeviceInfo
{
185 NS_DECL_NSICACHEDEVICEINFO
187 explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice
* device
)
193 virtual ~nsDiskCacheDeviceInfo() = default;
195 nsDiskCacheDevice
* mDevice
;
198 NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo
, nsICacheDeviceInfo
)
200 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetDescription(nsACString
& aDescription
)
202 aDescription
.AssignLiteral("Disk cache device");
206 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetUsageReport(nsACString
& aUsageReport
)
210 buffer
.AssignLiteral(" <tr>\n"
211 " <th>Cache Directory:</th>\n"
213 nsCOMPtr
<nsIFile
> cacheDir
;
215 mDevice
->getCacheDirectory(getter_AddRefs(cacheDir
));
216 nsresult rv
= cacheDir
->GetPath(path
);
217 if (NS_SUCCEEDED(rv
)) {
218 AppendUTF16toUTF8(path
, buffer
);
220 buffer
.AppendLiteral("directory unavailable");
222 buffer
.AppendLiteral("</td>\n"
225 aUsageReport
.Assign(buffer
);
229 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount
)
231 NS_ENSURE_ARG_POINTER(aEntryCount
);
232 *aEntryCount
= mDevice
->getEntryCount();
236 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize
)
238 NS_ENSURE_ARG_POINTER(aTotalSize
);
239 // Returned unit's are in bytes
240 *aTotalSize
= mDevice
->getCacheSize() * 1024;
244 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize
)
246 NS_ENSURE_ARG_POINTER(aMaximumSize
);
247 // Returned unit's are in bytes
248 *aMaximumSize
= mDevice
->getCacheCapacity() * 1024;
253 /******************************************************************************
255 *****************************************************************************/
258 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
260 * See http://burtleburtle.net/bob/hash/evahash.html for more information
261 * about this hash function.
263 * This algorithm of this method implies nsDiskCacheRecords will be stored
264 * in a certain order on disk. If the algorithm changes, existing cache
265 * map files may become invalid, and therefore the kCurrentVersion needs
269 static inline void hashmix(uint32_t& a
, uint32_t& b
, uint32_t& c
)
271 a
-= b
; a
-= c
; a
^= (c
>>13);
272 b
-= c
; b
-= a
; b
^= (a
<<8);
273 c
-= a
; c
-= b
; c
^= (b
>>13);
274 a
-= b
; a
-= c
; a
^= (c
>>12);
275 b
-= c
; b
-= a
; b
^= (a
<<16);
276 c
-= a
; c
-= b
; c
^= (b
>>5);
277 a
-= b
; a
-= c
; a
^= (c
>>3);
278 b
-= c
; b
-= a
; b
^= (a
<<10);
279 c
-= a
; c
-= b
; c
^= (b
>>15);
283 nsDiskCache::Hash(const char * key
, PLDHashNumber initval
)
285 const uint8_t *k
= reinterpret_cast<const uint8_t*>(key
);
286 uint32_t a
, b
, c
, len
, length
;
288 length
= strlen(key
);
289 /* Set up the internal state */
291 a
= b
= 0x9e3779b9; /* the golden ratio; an arbitrary value */
292 c
= initval
; /* variable initialization of internal state */
294 /*---------------------------------------- handle most of the key */
297 a
+= k
[0] + (uint32_t(k
[1])<<8) + (uint32_t(k
[2])<<16) + (uint32_t(k
[3])<<24);
298 b
+= k
[4] + (uint32_t(k
[5])<<8) + (uint32_t(k
[6])<<16) + (uint32_t(k
[7])<<24);
299 c
+= k
[8] + (uint32_t(k
[9])<<8) + (uint32_t(k
[10])<<16) + (uint32_t(k
[11])<<24);
304 /*------------------------------------- handle the last 11 bytes */
306 switch(len
) { /* all the case statements fall through */
307 case 11: c
+= (uint32_t(k
[10])<<24); MOZ_FALLTHROUGH
;
308 case 10: c
+= (uint32_t(k
[9])<<16); MOZ_FALLTHROUGH
;
309 case 9 : c
+= (uint32_t(k
[8])<<8); MOZ_FALLTHROUGH
;
310 /* the low-order byte of c is reserved for the length */
311 case 8 : b
+= (uint32_t(k
[7])<<24); MOZ_FALLTHROUGH
;
312 case 7 : b
+= (uint32_t(k
[6])<<16); MOZ_FALLTHROUGH
;
313 case 6 : b
+= (uint32_t(k
[5])<<8); MOZ_FALLTHROUGH
;
314 case 5 : b
+= k
[4]; MOZ_FALLTHROUGH
;
315 case 4 : a
+= (uint32_t(k
[3])<<24); MOZ_FALLTHROUGH
;
316 case 3 : a
+= (uint32_t(k
[2])<<16); MOZ_FALLTHROUGH
;
317 case 2 : a
+= (uint32_t(k
[1])<<8); MOZ_FALLTHROUGH
;
319 /* case 0: nothing left to add */
327 nsDiskCache::Truncate(PRFileDesc
* fd
, uint32_t newEOF
)
329 // use modified SetEOF from nsFileStreams::SetEOF()
332 if (ftruncate(PR_FileDesc2NativeHandle(fd
), newEOF
) != 0) {
333 NS_ERROR("ftruncate failed");
334 return NS_ERROR_FAILURE
;
337 #elif defined(XP_WIN)
338 int32_t cnt
= PR_Seek(fd
, newEOF
, PR_SEEK_SET
);
339 if (cnt
== -1) return NS_ERROR_FAILURE
;
340 if (!SetEndOfFile((HANDLE
) PR_FileDesc2NativeHandle(fd
))) {
341 NS_ERROR("SetEndOfFile failed");
342 return NS_ERROR_FAILURE
;
346 // add implementations for other platforms here
352 /******************************************************************************
354 *****************************************************************************/
356 nsDiskCacheDevice::nsDiskCacheDevice()
358 , mMaxEntrySize(-1) // -1 means "no limit"
359 , mInitialized(false)
360 , mClearingDiskCache(false)
364 nsDiskCacheDevice::~nsDiskCacheDevice()
371 * methods of nsCacheDevice
374 nsDiskCacheDevice::Init()
379 NS_ERROR("Disk cache already initialized!");
380 return NS_ERROR_UNEXPECTED
;
383 if (!mCacheDirectory
)
384 return NS_ERROR_FAILURE
;
389 rv
= OpenDiskCache();
391 (void) mCacheMap
.Close(false);
401 * NOTE: called while holding the cache service lock
404 nsDiskCacheDevice::Shutdown()
406 nsCacheService::AssertOwnsLock();
408 nsresult rv
= Shutdown_Private(true);
417 nsDiskCacheDevice::Shutdown_Private(bool flush
)
419 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush
));
422 // check cache limits in case we need to evict.
423 EvictDiskCacheEntries(mCacheCapacity
);
425 // At this point there may be a number of pending cache-requests on the
426 // cache-io thread. Wait for all these to run before we wipe out our
427 // datastructures (see bug #620660)
428 (void) nsCacheService::SyncWithCacheIOThread();
430 // write out persistent information about the cache.
431 (void) mCacheMap
.Close(flush
);
435 mInitialized
= false;
443 nsDiskCacheDevice::GetDeviceID()
445 return DISK_CACHE_DEVICE_ID
;
451 * cases: key not in disk cache, hash number free
452 * key not in disk cache, hash number used
455 * NOTE: called while holding the cache service lock
458 nsDiskCacheDevice::FindEntry(nsCString
* key
, bool *collision
)
460 Telemetry::AutoTimer
<Telemetry::CACHE_DISK_SEARCH_2
> timer
;
461 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
462 if (mClearingDiskCache
) return nullptr;
463 nsDiskCacheRecord record
;
464 nsDiskCacheBinding
* binding
= nullptr;
465 PLDHashNumber hashNumber
= nsDiskCache::Hash(key
->get());
469 binding
= mBindery
.FindActiveBinding(hashNumber
);
471 if (!binding
->mCacheEntry
->Key()->Equals(*key
)) {
475 if (binding
->mDeactivateEvent
) {
476 binding
->mDeactivateEvent
->CancelEvent();
477 binding
->mDeactivateEvent
= nullptr;
478 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
479 "req-key=%s entry-key=%s\n",
480 binding
->mCacheEntry
, key
->get(),
481 binding
->mCacheEntry
->Key()->get()));
483 return binding
->mCacheEntry
; // just return this one, observing that
484 // FindActiveBinding() does not return
485 // bindings to doomed entries
490 // lookup hash number in cache map
491 nsresult rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
492 if (NS_FAILED(rv
)) return nullptr; // XXX log error?
494 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
495 if (!diskEntry
) return nullptr;
497 // compare key to be sure
498 if (!key
->Equals(diskEntry
->Key())) {
503 nsCacheEntry
* entry
= diskEntry
->CreateCacheEntry(this);
505 binding
= mBindery
.CreateBinding(entry
, &record
);
513 (void) mCacheMap
.DeleteStorage(&record
);
514 (void) mCacheMap
.DeleteRecord(&record
);
522 * NOTE: called while holding the cache service lock
525 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry
* entry
)
527 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
528 if (!IsValidBinding(binding
))
529 return NS_ERROR_UNEXPECTED
;
531 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
532 entry
, binding
->mRecord
.HashNumber()));
534 nsDiskCacheDeviceDeactivateEntryEvent
*event
=
535 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry
, binding
);
537 // ensure we can cancel the event via the binding later if necessary
538 binding
->mDeactivateEvent
= event
;
540 DebugOnly
<nsresult
> rv
= nsCacheService::DispatchToCacheIOThread(event
);
541 NS_ASSERTION(NS_SUCCEEDED(rv
), "DeactivateEntry: Failed dispatching "
542 "deactivation event");
547 * NOTE: called while holding the cache service lock
550 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry
* entry
,
551 nsDiskCacheBinding
* binding
)
554 if (entry
->IsDoomed()) {
555 // delete data, entry, record from disk for entry
556 rv
= mCacheMap
.DeleteStorage(&binding
->mRecord
);
559 // save stuff to disk for entry
560 rv
= mCacheMap
.WriteDiskCacheEntry(binding
);
562 // clean up as best we can
563 (void) mCacheMap
.DeleteStorage(&binding
->mRecord
);
564 (void) mCacheMap
.DeleteRecord(&binding
->mRecord
);
565 binding
->mDoomed
= true; // record is no longer in cache map
569 mBindery
.RemoveBinding(binding
); // extract binding from collision detection stuff
570 delete entry
; // which will release binding
577 * no hash number collision -> no problem
579 * record not active -> evict, no problem
581 * record is already doomed -> record shouldn't have been in map, no problem
582 * record is not doomed -> doom, and replace record in map
584 * walk matching hashnumber list to find lowest generation number
585 * take generation number from other (data/meta) location,
586 * or walk active list
588 * NOTE: called while holding the cache service lock
591 nsDiskCacheDevice::BindEntry(nsCacheEntry
* entry
)
593 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
594 if (mClearingDiskCache
) return NS_ERROR_NOT_AVAILABLE
;
596 nsDiskCacheRecord record
, oldRecord
;
597 nsDiskCacheBinding
*binding
;
598 PLDHashNumber hashNumber
= nsDiskCache::Hash(entry
->Key()->get());
600 // Find out if there is already an active binding for this hash. If yes it
601 // should have another key since BindEntry() shouldn't be called twice for
602 // the same entry. Doom the old entry, the new one will get another
603 // generation number so files won't collide.
604 binding
= mBindery
.FindActiveBinding(hashNumber
);
606 NS_ASSERTION(!binding
->mCacheEntry
->Key()->Equals(*entry
->Key()),
607 "BindEntry called for already bound entry!");
608 // If the entry is pending deactivation, cancel deactivation
609 if (binding
->mDeactivateEvent
) {
610 binding
->mDeactivateEvent
->CancelEvent();
611 binding
->mDeactivateEvent
= nullptr;
613 nsCacheService::DoomEntry(binding
->mCacheEntry
);
617 // Lookup hash number in cache map. There can be a colliding inactive entry.
618 // See bug #321361 comment 21 for the scenario. If there is such entry,
620 rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
621 if (NS_SUCCEEDED(rv
)) {
622 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
624 // compare key to be sure
625 if (!entry
->Key()->Equals(diskEntry
->Key())) {
626 mCacheMap
.DeleteStorage(&record
);
627 rv
= mCacheMap
.DeleteRecord(&record
);
628 if (NS_FAILED(rv
)) return rv
;
631 record
= nsDiskCacheRecord();
634 // create a new record for this entry
635 record
.SetHashNumber(nsDiskCache::Hash(entry
->Key()->get()));
636 record
.SetEvictionRank(ULONG_MAX
- SecondsFromPRTime(PR_Now()));
638 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
639 entry
, record
.HashNumber()));
641 if (!entry
->IsDoomed()) {
642 // if entry isn't doomed, add it to the cache map
643 rv
= mCacheMap
.AddRecord(&record
, &oldRecord
); // deletes old record, if any
644 if (NS_FAILED(rv
)) return rv
;
646 uint32_t oldHashNumber
= oldRecord
.HashNumber();
648 // gotta evict this one first
649 nsDiskCacheBinding
* oldBinding
= mBindery
.FindActiveBinding(oldHashNumber
);
651 // XXX if debug : compare keys for hashNumber collision
653 if (!oldBinding
->mCacheEntry
->IsDoomed()) {
654 // If the old entry is pending deactivation, cancel deactivation
655 if (oldBinding
->mDeactivateEvent
) {
656 oldBinding
->mDeactivateEvent
->CancelEvent();
657 oldBinding
->mDeactivateEvent
= nullptr;
659 // we've got a live one!
660 nsCacheService::DoomEntry(oldBinding
->mCacheEntry
);
661 // storage will be delete when oldBinding->mCacheEntry is Deactivated
665 // XXX if debug : compare keys for hashNumber collision
666 rv
= mCacheMap
.DeleteStorage(&oldRecord
);
667 if (NS_FAILED(rv
)) return rv
; // XXX delete record we just added?
672 // Make sure this entry has its associated nsDiskCacheBinding attached.
673 binding
= mBindery
.CreateBinding(entry
, &record
);
674 NS_ASSERTION(binding
, "nsDiskCacheDevice::BindEntry");
675 if (!binding
) return NS_ERROR_OUT_OF_MEMORY
;
676 NS_ASSERTION(binding
->mRecord
.ValidRecord(), "bad cache map record");
683 * NOTE: called while holding the cache service lock
686 nsDiskCacheDevice::DoomEntry(nsCacheEntry
* entry
)
688 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry
));
690 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
691 NS_ASSERTION(binding
, "DoomEntry: binding == nullptr");
695 if (!binding
->mDoomed
) {
696 // so it can't be seen by FindEntry() ever again.
700 mCacheMap
.DeleteRecord(&binding
->mRecord
);
701 NS_ASSERTION(NS_SUCCEEDED(rv
),"DeleteRecord failed.");
702 binding
->mDoomed
= true; // record in no longer in cache map
708 * NOTE: called while holding the cache service lock
711 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry
* entry
,
712 nsCacheAccessMode mode
,
714 nsIInputStream
** result
)
716 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
717 entry
, mode
, offset
));
719 NS_ENSURE_ARG_POINTER(entry
);
720 NS_ENSURE_ARG_POINTER(result
);
723 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
724 if (!IsValidBinding(binding
))
725 return NS_ERROR_UNEXPECTED
;
727 NS_ASSERTION(binding
->mCacheEntry
== entry
, "binding & entry don't point to each other");
729 rv
= binding
->EnsureStreamIO();
730 if (NS_FAILED(rv
)) return rv
;
732 return binding
->mStreamIO
->GetInputStream(offset
, result
);
737 * NOTE: called while holding the cache service lock
740 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry
* entry
,
741 nsCacheAccessMode mode
,
743 nsIOutputStream
** result
)
745 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
746 entry
, mode
, offset
));
748 NS_ENSURE_ARG_POINTER(entry
);
749 NS_ENSURE_ARG_POINTER(result
);
752 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
753 if (!IsValidBinding(binding
))
754 return NS_ERROR_UNEXPECTED
;
756 NS_ASSERTION(binding
->mCacheEntry
== entry
, "binding & entry don't point to each other");
758 rv
= binding
->EnsureStreamIO();
759 if (NS_FAILED(rv
)) return rv
;
761 return binding
->mStreamIO
->GetOutputStream(offset
, result
);
766 * NOTE: called while holding the cache service lock
769 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry
* entry
,
772 NS_ENSURE_ARG_POINTER(result
);
777 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
778 if (!IsValidBinding(binding
))
779 return NS_ERROR_UNEXPECTED
;
781 // check/set binding->mRecord for separate file, sync w/mCacheMap
782 if (binding
->mRecord
.DataLocationInitialized()) {
783 if (binding
->mRecord
.DataFile() != 0)
784 return NS_ERROR_NOT_AVAILABLE
; // data not stored as separate file
786 NS_ASSERTION(binding
->mRecord
.DataFileGeneration() == binding
->mGeneration
, "error generations out of sync");
788 binding
->mRecord
.SetDataFileGeneration(binding
->mGeneration
);
789 binding
->mRecord
.SetDataFileSize(0); // 1k minimum
790 if (!binding
->mDoomed
) {
791 // record stored in cache map, so update it
792 rv
= mCacheMap
.UpdateRecord(&binding
->mRecord
);
793 if (NS_FAILED(rv
)) return rv
;
797 nsCOMPtr
<nsIFile
> file
;
798 rv
= mCacheMap
.GetFileForDiskCacheRecord(&binding
->mRecord
,
801 getter_AddRefs(file
));
802 if (NS_FAILED(rv
)) return rv
;
804 NS_IF_ADDREF(*result
= file
);
810 * This routine will get called every time an open descriptor is written to.
812 * NOTE: called while holding the cache service lock
815 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry
* entry
, int32_t deltaSize
)
817 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
820 // If passed a negative value, then there's nothing to do.
824 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
825 if (!IsValidBinding(binding
))
826 return NS_ERROR_UNEXPECTED
;
828 NS_ASSERTION(binding
->mRecord
.ValidRecord(), "bad record");
830 uint32_t newSize
= entry
->DataSize() + deltaSize
;
831 uint32_t newSizeK
= ((newSize
+ 0x3FF) >> 10);
833 // If the new size is larger than max. file size or larger than
834 // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
835 if (EntryIsTooBig(newSize
)) {
839 nsCacheService::DoomEntry(entry
);
840 NS_ASSERTION(NS_SUCCEEDED(rv
),"DoomEntry() failed.");
841 return NS_ERROR_ABORT
;
844 uint32_t sizeK
= ((entry
->DataSize() + 0x03FF) >> 10); // round up to next 1k
846 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
847 // the target capacity should be calculated the same way.
848 if (sizeK
> kMaxDataSizeK
) sizeK
= kMaxDataSizeK
;
849 if (newSizeK
> kMaxDataSizeK
) newSizeK
= kMaxDataSizeK
;
851 // pre-evict entries to make space for new data
852 uint32_t targetCapacity
= mCacheCapacity
> (newSizeK
- sizeK
)
853 ? mCacheCapacity
- (newSizeK
- sizeK
)
855 EvictDiskCacheEntries(targetCapacity
);
861 /******************************************************************************
863 *****************************************************************************/
864 class EntryInfoVisitor
: public nsDiskCacheRecordVisitor
867 EntryInfoVisitor(nsDiskCacheMap
* cacheMap
,
868 nsICacheVisitor
* visitor
)
869 : mCacheMap(cacheMap
)
873 virtual int32_t VisitRecord(nsDiskCacheRecord
* mapRecord
) override
875 // XXX optimization: do we have this record in memory?
877 // read in the entry (metadata)
878 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
880 return kVisitNextRecord
;
883 // create nsICacheEntryInfo
884 nsDiskCacheEntryInfo
* entryInfo
= new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID
, diskEntry
);
886 return kStopVisitingRecords
;
888 nsCOMPtr
<nsICacheEntryInfo
> ref(entryInfo
);
891 (void)mVisitor
->VisitEntry(DISK_CACHE_DEVICE_ID
, entryInfo
, &keepGoing
);
892 return keepGoing
? kVisitNextRecord
: kStopVisitingRecords
;
896 nsDiskCacheMap
* mCacheMap
;
897 nsICacheVisitor
* mVisitor
;
902 nsDiskCacheDevice::Visit(nsICacheVisitor
* visitor
)
904 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
905 nsDiskCacheDeviceInfo
* deviceInfo
= new nsDiskCacheDeviceInfo(this);
906 nsCOMPtr
<nsICacheDeviceInfo
> ref(deviceInfo
);
909 nsresult rv
= visitor
->VisitDevice(DISK_CACHE_DEVICE_ID
, deviceInfo
, &keepGoing
);
910 if (NS_FAILED(rv
)) return rv
;
913 EntryInfoVisitor
infoVisitor(&mCacheMap
, visitor
);
914 return mCacheMap
.VisitRecords(&infoVisitor
);
920 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
922 nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize
)
924 if (mMaxEntrySize
== -1) // no limit
925 return entrySize
> (static_cast<int64_t>(mCacheCapacity
) * 1024 / 8);
927 return entrySize
> mMaxEntrySize
||
928 entrySize
> (static_cast<int64_t>(mCacheCapacity
) * 1024 / 8);
932 nsDiskCacheDevice::EvictEntries(const char * clientID
)
934 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID
));
936 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
939 if (clientID
== nullptr) {
940 // we're clearing the entire disk cache
941 rv
= ClearDiskCache();
942 if (rv
!= NS_ERROR_CACHE_IN_USE
)
946 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, 0, clientID
);
947 rv
= mCacheMap
.VisitRecords(&evictor
);
949 if (clientID
== nullptr) // we tried to clear the entire cache
950 rv
= mCacheMap
.Trim(); // so trim cache block files (if possible)
960 nsDiskCacheDevice::OpenDiskCache()
962 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_OPEN
> timer
;
963 // if we don't have a cache directory, create one and open it
965 nsresult rv
= mCacheDirectory
->Exists(&exists
);
970 // Try opening cache map file.
971 nsDiskCache::CorruptCacheInfo corruptInfo
;
972 rv
= mCacheMap
.Open(mCacheDirectory
, &corruptInfo
);
974 if (rv
== NS_ERROR_ALREADY_INITIALIZED
) {
975 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
976 } else if (NS_FAILED(rv
)) {
977 // Consider cache corrupt: delete it
978 // delay delete by 1 minute to avoid IO thrash at startup
979 rv
= nsDeleteDir::DeleteDir(mCacheDirectory
, true, 60000);
986 // if we don't have a cache directory, create one and open it
988 nsCacheService::MarkStartingFresh();
989 rv
= mCacheDirectory
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
990 CACHE_LOG_PATH(LogLevel::Info
, "\ncreate cache directory: %s\n", mCacheDirectory
);
991 CACHE_LOG_INFO(("mCacheDirectory->Create() = %" PRIx32
"\n",
992 static_cast<uint32_t>(rv
)));
996 // reopen the cache map
997 nsDiskCache::CorruptCacheInfo corruptInfo
;
998 rv
= mCacheMap
.Open(mCacheDirectory
, &corruptInfo
);
1008 nsDiskCacheDevice::ClearDiskCache()
1010 if (mBindery
.ActiveBindings())
1011 return NS_ERROR_CACHE_IN_USE
;
1013 mClearingDiskCache
= true;
1015 nsresult rv
= Shutdown_Private(false); // false: don't bother flushing
1019 mClearingDiskCache
= false;
1021 // If the disk cache directory is already gone, then it's not an error if
1022 // we fail to delete it ;-)
1023 rv
= nsDeleteDir::DeleteDir(mCacheDirectory
, true);
1024 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
)
1032 nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity
)
1034 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1037 NS_ASSERTION(targetCapacity
> 0, "oops");
1039 if (mCacheMap
.TotalSize() < targetCapacity
)
1042 // targetCapacity is in KiB's
1043 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, targetCapacity
, nullptr);
1044 return mCacheMap
.EvictRecords(&evictor
);
1053 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile
* parentDir
)
1058 if (Initialized()) {
1059 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1064 mCacheDirectory
= nullptr;
1068 // ensure parent directory exists
1069 rv
= parentDir
->Exists(&exists
);
1070 if (NS_SUCCEEDED(rv
) && !exists
)
1071 rv
= parentDir
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
1072 if (NS_FAILED(rv
)) return;
1074 // ensure cache directory exists
1075 nsCOMPtr
<nsIFile
> directory
;
1077 rv
= parentDir
->Clone(getter_AddRefs(directory
));
1078 if (NS_FAILED(rv
)) return;
1079 rv
= directory
->AppendNative(NS_LITERAL_CSTRING("Cache"));
1080 if (NS_FAILED(rv
)) return;
1082 mCacheDirectory
= directory
;
1087 nsDiskCacheDevice::getCacheDirectory(nsIFile
** result
)
1089 *result
= mCacheDirectory
;
1090 NS_IF_ADDREF(*result
);
1095 * NOTE: called while holding the cache service lock
1098 nsDiskCacheDevice::SetCapacity(uint32_t capacity
)
1101 mCacheCapacity
= capacity
;
1102 if (Initialized()) {
1103 if (NS_IsMainThread()) {
1104 // Do not evict entries on the main thread
1105 nsCacheService::DispatchToCacheIOThread(
1106 new nsEvictDiskCacheEntriesEvent(this));
1108 // start evicting entries if the new size is smaller!
1109 EvictDiskCacheEntries(mCacheCapacity
);
1112 // Let cache map know of the new capacity
1113 mCacheMap
.NotifyCapacityChange(capacity
);
1117 uint32_t nsDiskCacheDevice::getCacheCapacity()
1119 return mCacheCapacity
;
1123 uint32_t nsDiskCacheDevice::getCacheSize()
1125 return mCacheMap
.TotalSize();
1129 uint32_t nsDiskCacheDevice::getEntryCount()
1131 return mCacheMap
.EntryCount();
1135 nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes
)
1137 // Internal units are bytes. Changing this only takes effect *after* the
1138 // change and has no consequences for existing cache-entries
1139 if (maxSizeInKilobytes
>= 0)
1140 mMaxEntrySize
= maxSizeInKilobytes
* 1024;
1146 nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
)
1148 size_t usage
= aMallocSizeOf(this);
1150 usage
+= mCacheMap
.SizeOfExcludingThis(aMallocSizeOf
);
1151 usage
+= mBindery
.SizeOfExcludingThis(aMallocSizeOf
);