Bug 1494162 - Part 45: Lazy load Menu and MenuItem in TabBar. r=pbro
[gecko.git] / netwerk / cache / nsDiskCacheDevice.cpp
blob39755904907b09e2d88309b8d3f309ba06f39585
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/. */
7 #include <limits.h>
9 #include "mozilla/DebugOnly.h"
11 #include "nsCache.h"
12 #include "nsIMemoryReporter.h"
14 // include files for ftruncate (or equivalent)
15 #if defined(XP_UNIX)
16 #include <unistd.h>
17 #elif defined(XP_WIN)
18 #include <windows.h>
19 #else
20 // XXX add necessary include file for ftruncate (or equivalent)
21 #endif
23 #include "prthread.h"
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"
42 #include "nsCRT.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 {
54 public:
55 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice* device,
56 nsCacheEntry* entry,
57 nsDiskCacheBinding* binding)
58 : mozilla::Runnable("nsDiskCacheDeviceDeactivateEntryEvent")
59 , mCanceled(false)
60 , mEntry(entry)
61 , mDevice(device)
62 , mBinding(binding)
66 NS_IMETHOD Run() override
68 nsCacheServiceAutoLock lock;
69 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
70 if (!mCanceled) {
71 (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
73 return NS_OK;
76 void CancelEvent() { mCanceled = true; }
77 private:
78 bool mCanceled;
79 nsCacheEntry *mEntry;
80 nsDiskCacheDevice *mDevice;
81 nsDiskCacheBinding *mBinding;
84 class nsEvictDiskCacheEntriesEvent : public Runnable {
85 public:
86 explicit nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice* device)
87 : mozilla::Runnable("nsEvictDiskCacheEntriesEvent")
88 , mDevice(device)
92 NS_IMETHOD Run() override
94 nsCacheServiceAutoLock lock;
95 mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
96 return NS_OK;
99 private:
100 nsDiskCacheDevice *mDevice;
103 /******************************************************************************
104 * nsDiskCacheEvictor
106 * Helper class for nsDiskCacheDevice.
108 *****************************************************************************/
110 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
112 public:
113 nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
114 nsDiskCacheBindery * cacheBindery,
115 uint32_t targetSize,
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;
127 private:
128 nsDiskCacheMap * mCacheMap;
129 nsDiskCacheBindery * mBindery;
130 uint32_t mTargetSize;
131 const char * mClientID;
132 uint32_t mClientIDSize;
136 int32_t
137 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
139 if (mCacheMap->TotalSize() < mTargetSize)
140 return kStopVisitingRecords;
142 if (mClientID) {
143 // we're just evicting records for a specific client
144 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
145 if (!diskEntry)
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());
157 if (binding) {
158 // If the entry is pending deactivation, cancel deactivation and doom
159 // the entry
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);
169 } else {
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 {
183 public:
184 NS_DECL_ISUPPORTS
185 NS_DECL_NSICACHEDEVICEINFO
187 explicit nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
188 : mDevice(device)
192 private:
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");
203 return NS_OK;
206 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(nsACString& aUsageReport)
208 nsCString buffer;
210 buffer.AssignLiteral(" <tr>\n"
211 " <th>Cache Directory:</th>\n"
212 " <td>");
213 nsCOMPtr<nsIFile> cacheDir;
214 nsAutoString path;
215 mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
216 nsresult rv = cacheDir->GetPath(path);
217 if (NS_SUCCEEDED(rv)) {
218 AppendUTF16toUTF8(path, buffer);
219 } else {
220 buffer.AppendLiteral("directory unavailable");
222 buffer.AppendLiteral("</td>\n"
223 " </tr>\n");
225 aUsageReport.Assign(buffer);
226 return NS_OK;
229 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
231 NS_ENSURE_ARG_POINTER(aEntryCount);
232 *aEntryCount = mDevice->getEntryCount();
233 return NS_OK;
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;
241 return NS_OK;
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;
249 return NS_OK;
253 /******************************************************************************
254 * nsDiskCache
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
266 * to be revised.
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);
282 PLDHashNumber
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 */
290 len = length;
291 a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
292 c = initval; /* variable initialization of internal state */
294 /*---------------------------------------- handle most of the key */
295 while (len >= 12)
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);
300 hashmix(a, b, c);
301 k += 12; len -= 12;
304 /*------------------------------------- handle the last 11 bytes */
305 c += length;
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;
318 case 1 : a += k[0];
319 /* case 0: nothing left to add */
321 hashmix(a, b, c);
323 return c;
326 nsresult
327 nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
329 // use modified SetEOF from nsFileStreams::SetEOF()
331 #if defined(XP_UNIX)
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;
345 #else
346 // add implementations for other platforms here
347 #endif
348 return NS_OK;
352 /******************************************************************************
353 * nsDiskCacheDevice
354 *****************************************************************************/
356 nsDiskCacheDevice::nsDiskCacheDevice()
357 : mCacheCapacity(0)
358 , mMaxEntrySize(-1) // -1 means "no limit"
359 , mInitialized(false)
360 , mClearingDiskCache(false)
364 nsDiskCacheDevice::~nsDiskCacheDevice()
366 Shutdown();
371 * methods of nsCacheDevice
373 nsresult
374 nsDiskCacheDevice::Init()
376 nsresult rv;
378 if (Initialized()) {
379 NS_ERROR("Disk cache already initialized!");
380 return NS_ERROR_UNEXPECTED;
383 if (!mCacheDirectory)
384 return NS_ERROR_FAILURE;
386 mBindery.Init();
388 // Open Disk Cache
389 rv = OpenDiskCache();
390 if (NS_FAILED(rv)) {
391 (void) mCacheMap.Close(false);
392 return rv;
395 mInitialized = true;
396 return NS_OK;
401 * NOTE: called while holding the cache service lock
403 nsresult
404 nsDiskCacheDevice::Shutdown()
406 nsCacheService::AssertOwnsLock();
408 nsresult rv = Shutdown_Private(true);
409 if (NS_FAILED(rv))
410 return rv;
412 return NS_OK;
416 nsresult
417 nsDiskCacheDevice::Shutdown_Private(bool flush)
419 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
421 if (Initialized()) {
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);
433 mBindery.Reset();
435 mInitialized = false;
438 return NS_OK;
442 const char *
443 nsDiskCacheDevice::GetDeviceID()
445 return DISK_CACHE_DEVICE_ID;
449 * FindEntry -
451 * cases: key not in disk cache, hash number free
452 * key not in disk cache, hash number used
453 * key in disk cache
455 * NOTE: called while holding the cache service lock
457 nsCacheEntry *
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());
467 *collision = false;
469 binding = mBindery.FindActiveBinding(hashNumber);
470 if (binding) {
471 if (!binding->mCacheEntry->Key()->Equals(*key)) {
472 *collision = true;
473 return nullptr;
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
488 binding = nullptr;
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())) {
499 *collision = true;
500 return nullptr;
503 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
504 if (entry) {
505 binding = mBindery.CreateBinding(entry, &record);
506 if (!binding) {
507 delete entry;
508 entry = nullptr;
512 if (!entry) {
513 (void) mCacheMap.DeleteStorage(&record);
514 (void) mCacheMap.DeleteRecord(&record);
517 return entry;
522 * NOTE: called while holding the cache service lock
524 nsresult
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");
543 return NS_OK;
547 * NOTE: called while holding the cache service lock
549 nsresult
550 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
551 nsDiskCacheBinding * binding)
553 nsresult rv = NS_OK;
554 if (entry->IsDoomed()) {
555 // delete data, entry, record from disk for entry
556 rv = mCacheMap.DeleteStorage(&binding->mRecord);
558 } else {
559 // save stuff to disk for entry
560 rv = mCacheMap.WriteDiskCacheEntry(binding);
561 if (NS_FAILED(rv)) {
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
571 return rv;
576 * BindEntry()
577 * no hash number collision -> no problem
578 * collision
579 * record not active -> evict, no problem
580 * record is active
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
590 nsresult
591 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
593 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
594 if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
595 nsresult rv = NS_OK;
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);
605 if (binding) {
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);
614 binding = nullptr;
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,
619 // delete it.
620 rv = mCacheMap.FindRecord(hashNumber, &record);
621 if (NS_SUCCEEDED(rv)) {
622 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
623 if (diskEntry) {
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();
647 if (oldHashNumber) {
648 // gotta evict this one first
649 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
650 if (oldBinding) {
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
663 } else {
664 // delete storage
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");
678 return NS_OK;
683 * NOTE: called while holding the cache service lock
685 void
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");
692 if (!binding)
693 return;
695 if (!binding->mDoomed) {
696 // so it can't be seen by FindEntry() ever again.
697 #ifdef DEBUG
698 nsresult rv =
699 #endif
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
710 nsresult
711 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
712 nsCacheAccessMode mode,
713 uint32_t offset,
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);
722 nsresult rv;
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
739 nsresult
740 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
741 nsCacheAccessMode mode,
742 uint32_t offset,
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);
751 nsresult rv;
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
768 nsresult
769 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
770 nsIFile ** result)
772 NS_ENSURE_ARG_POINTER(result);
773 *result = nullptr;
775 nsresult rv;
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");
787 } else {
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,
799 nsDiskCache::kData,
800 false,
801 getter_AddRefs(file));
802 if (NS_FAILED(rv)) return rv;
804 NS_IF_ADDREF(*result = file);
805 return NS_OK;
810 * This routine will get called every time an open descriptor is written to.
812 * NOTE: called while holding the cache service lock
814 nsresult
815 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
817 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
818 entry, deltaSize));
820 // If passed a negative value, then there's nothing to do.
821 if (deltaSize < 0)
822 return NS_OK;
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)) {
836 #ifdef DEBUG
837 nsresult rv =
838 #endif
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)
854 : 0;
855 EvictDiskCacheEntries(targetCapacity);
857 return NS_OK;
861 /******************************************************************************
862 * EntryInfoVisitor
863 *****************************************************************************/
864 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
866 public:
867 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
868 nsICacheVisitor * visitor)
869 : mCacheMap(cacheMap)
870 , mVisitor(visitor)
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);
879 if (!diskEntry) {
880 return kVisitNextRecord;
883 // create nsICacheEntryInfo
884 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
885 if (!entryInfo) {
886 return kStopVisitingRecords;
888 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
890 bool keepGoing;
891 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
892 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
895 private:
896 nsDiskCacheMap * mCacheMap;
897 nsICacheVisitor * mVisitor;
901 nsresult
902 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
904 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
905 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
906 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
908 bool keepGoing;
909 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
910 if (NS_FAILED(rv)) return rv;
912 if (keepGoing) {
913 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
914 return mCacheMap.VisitRecords(&infoVisitor);
917 return NS_OK;
920 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
921 bool
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);
931 nsresult
932 nsDiskCacheDevice::EvictEntries(const char * clientID)
934 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
936 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
937 nsresult rv;
939 if (clientID == nullptr) {
940 // we're clearing the entire disk cache
941 rv = ClearDiskCache();
942 if (rv != NS_ERROR_CACHE_IN_USE)
943 return rv;
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)
951 return rv;
956 * private methods
959 nsresult
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
964 bool exists;
965 nsresult rv = mCacheDirectory->Exists(&exists);
966 if (NS_FAILED(rv))
967 return rv;
969 if (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);
980 if (NS_FAILED(rv))
981 return rv;
982 exists = false;
986 // if we don't have a cache directory, create one and open it
987 if (!exists) {
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)));
993 if (NS_FAILED(rv))
994 return rv;
996 // reopen the cache map
997 nsDiskCache::CorruptCacheInfo corruptInfo;
998 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo);
999 if (NS_FAILED(rv))
1000 return rv;
1003 return NS_OK;
1007 nsresult
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
1016 if (NS_FAILED(rv))
1017 return rv;
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)
1025 return rv;
1027 return Init();
1031 nsresult
1032 nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
1034 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
1035 targetCapacity));
1037 NS_ASSERTION(targetCapacity > 0, "oops");
1039 if (mCacheMap.TotalSize() < targetCapacity)
1040 return NS_OK;
1042 // targetCapacity is in KiB's
1043 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
1044 return mCacheMap.EvictRecords(&evictor);
1049 * methods for prefs
1052 void
1053 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
1055 nsresult rv;
1056 bool exists;
1058 if (Initialized()) {
1059 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
1060 return;
1063 if (!parentDir) {
1064 mCacheDirectory = nullptr;
1065 return;
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;
1086 void
1087 nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
1089 *result = mCacheDirectory;
1090 NS_IF_ADDREF(*result);
1095 * NOTE: called while holding the cache service lock
1097 void
1098 nsDiskCacheDevice::SetCapacity(uint32_t capacity)
1100 // Units are KiB's
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));
1107 } else {
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();
1134 void
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;
1141 else
1142 mMaxEntrySize = -1;
1145 size_t
1146 nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
1148 size_t usage = aMallocSizeOf(this);
1150 usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
1151 usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
1153 return usage;