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 files for ftruncate (or equivalent)
15 #define INCL_DOSERRORS
18 // 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"
37 #include "nsDeleteDir.h"
39 #include "nsICacheVisitor.h"
40 #include "nsReadableUtils.h"
41 #include "nsIInputStream.h"
42 #include "nsIOutputStream.h"
44 #include "nsCOMArray.h"
45 #include "nsISimpleEnumerator.h"
47 #include "mozilla/FunctionTimer.h"
48 #include "nsThreadUtils.h"
49 #include "mozilla/Telemetry.h"
51 static const char DISK_CACHE_DEVICE_ID
[] = { "disk" };
52 using namespace mozilla
;
54 class nsDiskCacheDeviceDeactivateEntryEvent
: public nsRunnable
{
56 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice
*device
,
58 nsDiskCacheBinding
* binding
)
68 nsCacheServiceAutoLock
lock(LOCK_TELEM(NSDISKCACHEDEVICEDEACTIVATEENTRYEVENT_RUN
));
70 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
73 (void) mDevice
->DeactivateEntry_Private(mEntry
, mBinding
);
78 void CancelEvent() { mCanceled
= true; }
82 nsDiskCacheDevice
*mDevice
;
83 nsDiskCacheBinding
*mBinding
;
86 class nsEvictDiskCacheEntriesEvent
: public nsRunnable
{
88 nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice
*device
)
93 nsCacheServiceAutoLock
lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN
));
94 mDevice
->EvictDiskCacheEntries(mDevice
->mCacheCapacity
);
99 nsDiskCacheDevice
*mDevice
;
102 /******************************************************************************
105 * Helper class for nsDiskCacheDevice.
107 *****************************************************************************/
109 class nsDiskCacheEvictor
: public nsDiskCacheRecordVisitor
112 nsDiskCacheEvictor( nsDiskCacheMap
* cacheMap
,
113 nsDiskCacheBindery
* cacheBindery
,
115 const char * clientID
)
116 : mCacheMap(cacheMap
)
117 , mBindery(cacheBindery
)
118 , mTargetSize(targetSize
)
119 , mClientID(clientID
)
121 mClientIDSize
= clientID
? strlen(clientID
) : 0;
124 virtual PRInt32
VisitRecord(nsDiskCacheRecord
* mapRecord
);
127 nsDiskCacheMap
* mCacheMap
;
128 nsDiskCacheBindery
* mBindery
;
129 PRUint32 mTargetSize
;
130 const char * mClientID
;
131 PRUint32 mClientIDSize
;
136 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord
* mapRecord
)
138 if (mCacheMap
->TotalSize() < mTargetSize
)
139 return kStopVisitingRecords
;
142 // we're just evicting records for a specific client
143 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
145 return kVisitNextRecord
; // XXX or delete record?
147 // Compare clientID's without malloc
148 if ((diskEntry
->mKeySize
<= mClientIDSize
) ||
149 (diskEntry
->Key()[mClientIDSize
] != ':') ||
150 (memcmp(diskEntry
->Key(), mClientID
, mClientIDSize
) != 0)) {
151 return kVisitNextRecord
; // clientID doesn't match, skip it
155 nsDiskCacheBinding
* binding
= mBindery
->FindActiveBinding(mapRecord
->HashNumber());
157 // If the entry is pending deactivation, cancel deactivation and doom
159 if (binding
->mDeactivateEvent
) {
160 binding
->mDeactivateEvent
->CancelEvent();
161 binding
->mDeactivateEvent
= nullptr;
163 // We are currently using this entry, so all we can do is doom it.
164 // Since we're enumerating the records, we don't want to call
165 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
166 binding
->mDoomed
= true; // mark binding record as 'deleted'
167 nsCacheService::DoomEntry(binding
->mCacheEntry
);
169 // entry not in use, just delete storage because we're enumerating the records
170 (void) mCacheMap
->DeleteStorage(mapRecord
);
173 return kDeleteRecordAndContinue
; // this will REALLY delete the record
177 /******************************************************************************
178 * nsDiskCacheDeviceInfo
179 *****************************************************************************/
181 class nsDiskCacheDeviceInfo
: public nsICacheDeviceInfo
{
184 NS_DECL_NSICACHEDEVICEINFO
186 nsDiskCacheDeviceInfo(nsDiskCacheDevice
* device
)
191 virtual ~nsDiskCacheDeviceInfo() {}
194 nsDiskCacheDevice
* mDevice
;
197 NS_IMPL_ISUPPORTS1(nsDiskCacheDeviceInfo
, nsICacheDeviceInfo
)
199 /* readonly attribute string description; */
200 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetDescription(char ** aDescription
)
202 NS_ENSURE_ARG_POINTER(aDescription
);
203 *aDescription
= NS_strdup("Disk cache device");
204 return *aDescription
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
207 /* readonly attribute string usageReport; */
208 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport
)
210 NS_ENSURE_ARG_POINTER(usageReport
);
213 buffer
.AssignLiteral(" <tr>\n"
214 " <th>Cache Directory:</th>\n"
216 nsCOMPtr
<nsIFile
> cacheDir
;
218 mDevice
->getCacheDirectory(getter_AddRefs(cacheDir
));
219 nsresult rv
= cacheDir
->GetPath(path
);
220 if (NS_SUCCEEDED(rv
)) {
221 AppendUTF16toUTF8(path
, buffer
);
223 buffer
.AppendLiteral("directory unavailable");
225 buffer
.AppendLiteral("</td>\n"
228 *usageReport
= ToNewCString(buffer
);
229 if (!*usageReport
) return NS_ERROR_OUT_OF_MEMORY
;
234 /* readonly attribute unsigned long entryCount; */
235 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetEntryCount(PRUint32
*aEntryCount
)
237 NS_ENSURE_ARG_POINTER(aEntryCount
);
238 *aEntryCount
= mDevice
->getEntryCount();
242 /* readonly attribute unsigned long totalSize; */
243 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetTotalSize(PRUint32
*aTotalSize
)
245 NS_ENSURE_ARG_POINTER(aTotalSize
);
246 // Returned unit's are in bytes
247 *aTotalSize
= mDevice
->getCacheSize() * 1024;
251 /* readonly attribute unsigned long maximumSize; */
252 NS_IMETHODIMP
nsDiskCacheDeviceInfo::GetMaximumSize(PRUint32
*aMaximumSize
)
254 NS_ENSURE_ARG_POINTER(aMaximumSize
);
255 // Returned unit's are in bytes
256 *aMaximumSize
= mDevice
->getCacheCapacity() * 1024;
261 /******************************************************************************
263 *****************************************************************************/
266 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
268 * See http://burtleburtle.net/bob/hash/evahash.html for more information
269 * about this hash function.
271 * This algorithm of this method implies nsDiskCacheRecords will be stored
272 * in a certain order on disk. If the algorithm changes, existing cache
273 * map files may become invalid, and therefore the kCurrentVersion needs
277 static inline void hashmix(PRUint32
& a
, PRUint32
& b
, PRUint32
& c
)
279 a
-= b
; a
-= c
; a
^= (c
>>13);
280 b
-= c
; b
-= a
; b
^= (a
<<8);
281 c
-= a
; c
-= b
; c
^= (b
>>13);
282 a
-= b
; a
-= c
; a
^= (c
>>12);
283 b
-= c
; b
-= a
; b
^= (a
<<16);
284 c
-= a
; c
-= b
; c
^= (b
>>5);
285 a
-= b
; a
-= c
; a
^= (c
>>3);
286 b
-= c
; b
-= a
; b
^= (a
<<10);
287 c
-= a
; c
-= b
; c
^= (b
>>15);
291 nsDiskCache::Hash(const char * key
, PLDHashNumber initval
)
293 const PRUint8
*k
= reinterpret_cast<const PRUint8
*>(key
);
294 PRUint32 a
, b
, c
, len
, length
;
296 length
= PL_strlen(key
);
297 /* Set up the internal state */
299 a
= b
= 0x9e3779b9; /* the golden ratio; an arbitrary value */
300 c
= initval
; /* variable initialization of internal state */
302 /*---------------------------------------- handle most of the key */
305 a
+= k
[0] + (PRUint32(k
[1])<<8) + (PRUint32(k
[2])<<16) + (PRUint32(k
[3])<<24);
306 b
+= k
[4] + (PRUint32(k
[5])<<8) + (PRUint32(k
[6])<<16) + (PRUint32(k
[7])<<24);
307 c
+= k
[8] + (PRUint32(k
[9])<<8) + (PRUint32(k
[10])<<16) + (PRUint32(k
[11])<<24);
312 /*------------------------------------- handle the last 11 bytes */
314 switch(len
) { /* all the case statements fall through */
315 case 11: c
+= (PRUint32(k
[10])<<24);
316 case 10: c
+= (PRUint32(k
[9])<<16);
317 case 9 : c
+= (PRUint32(k
[8])<<8);
318 /* the low-order byte of c is reserved for the length */
319 case 8 : b
+= (PRUint32(k
[7])<<24);
320 case 7 : b
+= (PRUint32(k
[6])<<16);
321 case 6 : b
+= (PRUint32(k
[5])<<8);
323 case 4 : a
+= (PRUint32(k
[3])<<24);
324 case 3 : a
+= (PRUint32(k
[2])<<16);
325 case 2 : a
+= (PRUint32(k
[1])<<8);
327 /* case 0: nothing left to add */
335 nsDiskCache::Truncate(PRFileDesc
* fd
, PRUint32 newEOF
)
337 // use modified SetEOF from nsFileStreams::SetEOF()
340 if (ftruncate(PR_FileDesc2NativeHandle(fd
), newEOF
) != 0) {
341 NS_ERROR("ftruncate failed");
342 return NS_ERROR_FAILURE
;
345 #elif defined(XP_WIN)
346 PRInt32 cnt
= PR_Seek(fd
, newEOF
, PR_SEEK_SET
);
347 if (cnt
== -1) return NS_ERROR_FAILURE
;
348 if (!SetEndOfFile((HANDLE
) PR_FileDesc2NativeHandle(fd
))) {
349 NS_ERROR("SetEndOfFile failed");
350 return NS_ERROR_FAILURE
;
353 #elif defined(XP_OS2)
354 if (DosSetFileSize((HFILE
) PR_FileDesc2NativeHandle(fd
), newEOF
) != NO_ERROR
) {
355 NS_ERROR("DosSetFileSize failed");
356 return NS_ERROR_FAILURE
;
359 // add implementations for other platforms here
365 /******************************************************************************
367 *****************************************************************************/
369 nsDiskCacheDevice::nsDiskCacheDevice()
371 , mMaxEntrySize(-1) // -1 means "no limit"
372 , mInitialized(false)
373 , mClearingDiskCache(false)
377 nsDiskCacheDevice::~nsDiskCacheDevice()
384 * methods of nsCacheDevice
387 nsDiskCacheDevice::Init()
394 NS_ERROR("Disk cache already initialized!");
395 return NS_ERROR_UNEXPECTED
;
398 if (!mCacheDirectory
)
399 return NS_ERROR_FAILURE
;
401 rv
= mBindery
.Init();
405 nsDeleteDir::RemoveOldTrashes(mCacheDirectory
);
408 rv
= OpenDiskCache();
410 (void) mCacheMap
.Close(false);
420 * NOTE: called while holding the cache service lock
423 nsDiskCacheDevice::Shutdown()
425 nsCacheService::AssertOwnsLock();
427 nsresult rv
= Shutdown_Private(true);
436 nsDiskCacheDevice::Shutdown_Private(bool flush
)
438 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush
));
441 // check cache limits in case we need to evict.
442 EvictDiskCacheEntries(mCacheCapacity
);
444 // At this point there may be a number of pending cache-requests on the
445 // cache-io thread. Wait for all these to run before we wipe out our
446 // datastructures (see bug #620660)
447 (void) nsCacheService::SyncWithCacheIOThread();
449 // write out persistent information about the cache.
450 (void) mCacheMap
.Close(flush
);
454 mInitialized
= false;
462 nsDiskCacheDevice::GetDeviceID()
464 return DISK_CACHE_DEVICE_ID
;
470 * cases: key not in disk cache, hash number free
471 * key not in disk cache, hash number used
474 * NOTE: called while holding the cache service lock
477 nsDiskCacheDevice::FindEntry(nsCString
* key
, bool *collision
)
479 Telemetry::AutoTimer
<Telemetry::CACHE_DISK_SEARCH
> timer
;
480 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
481 if (mClearingDiskCache
) return nullptr;
482 nsDiskCacheRecord record
;
483 nsDiskCacheBinding
* binding
= nullptr;
484 PLDHashNumber hashNumber
= nsDiskCache::Hash(key
->get());
488 binding
= mBindery
.FindActiveBinding(hashNumber
);
489 if (binding
&& !binding
->mCacheEntry
->Key()->Equals(*key
)) {
492 } else if (binding
&& binding
->mDeactivateEvent
) {
493 binding
->mDeactivateEvent
->CancelEvent();
494 binding
->mDeactivateEvent
= nullptr;
495 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
496 "req-key=%s entry-key=%s\n",
497 binding
->mCacheEntry
, key
, binding
->mCacheEntry
->Key()));
499 return binding
->mCacheEntry
; // just return this one, observing that
500 // FindActiveBinding() does not return
501 // bindings to doomed entries
505 // lookup hash number in cache map
506 nsresult rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
507 if (NS_FAILED(rv
)) return nullptr; // XXX log error?
509 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
510 if (!diskEntry
) return nullptr;
512 // compare key to be sure
513 if (!key
->Equals(diskEntry
->Key())) {
518 nsCacheEntry
* entry
= diskEntry
->CreateCacheEntry(this);
520 binding
= mBindery
.CreateBinding(entry
, &record
);
528 (void) mCacheMap
.DeleteStorage(&record
);
529 (void) mCacheMap
.DeleteRecord(&record
);
537 * NOTE: called while holding the cache service lock
540 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry
* entry
)
543 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
544 if (!IsValidBinding(binding
))
545 return NS_ERROR_UNEXPECTED
;
547 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
548 entry
, binding
->mRecord
.HashNumber()));
550 nsDiskCacheDeviceDeactivateEntryEvent
*event
=
551 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry
, binding
);
553 // ensure we can cancel the event via the binding later if necessary
554 binding
->mDeactivateEvent
= event
;
556 rv
= nsCacheService::DispatchToCacheIOThread(event
);
557 NS_ASSERTION(NS_SUCCEEDED(rv
), "DeactivateEntry: Failed dispatching "
558 "deactivation event");
563 * NOTE: called while holding the cache service lock
566 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry
* entry
,
567 nsDiskCacheBinding
* binding
)
570 if (entry
->IsDoomed()) {
571 // delete data, entry, record from disk for entry
572 rv
= mCacheMap
.DeleteStorage(&binding
->mRecord
);
575 // save stuff to disk for entry
576 rv
= mCacheMap
.WriteDiskCacheEntry(binding
);
578 // clean up as best we can
579 (void) mCacheMap
.DeleteStorage(&binding
->mRecord
);
580 (void) mCacheMap
.DeleteRecord(&binding
->mRecord
);
581 binding
->mDoomed
= true; // record is no longer in cache map
585 mBindery
.RemoveBinding(binding
); // extract binding from collision detection stuff
586 delete entry
; // which will release binding
593 * no hash number collision -> no problem
595 * record not active -> evict, no problem
597 * record is already doomed -> record shouldn't have been in map, no problem
598 * record is not doomed -> doom, and replace record in map
600 * walk matching hashnumber list to find lowest generation number
601 * take generation number from other (data/meta) location,
602 * or walk active list
604 * NOTE: called while holding the cache service lock
607 nsDiskCacheDevice::BindEntry(nsCacheEntry
* entry
)
609 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
610 if (mClearingDiskCache
) return NS_ERROR_NOT_AVAILABLE
;
612 nsDiskCacheRecord record
, oldRecord
;
613 nsDiskCacheBinding
*binding
;
614 PLDHashNumber hashNumber
= nsDiskCache::Hash(entry
->Key()->get());
616 // Find out if there is already an active binding for this hash. If yes it
617 // should have another key since BindEntry() shouldn't be called twice for
618 // the same entry. Doom the old entry, the new one will get another
619 // generation number so files won't collide.
620 binding
= mBindery
.FindActiveBinding(hashNumber
);
622 NS_ASSERTION(!binding
->mCacheEntry
->Key()->Equals(*entry
->Key()),
623 "BindEntry called for already bound entry!");
624 // If the entry is pending deactivation, cancel deactivation
625 if (binding
->mDeactivateEvent
) {
626 binding
->mDeactivateEvent
->CancelEvent();
627 binding
->mDeactivateEvent
= nullptr;
629 nsCacheService::DoomEntry(binding
->mCacheEntry
);
633 // Lookup hash number in cache map. There can be a colliding inactive entry.
634 // See bug #321361 comment 21 for the scenario. If there is such entry,
636 rv
= mCacheMap
.FindRecord(hashNumber
, &record
);
637 if (NS_SUCCEEDED(rv
)) {
638 nsDiskCacheEntry
* diskEntry
= mCacheMap
.ReadDiskCacheEntry(&record
);
640 // compare key to be sure
641 if (!entry
->Key()->Equals(diskEntry
->Key())) {
642 mCacheMap
.DeleteStorage(&record
);
643 rv
= mCacheMap
.DeleteRecord(&record
);
644 if (NS_FAILED(rv
)) return rv
;
647 record
= nsDiskCacheRecord();
650 // create a new record for this entry
651 record
.SetHashNumber(nsDiskCache::Hash(entry
->Key()->get()));
652 record
.SetEvictionRank(ULONG_MAX
- SecondsFromPRTime(PR_Now()));
654 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
655 entry
, record
.HashNumber()));
657 if (!entry
->IsDoomed()) {
658 // if entry isn't doomed, add it to the cache map
659 rv
= mCacheMap
.AddRecord(&record
, &oldRecord
); // deletes old record, if any
660 if (NS_FAILED(rv
)) return rv
;
662 PRUint32 oldHashNumber
= oldRecord
.HashNumber();
664 // gotta evict this one first
665 nsDiskCacheBinding
* oldBinding
= mBindery
.FindActiveBinding(oldHashNumber
);
667 // XXX if debug : compare keys for hashNumber collision
669 if (!oldBinding
->mCacheEntry
->IsDoomed()) {
670 // If the old entry is pending deactivation, cancel deactivation
671 if (oldBinding
->mDeactivateEvent
) {
672 oldBinding
->mDeactivateEvent
->CancelEvent();
673 oldBinding
->mDeactivateEvent
= nullptr;
675 // we've got a live one!
676 nsCacheService::DoomEntry(oldBinding
->mCacheEntry
);
677 // storage will be delete when oldBinding->mCacheEntry is Deactivated
681 // XXX if debug : compare keys for hashNumber collision
682 rv
= mCacheMap
.DeleteStorage(&oldRecord
);
683 if (NS_FAILED(rv
)) return rv
; // XXX delete record we just added?
688 // Make sure this entry has its associated nsDiskCacheBinding attached.
689 binding
= mBindery
.CreateBinding(entry
, &record
);
690 NS_ASSERTION(binding
, "nsDiskCacheDevice::BindEntry");
691 if (!binding
) return NS_ERROR_OUT_OF_MEMORY
;
692 NS_ASSERTION(binding
->mRecord
.ValidRecord(), "bad cache map record");
699 * NOTE: called while holding the cache service lock
702 nsDiskCacheDevice::DoomEntry(nsCacheEntry
* entry
)
704 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry
));
706 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
707 NS_ASSERTION(binding
, "DoomEntry: binding == nullptr");
711 if (!binding
->mDoomed
) {
712 // so it can't be seen by FindEntry() ever again.
716 mCacheMap
.DeleteRecord(&binding
->mRecord
);
717 NS_ASSERTION(NS_SUCCEEDED(rv
),"DeleteRecord failed.");
718 binding
->mDoomed
= true; // record in no longer in cache map
724 * NOTE: called while holding the cache service lock
727 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry
* entry
,
728 nsCacheAccessMode mode
,
730 nsIInputStream
** result
)
732 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
733 entry
, mode
, offset
));
735 NS_ENSURE_ARG_POINTER(entry
);
736 NS_ENSURE_ARG_POINTER(result
);
739 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
740 if (!IsValidBinding(binding
))
741 return NS_ERROR_UNEXPECTED
;
743 NS_ASSERTION(binding
->mCacheEntry
== entry
, "binding & entry don't point to each other");
745 rv
= binding
->EnsureStreamIO();
746 if (NS_FAILED(rv
)) return rv
;
748 return binding
->mStreamIO
->GetInputStream(offset
, result
);
753 * NOTE: called while holding the cache service lock
756 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry
* entry
,
757 nsCacheAccessMode mode
,
759 nsIOutputStream
** result
)
761 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
762 entry
, mode
, offset
));
764 NS_ENSURE_ARG_POINTER(entry
);
765 NS_ENSURE_ARG_POINTER(result
);
768 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
769 if (!IsValidBinding(binding
))
770 return NS_ERROR_UNEXPECTED
;
772 NS_ASSERTION(binding
->mCacheEntry
== entry
, "binding & entry don't point to each other");
774 rv
= binding
->EnsureStreamIO();
775 if (NS_FAILED(rv
)) return rv
;
777 return binding
->mStreamIO
->GetOutputStream(offset
, result
);
782 * NOTE: called while holding the cache service lock
785 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry
* entry
,
788 NS_ENSURE_ARG_POINTER(result
);
793 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
794 if (!IsValidBinding(binding
))
795 return NS_ERROR_UNEXPECTED
;
797 // check/set binding->mRecord for separate file, sync w/mCacheMap
798 if (binding
->mRecord
.DataLocationInitialized()) {
799 if (binding
->mRecord
.DataFile() != 0)
800 return NS_ERROR_NOT_AVAILABLE
; // data not stored as separate file
802 NS_ASSERTION(binding
->mRecord
.DataFileGeneration() == binding
->mGeneration
, "error generations out of sync");
804 binding
->mRecord
.SetDataFileGeneration(binding
->mGeneration
);
805 binding
->mRecord
.SetDataFileSize(0); // 1k minimum
806 if (!binding
->mDoomed
) {
807 // record stored in cache map, so update it
808 rv
= mCacheMap
.UpdateRecord(&binding
->mRecord
);
809 if (NS_FAILED(rv
)) return rv
;
813 nsCOMPtr
<nsIFile
> file
;
814 rv
= mCacheMap
.GetFileForDiskCacheRecord(&binding
->mRecord
,
817 getter_AddRefs(file
));
818 if (NS_FAILED(rv
)) return rv
;
820 NS_IF_ADDREF(*result
= file
);
826 * This routine will get called every time an open descriptor is written to.
828 * NOTE: called while holding the cache service lock
831 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry
* entry
, PRInt32 deltaSize
)
833 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
836 // If passed a negative value, then there's nothing to do.
840 nsDiskCacheBinding
* binding
= GetCacheEntryBinding(entry
);
841 if (!IsValidBinding(binding
))
842 return NS_ERROR_UNEXPECTED
;
844 NS_ASSERTION(binding
->mRecord
.ValidRecord(), "bad record");
846 PRUint32 newSize
= entry
->DataSize() + deltaSize
;
847 PRUint32 newSizeK
= ((newSize
+ 0x3FF) >> 10);
849 // If the new size is larger than max. file size or larger than
850 // 1/8 the cache capacity (which is in KiB's), and the entry has
851 // not been marked for file storage, doom the entry and abort.
852 if (EntryIsTooBig(newSize
) &&
853 entry
->StoragePolicy() != nsICache::STORE_ON_DISK_AS_FILE
) {
857 nsCacheService::DoomEntry(entry
);
858 NS_ASSERTION(NS_SUCCEEDED(rv
),"DoomEntry() failed.");
859 return NS_ERROR_ABORT
;
862 PRUint32 sizeK
= ((entry
->DataSize() + 0x03FF) >> 10); // round up to next 1k
864 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
865 // the target capacity should be calculated the same way.
866 if (sizeK
> kMaxDataSizeK
) sizeK
= kMaxDataSizeK
;
867 if (newSizeK
> kMaxDataSizeK
) newSizeK
= kMaxDataSizeK
;
869 // pre-evict entries to make space for new data
870 PRUint32 targetCapacity
= mCacheCapacity
> (newSizeK
- sizeK
)
871 ? mCacheCapacity
- (newSizeK
- sizeK
)
873 EvictDiskCacheEntries(targetCapacity
);
879 /******************************************************************************
881 *****************************************************************************/
882 class EntryInfoVisitor
: public nsDiskCacheRecordVisitor
885 EntryInfoVisitor(nsDiskCacheMap
* cacheMap
,
886 nsICacheVisitor
* visitor
)
887 : mCacheMap(cacheMap
)
891 virtual PRInt32
VisitRecord(nsDiskCacheRecord
* mapRecord
)
893 // XXX optimization: do we have this record in memory?
895 // read in the entry (metadata)
896 nsDiskCacheEntry
* diskEntry
= mCacheMap
->ReadDiskCacheEntry(mapRecord
);
898 return kVisitNextRecord
;
901 // create nsICacheEntryInfo
902 nsDiskCacheEntryInfo
* entryInfo
= new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID
, diskEntry
);
904 return kStopVisitingRecords
;
906 nsCOMPtr
<nsICacheEntryInfo
> ref(entryInfo
);
909 (void)mVisitor
->VisitEntry(DISK_CACHE_DEVICE_ID
, entryInfo
, &keepGoing
);
910 return keepGoing
? kVisitNextRecord
: kStopVisitingRecords
;
914 nsDiskCacheMap
* mCacheMap
;
915 nsICacheVisitor
* mVisitor
;
920 nsDiskCacheDevice::Visit(nsICacheVisitor
* visitor
)
922 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
923 nsDiskCacheDeviceInfo
* deviceInfo
= new nsDiskCacheDeviceInfo(this);
924 nsCOMPtr
<nsICacheDeviceInfo
> ref(deviceInfo
);
927 nsresult rv
= visitor
->VisitDevice(DISK_CACHE_DEVICE_ID
, deviceInfo
, &keepGoing
);
928 if (NS_FAILED(rv
)) return rv
;
931 EntryInfoVisitor
infoVisitor(&mCacheMap
, visitor
);
932 return mCacheMap
.VisitRecords(&infoVisitor
);
938 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
940 nsDiskCacheDevice::EntryIsTooBig(PRInt64 entrySize
)
942 if (mMaxEntrySize
== -1) // no limit
943 return entrySize
> (static_cast<PRInt64
>(mCacheCapacity
) * 1024 / 8);
945 return entrySize
> mMaxEntrySize
||
946 entrySize
> (static_cast<PRInt64
>(mCacheCapacity
) * 1024 / 8);
950 nsDiskCacheDevice::EvictEntries(const char * clientID
)
952 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID
));
954 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED
;
957 if (clientID
== nullptr) {
958 // we're clearing the entire disk cache
959 rv
= ClearDiskCache();
960 if (rv
!= NS_ERROR_CACHE_IN_USE
)
964 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, 0, clientID
);
965 rv
= mCacheMap
.VisitRecords(&evictor
);
967 if (clientID
== nullptr) // we tried to clear the entire cache
968 rv
= mCacheMap
.Trim(); // so trim cache block files (if possible)
978 nsDiskCacheDevice::OpenDiskCache()
980 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_OPEN
> timer
;
981 // if we don't have a cache directory, create one and open it
983 nsresult rv
= mCacheDirectory
->Exists(&exists
);
988 // Try opening cache map file.
989 nsDiskCache::CorruptCacheInfo corruptInfo
;
990 rv
= mCacheMap
.Open(mCacheDirectory
, &corruptInfo
);
992 if (NS_SUCCEEDED(rv
)) {
993 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS
,
995 } else if (rv
== NS_ERROR_ALREADY_INITIALIZED
) {
996 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
998 // Consider cache corrupt: delete it
999 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS
,
1001 // delay delete by 1 minute to avoid IO thrash at startup
1002 rv
= nsDeleteDir::DeleteDir(mCacheDirectory
, true, 60000);
1009 // if we don't have a cache directory, create one and open it
1011 nsCacheService::MarkStartingFresh();
1012 rv
= mCacheDirectory
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
1013 CACHE_LOG_PATH(PR_LOG_ALWAYS
, "\ncreate cache directory: %s\n", mCacheDirectory
);
1014 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv
));
1018 // reopen the cache map
1019 nsDiskCache::CorruptCacheInfo corruptInfo
;
1020 rv
= mCacheMap
.Open(mCacheDirectory
, &corruptInfo
);
1030 nsDiskCacheDevice::ClearDiskCache()
1032 if (mBindery
.ActiveBindings())
1033 return NS_ERROR_CACHE_IN_USE
;
1035 mClearingDiskCache
= true;
1037 nsresult rv
= Shutdown_Private(false); // false: don't bother flushing
1041 mClearingDiskCache
= false;
1043 // If the disk cache directory is already gone, then it's not an error if
1044 // we fail to delete it ;-)
1045 rv
= nsDeleteDir::DeleteDir(mCacheDirectory
, true);
1046 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
)
1054 nsDiskCacheDevice::EvictDiskCacheEntries(PRUint32 targetCapacity
)
1056 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1059 NS_ASSERTION(targetCapacity
> 0, "oops");
1061 if (mCacheMap
.TotalSize() < targetCapacity
)
1064 // targetCapacity is in KiB's
1065 nsDiskCacheEvictor
evictor(&mCacheMap
, &mBindery
, targetCapacity
, nullptr);
1066 return mCacheMap
.EvictRecords(&evictor
);
1075 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile
* parentDir
)
1080 if (Initialized()) {
1081 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1086 mCacheDirectory
= nullptr;
1090 // ensure parent directory exists
1091 rv
= parentDir
->Exists(&exists
);
1092 if (NS_SUCCEEDED(rv
) && !exists
)
1093 rv
= parentDir
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
1094 if (NS_FAILED(rv
)) return;
1096 // ensure cache directory exists
1097 nsCOMPtr
<nsIFile
> directory
;
1099 rv
= parentDir
->Clone(getter_AddRefs(directory
));
1100 if (NS_FAILED(rv
)) return;
1101 rv
= directory
->AppendNative(NS_LITERAL_CSTRING("Cache"));
1102 if (NS_FAILED(rv
)) return;
1104 mCacheDirectory
= do_QueryInterface(directory
);
1109 nsDiskCacheDevice::getCacheDirectory(nsIFile
** result
)
1111 *result
= mCacheDirectory
;
1112 NS_IF_ADDREF(*result
);
1117 * NOTE: called while holding the cache service lock
1120 nsDiskCacheDevice::SetCapacity(PRUint32 capacity
)
1123 mCacheCapacity
= capacity
;
1124 if (Initialized()) {
1125 if (NS_IsMainThread()) {
1126 // Do not evict entries on the main thread
1127 nsCacheService::DispatchToCacheIOThread(
1128 new nsEvictDiskCacheEntriesEvent(this));
1130 // start evicting entries if the new size is smaller!
1131 EvictDiskCacheEntries(mCacheCapacity
);
1134 // Let cache map know of the new capacity
1135 mCacheMap
.NotifyCapacityChange(capacity
);
1139 PRUint32
nsDiskCacheDevice::getCacheCapacity()
1141 return mCacheCapacity
;
1145 PRUint32
nsDiskCacheDevice::getCacheSize()
1147 return mCacheMap
.TotalSize();
1151 PRUint32
nsDiskCacheDevice::getEntryCount()
1153 return mCacheMap
.EntryCount();
1157 nsDiskCacheDevice::SetMaxEntrySize(PRInt32 maxSizeInKilobytes
)
1159 // Internal units are bytes. Changing this only takes effect *after* the
1160 // change and has no consequences for existing cache-entries
1161 if (maxSizeInKilobytes
>= 0)
1162 mMaxEntrySize
= maxSizeInKilobytes
* 1024;