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 nsRunnable
{
55 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice
*device
,
57 nsDiskCacheBinding
* binding
)
67 nsCacheServiceAutoLock
lock(LOCK_TELEM(NSDISKCACHEDEVICEDEACTIVATEENTRYEVENT_RUN
));
69 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
72 (void) mDevice
->DeactivateEntry_Private(mEntry
, mBinding
);
77 void CancelEvent() { mCanceled
= true; }
81 nsDiskCacheDevice
*mDevice
;
82 nsDiskCacheBinding
*mBinding
;
85 class nsEvictDiskCacheEntriesEvent
: public nsRunnable
{
87 explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice
*device
)
92 nsCacheServiceAutoLock
lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN
));
93 mDevice
->EvictDiskCacheEntries(mDevice
->mCacheCapacity
);
98 nsDiskCacheDevice
*mDevice
;
101 /******************************************************************************
104 * Helper class for nsDiskCacheDevice.
106 *****************************************************************************/
108 class nsDiskCacheEvictor
: public nsDiskCacheRecordVisitor
111 nsDiskCacheEvictor( nsDiskCacheMap
* cacheMap
,
112 nsDiskCacheBindery
* cacheBindery
,
114 const char * clientID
)
115 : mCacheMap(cacheMap
)
116 , mBindery(cacheBindery
)
117 , mTargetSize(targetSize
)
118 , mClientID(clientID
)
120 mClientIDSize
= clientID
? strlen(clientID
) : 0;
123 virtual int32_t VisitRecord(nsDiskCacheRecord
* mapRecord
);
126 nsDiskCacheMap
* mCacheMap
;
127 nsDiskCacheBindery
* mBindery
;
128 uint32_t mTargetSize
;
129 const char * mClientID
;
130 uint32_t mClientIDSize
;
135 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord
* mapRecord
)
137 if (mCacheMap
->TotalSize() < mTargetSize
)
138 return kStopVisitingRecords
;
141 // we're just evicting records for a specific client
142 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
144 return kVisitNextRecord
; // XXX or delete record?
146 // Compare clientID's without malloc
147 if ((diskEntry
->mKeySize
<= mClientIDSize
) ||
148 (diskEntry
->Key()[mClientIDSize
] != ':') ||
149 (memcmp(diskEntry
->Key(), mClientID
, mClientIDSize
) != 0)) {
150 return kVisitNextRecord
; // clientID doesn't match, skip it
154 nsDiskCacheBinding
* binding
= mBindery
->FindActiveBinding(mapRecord
->HashNumber());
156 // If the entry is pending deactivation, cancel deactivation and doom
158 if (binding
->mDeactivateEvent
) {
159 binding
->mDeactivateEvent
->CancelEvent();
160 binding
->mDeactivateEvent
= nullptr;
162 // We are currently using this entry, so all we can do is doom it.
163 // Since we're enumerating the records, we don't want to call
164 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
165 binding
->mDoomed
= true; // mark binding record as 'deleted'
166 nsCacheService::DoomEntry(binding
->mCacheEntry
);
168 // entry not in use, just delete storage because we're enumerating the records
169 (void) mCacheMap
->DeleteStorage(mapRecord
);
172 return kDeleteRecordAndContinue
; // this will REALLY delete the record
176 /******************************************************************************
177 * nsDiskCacheDeviceInfo
178 *****************************************************************************/
180 class nsDiskCacheDeviceInfo
: public nsICacheDeviceInfo
{
183 NS_DECL_NSICACHEDEVICEINFO
185 explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice
* device
)
191 virtual ~nsDiskCacheDeviceInfo() {}
193 nsDiskCacheDevice
* mDevice
;
196 NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo
, nsICacheDeviceInfo
)
198 /* readonly attribute string description; */
199 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetDescription(char ** aDescription
)
201 NS_ENSURE_ARG_POINTER(aDescription
);
202 *aDescription
= NS_strdup("Disk cache device");
203 return *aDescription
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
206 /* readonly attribute string usageReport; */
207 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport
)
209 NS_ENSURE_ARG_POINTER(usageReport
);
212 buffer
.AssignLiteral(" <tr>\n"
213 " <th>Cache Directory:</th>\n"
215 nsCOMPtr
<nsIFile
> cacheDir
;
217 mDevice
->getCacheDirectory(getter_AddRefs(cacheDir
));
218 nsresult rv
= cacheDir
->GetPath(path
);
219 if (NS_SUCCEEDED(rv
)) {
220 AppendUTF16toUTF8(path
, buffer
);
222 buffer
.AppendLiteral("directory unavailable");
224 buffer
.AppendLiteral("</td>\n"
227 *usageReport
= ToNewCString(buffer
);
228 if (!*usageReport
) return NS_ERROR_OUT_OF_MEMORY
;
233 /* readonly attribute unsigned long entryCount; */
234 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount
)
236 NS_ENSURE_ARG_POINTER(aEntryCount
);
237 *aEntryCount
= mDevice
->getEntryCount();
241 /* readonly attribute unsigned long totalSize; */
242 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize
)
244 NS_ENSURE_ARG_POINTER(aTotalSize
);
245 // Returned unit's are in bytes
246 *aTotalSize
= mDevice
->getCacheSize() * 1024;
250 /* readonly attribute unsigned long maximumSize; */
251 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize
)
253 NS_ENSURE_ARG_POINTER(aMaximumSize
);
254 // Returned unit's are in bytes
255 *aMaximumSize
= mDevice
->getCacheCapacity() * 1024;
260 /******************************************************************************
262 *****************************************************************************/
265 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
267 * See http://burtleburtle.net/bob/hash/evahash.html for more information
268 * about this hash function.
270 * This algorithm of this method implies nsDiskCacheRecords will be stored
271 * in a certain order on disk. If the algorithm changes, existing cache
272 * map files may become invalid, and therefore the kCurrentVersion needs
276 static inline void hashmix(uint32_t& a
, uint32_t& b
, uint32_t& c
)
278 a
-= b
; a
-= c
; a
^= (c
>>13);
279 b
-= c
; b
-= a
; b
^= (a
<<8);
280 c
-= a
; c
-= b
; c
^= (b
>>13);
281 a
-= b
; a
-= c
; a
^= (c
>>12);
282 b
-= c
; b
-= a
; b
^= (a
<<16);
283 c
-= a
; c
-= b
; c
^= (b
>>5);
284 a
-= b
; a
-= c
; a
^= (c
>>3);
285 b
-= c
; b
-= a
; b
^= (a
<<10);
286 c
-= a
; c
-= b
; c
^= (b
>>15);
290 nsDiskCache::Hash(const char * key
, PLDHashNumber initval
)
292 const uint8_t *k
= reinterpret_cast<const uint8_t*>(key
);
293 uint32_t a
, b
, c
, len
, length
;
295 length
= strlen(key
);
296 /* Set up the internal state */
298 a
= b
= 0x9e3779b9; /* the golden ratio; an arbitrary value */
299 c
= initval
; /* variable initialization of internal state */
301 /*---------------------------------------- handle most of the key */
304 a
+= k
[0] + (uint32_t(k
[1])<<8) + (uint32_t(k
[2])<<16) + (uint32_t(k
[3])<<24);
305 b
+= k
[4] + (uint32_t(k
[5])<<8) + (uint32_t(k
[6])<<16) + (uint32_t(k
[7])<<24);
306 c
+= k
[8] + (uint32_t(k
[9])<<8) + (uint32_t(k
[10])<<16) + (uint32_t(k
[11])<<24);
311 /*------------------------------------- handle the last 11 bytes */
313 switch(len
) { /* all the case statements fall through */
314 case 11: c
+= (uint32_t(k
[10])<<24);
315 case 10: c
+= (uint32_t(k
[9])<<16);
316 case 9 : c
+= (uint32_t(k
[8])<<8);
317 /* the low-order byte of c is reserved for the length */
318 case 8 : b
+= (uint32_t(k
[7])<<24);
319 case 7 : b
+= (uint32_t(k
[6])<<16);
320 case 6 : b
+= (uint32_t(k
[5])<<8);
322 case 4 : a
+= (uint32_t(k
[3])<<24);
323 case 3 : a
+= (uint32_t(k
[2])<<16);
324 case 2 : a
+= (uint32_t(k
[1])<<8);
326 /* case 0: nothing left to add */
334 nsDiskCache::Truncate(PRFileDesc
* fd
, uint32_t newEOF
)
336 // use modified SetEOF from nsFileStreams::SetEOF()
339 if (ftruncate(PR_FileDesc2NativeHandle(fd
), newEOF
) != 0) {
340 NS_ERROR("ftruncate failed");
341 return NS_ERROR_FAILURE
;
344 #elif defined(XP_WIN)
345 int32_t cnt
= PR_Seek(fd
, newEOF
, PR_SEEK_SET
);
346 if (cnt
== -1) return NS_ERROR_FAILURE
;
347 if (!SetEndOfFile((HANDLE
) PR_FileDesc2NativeHandle(fd
))) {
348 NS_ERROR("SetEndOfFile failed");
349 return NS_ERROR_FAILURE
;
353 // add implementations for other platforms here
359 /******************************************************************************
361 *****************************************************************************/
363 nsDiskCacheDevice::nsDiskCacheDevice()
365 , mMaxEntrySize(-1) // -1 means "no limit"
366 , mInitialized(false)
367 , mClearingDiskCache(false)
371 nsDiskCacheDevice::~nsDiskCacheDevice()
378 * methods of nsCacheDevice
381 nsDiskCacheDevice::Init()
386 NS_ERROR("Disk cache already initialized!");
387 return NS_ERROR_UNEXPECTED
;
390 if (!mCacheDirectory
)
391 return NS_ERROR_FAILURE
;
393 rv
= mBindery
.Init();
398 rv
= OpenDiskCache();
400 (void) mCacheMap
.Close(false);
410 * NOTE: called while holding the cache service lock
413 nsDiskCacheDevice::Shutdown()
415 nsCacheService::AssertOwnsLock();
417 nsresult rv
= Shutdown_Private(true);
426 nsDiskCacheDevice::Shutdown_Private(bool flush
)
428 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush
));
431 // check cache limits in case we need to evict.
432 EvictDiskCacheEntries(mCacheCapacity
);
434 // At this point there may be a number of pending cache-requests on the
435 // cache-io thread. Wait for all these to run before we wipe out our
436 // datastructures (see bug #620660)
437 (void) nsCacheService::SyncWithCacheIOThread();
439 // write out persistent information about the cache.
440 (void) mCacheMap
.Close(flush
);
444 mInitialized
= false;
452 nsDiskCacheDevice::GetDeviceID()
454 return DISK_CACHE_DEVICE_ID
;
460 * cases: key not in disk cache, hash number free
461 * key not in disk cache, hash number used
464 * NOTE: called while holding the cache service lock
467 nsDiskCacheDevice::FindEntry(nsCString
* key
, bool *collision
)
469 Telemetry::AutoTimer
<Telemetry::CACHE_DISK_SEARCH_2
> timer
;
470 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
471 if (mClearingDiskCache
) return nullptr;
472 nsDiskCacheRecord record
;
473 nsDiskCacheBinding
* binding
= nullptr;
474 PLDHashNumber hashNumber
= nsDiskCache::Hash(key
->get());
478 binding
= mBindery
.FindActiveBinding(hashNumber
);
479 if (binding
&& !binding
->mCacheEntry
->Key()->Equals(*key
)) {
482 } else if (binding
&& binding
->mDeactivateEvent
) {
483 binding
->mDeactivateEvent
->CancelEvent();
484 binding
->mDeactivateEvent
= nullptr;
485 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
486 "req-key=%s entry-key=%s\n",
487 binding
->mCacheEntry
, key
, binding
->mCacheEntry
->Key()));
489 return binding
->mCacheEntry
; // just return this one, observing that
490 // FindActiveBinding() does not return
491 // bindings to doomed entries
495 // lookup hash number in cache map
496 nsresult rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
497 if (NS_FAILED(rv
)) return nullptr; // XXX log error?
499 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
500 if (!diskEntry
) return nullptr;
502 // compare key to be sure
503 if (!key
->Equals(diskEntry
->Key())) {
508 nsCacheEntry
* entry
= diskEntry
->CreateCacheEntry(this);
510 binding
= mBindery
.CreateBinding(entry
, &record
);
518 (void) mCacheMap
.DeleteStorage(&record
);
519 (void) mCacheMap
.DeleteRecord(&record
);
527 * NOTE: called while holding the cache service lock
530 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry
* entry
)
532 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
533 if (!IsValidBinding(binding
))
534 return NS_ERROR_UNEXPECTED
;
536 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
537 entry
, binding
->mRecord
.HashNumber()));
539 nsDiskCacheDeviceDeactivateEntryEvent
*event
=
540 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry
, binding
);
542 // ensure we can cancel the event via the binding later if necessary
543 binding
->mDeactivateEvent
= event
;
545 DebugOnly
<nsresult
> rv
= nsCacheService::DispatchToCacheIOThread(event
);
546 NS_ASSERTION(NS_SUCCEEDED(rv
), "DeactivateEntry: Failed dispatching "
547 "deactivation event");
552 * NOTE: called while holding the cache service lock
555 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry
* entry
,
556 nsDiskCacheBinding
* binding
)
559 if (entry
->IsDoomed()) {
560 // delete data, entry, record from disk for entry
561 rv
= mCacheMap
.DeleteStorage(&binding
->mRecord
);
564 // save stuff to disk for entry
565 rv
= mCacheMap
.WriteDiskCacheEntry(binding
);
567 // clean up as best we can
568 (void) mCacheMap
.DeleteStorage(&binding
->mRecord
);
569 (void) mCacheMap
.DeleteRecord(&binding
->mRecord
);
570 binding
->mDoomed
= true; // record is no longer in cache map
574 mBindery
.RemoveBinding(binding
); // extract binding from collision detection stuff
575 delete entry
; // which will release binding
582 * no hash number collision -> no problem
584 * record not active -> evict, no problem
586 * record is already doomed -> record shouldn't have been in map, no problem
587 * record is not doomed -> doom, and replace record in map
589 * walk matching hashnumber list to find lowest generation number
590 * take generation number from other (data/meta) location,
591 * or walk active list
593 * NOTE: called while holding the cache service lock
596 nsDiskCacheDevice::BindEntry(nsCacheEntry
* entry
)
598 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
599 if (mClearingDiskCache
) return NS_ERROR_NOT_AVAILABLE
;
601 nsDiskCacheRecord record
, oldRecord
;
602 nsDiskCacheBinding
*binding
;
603 PLDHashNumber hashNumber
= nsDiskCache::Hash(entry
->Key()->get());
605 // Find out if there is already an active binding for this hash. If yes it
606 // should have another key since BindEntry() shouldn't be called twice for
607 // the same entry. Doom the old entry, the new one will get another
608 // generation number so files won't collide.
609 binding
= mBindery
.FindActiveBinding(hashNumber
);
611 NS_ASSERTION(!binding
->mCacheEntry
->Key()->Equals(*entry
->Key()),
612 "BindEntry called for already bound entry!");
613 // If the entry is pending deactivation, cancel deactivation
614 if (binding
->mDeactivateEvent
) {
615 binding
->mDeactivateEvent
->CancelEvent();
616 binding
->mDeactivateEvent
= nullptr;
618 nsCacheService::DoomEntry(binding
->mCacheEntry
);
622 // Lookup hash number in cache map. There can be a colliding inactive entry.
623 // See bug #321361 comment 21 for the scenario. If there is such entry,
625 rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
626 if (NS_SUCCEEDED(rv
)) {
627 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
629 // compare key to be sure
630 if (!entry
->Key()->Equals(diskEntry
->Key())) {
631 mCacheMap
.DeleteStorage(&record
);
632 rv
= mCacheMap
.DeleteRecord(&record
);
633 if (NS_FAILED(rv
)) return rv
;
636 record
= nsDiskCacheRecord();
639 // create a new record for this entry
640 record
.SetHashNumber(nsDiskCache::Hash(entry
->Key()->get()));
641 record
.SetEvictionRank(ULONG_MAX
- SecondsFromPRTime(PR_Now()));
643 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
644 entry
, record
.HashNumber()));
646 if (!entry
->IsDoomed()) {
647 // if entry isn't doomed, add it to the cache map
648 rv
= mCacheMap
.AddRecord(&record
, &oldRecord
); // deletes old record, if any
649 if (NS_FAILED(rv
)) return rv
;
651 uint32_t oldHashNumber
= oldRecord
.HashNumber();
653 // gotta evict this one first
654 nsDiskCacheBinding
* oldBinding
= mBindery
.FindActiveBinding(oldHashNumber
);
656 // XXX if debug : compare keys for hashNumber collision
658 if (!oldBinding
->mCacheEntry
->IsDoomed()) {
659 // If the old entry is pending deactivation, cancel deactivation
660 if (oldBinding
->mDeactivateEvent
) {
661 oldBinding
->mDeactivateEvent
->CancelEvent();
662 oldBinding
->mDeactivateEvent
= nullptr;
664 // we've got a live one!
665 nsCacheService::DoomEntry(oldBinding
->mCacheEntry
);
666 // storage will be delete when oldBinding->mCacheEntry is Deactivated
670 // XXX if debug : compare keys for hashNumber collision
671 rv
= mCacheMap
.DeleteStorage(&oldRecord
);
672 if (NS_FAILED(rv
)) return rv
; // XXX delete record we just added?
677 // Make sure this entry has its associated nsDiskCacheBinding attached.
678 binding
= mBindery
.CreateBinding(entry
, &record
);
679 NS_ASSERTION(binding
, "nsDiskCacheDevice::BindEntry");
680 if (!binding
) return NS_ERROR_OUT_OF_MEMORY
;
681 NS_ASSERTION(binding
->mRecord
.ValidRecord(), "bad cache map record");
688 * NOTE: called while holding the cache service lock
691 nsDiskCacheDevice::DoomEntry(nsCacheEntry
* entry
)
693 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry
));
695 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
696 NS_ASSERTION(binding
, "DoomEntry: binding == nullptr");
700 if (!binding
->mDoomed
) {
701 // so it can't be seen by FindEntry() ever again.
705 mCacheMap
.DeleteRecord(&binding
->mRecord
);
706 NS_ASSERTION(NS_SUCCEEDED(rv
),"DeleteRecord failed.");
707 binding
->mDoomed
= true; // record in no longer in cache map
713 * NOTE: called while holding the cache service lock
716 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry
* entry
,
717 nsCacheAccessMode mode
,
719 nsIInputStream
** result
)
721 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
722 entry
, mode
, offset
));
724 NS_ENSURE_ARG_POINTER(entry
);
725 NS_ENSURE_ARG_POINTER(result
);
728 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
729 if (!IsValidBinding(binding
))
730 return NS_ERROR_UNEXPECTED
;
732 NS_ASSERTION(binding
->mCacheEntry
== entry
, "binding & entry don't point to each other");
734 rv
= binding
->EnsureStreamIO();
735 if (NS_FAILED(rv
)) return rv
;
737 return binding
->mStreamIO
->GetInputStream(offset
, result
);
742 * NOTE: called while holding the cache service lock
745 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry
* entry
,
746 nsCacheAccessMode mode
,
748 nsIOutputStream
** result
)
750 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
751 entry
, mode
, offset
));
753 NS_ENSURE_ARG_POINTER(entry
);
754 NS_ENSURE_ARG_POINTER(result
);
757 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
758 if (!IsValidBinding(binding
))
759 return NS_ERROR_UNEXPECTED
;
761 NS_ASSERTION(binding
->mCacheEntry
== entry
, "binding & entry don't point to each other");
763 rv
= binding
->EnsureStreamIO();
764 if (NS_FAILED(rv
)) return rv
;
766 return binding
->mStreamIO
->GetOutputStream(offset
, result
);
771 * NOTE: called while holding the cache service lock
774 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry
* entry
,
777 NS_ENSURE_ARG_POINTER(result
);
782 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
783 if (!IsValidBinding(binding
))
784 return NS_ERROR_UNEXPECTED
;
786 // check/set binding->mRecord for separate file, sync w/mCacheMap
787 if (binding
->mRecord
.DataLocationInitialized()) {
788 if (binding
->mRecord
.DataFile() != 0)
789 return NS_ERROR_NOT_AVAILABLE
; // data not stored as separate file
791 NS_ASSERTION(binding
->mRecord
.DataFileGeneration() == binding
->mGeneration
, "error generations out of sync");
793 binding
->mRecord
.SetDataFileGeneration(binding
->mGeneration
);
794 binding
->mRecord
.SetDataFileSize(0); // 1k minimum
795 if (!binding
->mDoomed
) {
796 // record stored in cache map, so update it
797 rv
= mCacheMap
.UpdateRecord(&binding
->mRecord
);
798 if (NS_FAILED(rv
)) return rv
;
802 nsCOMPtr
<nsIFile
> file
;
803 rv
= mCacheMap
.GetFileForDiskCacheRecord(&binding
->mRecord
,
806 getter_AddRefs(file
));
807 if (NS_FAILED(rv
)) return rv
;
809 NS_IF_ADDREF(*result
= file
);
815 * This routine will get called every time an open descriptor is written to.
817 * NOTE: called while holding the cache service lock
820 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry
* entry
, int32_t deltaSize
)
822 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
825 // If passed a negative value, then there's nothing to do.
829 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
830 if (!IsValidBinding(binding
))
831 return NS_ERROR_UNEXPECTED
;
833 NS_ASSERTION(binding
->mRecord
.ValidRecord(), "bad record");
835 uint32_t newSize
= entry
->DataSize() + deltaSize
;
836 uint32_t newSizeK
= ((newSize
+ 0x3FF) >> 10);
838 // If the new size is larger than max. file size or larger than
839 // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
840 if (EntryIsTooBig(newSize
)) {
844 nsCacheService::DoomEntry(entry
);
845 NS_ASSERTION(NS_SUCCEEDED(rv
),"DoomEntry() failed.");
846 return NS_ERROR_ABORT
;
849 uint32_t sizeK
= ((entry
->DataSize() + 0x03FF) >> 10); // round up to next 1k
851 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
852 // the target capacity should be calculated the same way.
853 if (sizeK
> kMaxDataSizeK
) sizeK
= kMaxDataSizeK
;
854 if (newSizeK
> kMaxDataSizeK
) newSizeK
= kMaxDataSizeK
;
856 // pre-evict entries to make space for new data
857 uint32_t targetCapacity
= mCacheCapacity
> (newSizeK
- sizeK
)
858 ? mCacheCapacity
- (newSizeK
- sizeK
)
860 EvictDiskCacheEntries(targetCapacity
);
866 /******************************************************************************
868 *****************************************************************************/
869 class EntryInfoVisitor
: public nsDiskCacheRecordVisitor
872 EntryInfoVisitor(nsDiskCacheMap
* cacheMap
,
873 nsICacheVisitor
* visitor
)
874 : mCacheMap(cacheMap
)
878 virtual int32_t VisitRecord(nsDiskCacheRecord
* mapRecord
)
880 // XXX optimization: do we have this record in memory?
882 // read in the entry (metadata)
883 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
885 return kVisitNextRecord
;
888 // create nsICacheEntryInfo
889 nsDiskCacheEntryInfo
* entryInfo
= new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID
, diskEntry
);
891 return kStopVisitingRecords
;
893 nsCOMPtr
<nsICacheEntryInfo
> ref(entryInfo
);
896 (void)mVisitor
->VisitEntry(DISK_CACHE_DEVICE_ID
, entryInfo
, &keepGoing
);
897 return keepGoing
? kVisitNextRecord
: kStopVisitingRecords
;
901 nsDiskCacheMap
* mCacheMap
;
902 nsICacheVisitor
* mVisitor
;
907 nsDiskCacheDevice::Visit(nsICacheVisitor
* visitor
)
909 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
910 nsDiskCacheDeviceInfo
* deviceInfo
= new nsDiskCacheDeviceInfo(this);
911 nsCOMPtr
<nsICacheDeviceInfo
> ref(deviceInfo
);
914 nsresult rv
= visitor
->VisitDevice(DISK_CACHE_DEVICE_ID
, deviceInfo
, &keepGoing
);
915 if (NS_FAILED(rv
)) return rv
;
918 EntryInfoVisitor
infoVisitor(&mCacheMap
, visitor
);
919 return mCacheMap
.VisitRecords(&infoVisitor
);
925 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
927 nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize
)
929 if (mMaxEntrySize
== -1) // no limit
930 return entrySize
> (static_cast<int64_t>(mCacheCapacity
) * 1024 / 8);
932 return entrySize
> mMaxEntrySize
||
933 entrySize
> (static_cast<int64_t>(mCacheCapacity
) * 1024 / 8);
937 nsDiskCacheDevice::EvictEntries(const char * clientID
)
939 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID
));
941 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
944 if (clientID
== nullptr) {
945 // we're clearing the entire disk cache
946 rv
= ClearDiskCache();
947 if (rv
!= NS_ERROR_CACHE_IN_USE
)
951 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, 0, clientID
);
952 rv
= mCacheMap
.VisitRecords(&evictor
);
954 if (clientID
== nullptr) // we tried to clear the entire cache
955 rv
= mCacheMap
.Trim(); // so trim cache block files (if possible)
965 nsDiskCacheDevice::OpenDiskCache()
967 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_OPEN
> timer
;
968 // if we don't have a cache directory, create one and open it
970 nsresult rv
= mCacheDirectory
->Exists(&exists
);
975 // Try opening cache map file.
976 nsDiskCache::CorruptCacheInfo corruptInfo
;
977 rv
= mCacheMap
.Open(mCacheDirectory
, &corruptInfo
, true);
979 if (NS_SUCCEEDED(rv
)) {
980 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS
,
982 } else if (rv
== NS_ERROR_ALREADY_INITIALIZED
) {
983 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
985 // Consider cache corrupt: delete it
986 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS
,
988 // delay delete by 1 minute to avoid IO thrash at startup
989 rv
= nsDeleteDir::DeleteDir(mCacheDirectory
, true, 60000);
996 // if we don't have a cache directory, create one and open it
998 nsCacheService::MarkStartingFresh();
999 rv
= mCacheDirectory
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
1000 CACHE_LOG_PATH(PR_LOG_ALWAYS
, "\ncreate cache directory: %s\n", mCacheDirectory
);
1001 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv
));
1005 // reopen the cache map
1006 nsDiskCache::CorruptCacheInfo corruptInfo
;
1007 rv
= mCacheMap
.Open(mCacheDirectory
, &corruptInfo
, false);
1017 nsDiskCacheDevice::ClearDiskCache()
1019 if (mBindery
.ActiveBindings())
1020 return NS_ERROR_CACHE_IN_USE
;
1022 mClearingDiskCache
= true;
1024 nsresult rv
= Shutdown_Private(false); // false: don't bother flushing
1028 mClearingDiskCache
= false;
1030 // If the disk cache directory is already gone, then it's not an error if
1031 // we fail to delete it ;-)
1032 rv
= nsDeleteDir::DeleteDir(mCacheDirectory
, true);
1033 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
)
1041 nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity
)
1043 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1046 NS_ASSERTION(targetCapacity
> 0, "oops");
1048 if (mCacheMap
.TotalSize() < targetCapacity
)
1051 // targetCapacity is in KiB's
1052 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, targetCapacity
, nullptr);
1053 return mCacheMap
.EvictRecords(&evictor
);
1062 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile
* parentDir
)
1067 if (Initialized()) {
1068 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1073 mCacheDirectory
= nullptr;
1077 // ensure parent directory exists
1078 rv
= parentDir
->Exists(&exists
);
1079 if (NS_SUCCEEDED(rv
) && !exists
)
1080 rv
= parentDir
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
1081 if (NS_FAILED(rv
)) return;
1083 // ensure cache directory exists
1084 nsCOMPtr
<nsIFile
> directory
;
1086 rv
= parentDir
->Clone(getter_AddRefs(directory
));
1087 if (NS_FAILED(rv
)) return;
1088 rv
= directory
->AppendNative(NS_LITERAL_CSTRING("Cache"));
1089 if (NS_FAILED(rv
)) return;
1091 mCacheDirectory
= do_QueryInterface(directory
);
1096 nsDiskCacheDevice::getCacheDirectory(nsIFile
** result
)
1098 *result
= mCacheDirectory
;
1099 NS_IF_ADDREF(*result
);
1104 * NOTE: called while holding the cache service lock
1107 nsDiskCacheDevice::SetCapacity(uint32_t capacity
)
1110 mCacheCapacity
= capacity
;
1111 if (Initialized()) {
1112 if (NS_IsMainThread()) {
1113 // Do not evict entries on the main thread
1114 nsCacheService::DispatchToCacheIOThread(
1115 new nsEvictDiskCacheEntriesEvent(this));
1117 // start evicting entries if the new size is smaller!
1118 EvictDiskCacheEntries(mCacheCapacity
);
1121 // Let cache map know of the new capacity
1122 mCacheMap
.NotifyCapacityChange(capacity
);
1126 uint32_t nsDiskCacheDevice::getCacheCapacity()
1128 return mCacheCapacity
;
1132 uint32_t nsDiskCacheDevice::getCacheSize()
1134 return mCacheMap
.TotalSize();
1138 uint32_t nsDiskCacheDevice::getEntryCount()
1140 return mCacheMap
.EntryCount();
1144 nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes
)
1146 // Internal units are bytes. Changing this only takes effect *after* the
1147 // change and has no consequences for existing cache-entries
1148 if (maxSizeInKilobytes
>= 0)
1149 mMaxEntrySize
= maxSizeInKilobytes
* 1024;
1155 nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
)
1157 size_t usage
= aMallocSizeOf(this);
1159 usage
+= mCacheMap
.SizeOfExcludingThis(aMallocSizeOf
);
1160 usage
+= mBindery
.SizeOfExcludingThis(aMallocSizeOf
);