netwerk/cache/nsCacheService.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* vim: set ts=8 sts=4 et sw=4 tw=80: */
     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 "mozilla/ArrayUtils.h"
     8 #include "mozilla/Attributes.h"
     9 #include "mozilla/Assertions.h"
    10 #include "mozilla/DebugOnly.h"
    12 #include "necko-config.h"
    14 #include "nsCache.h"
    15 #include "nsCacheService.h"
    16 #include "nsCacheRequest.h"
    17 #include "nsCacheEntry.h"
    18 #include "nsCacheEntryDescriptor.h"
    19 #include "nsCacheDevice.h"
    20 #include "nsMemoryCacheDevice.h"
    21 #include "nsICacheVisitor.h"
    22 #include "nsDiskCacheDevice.h"
    23 #include "nsDiskCacheDeviceSQL.h"
    24 #include "nsCacheUtils.h"
    25 #include "../cache2/CacheObserver.h"
    27 #include "nsIObserverService.h"
    28 #include "nsIPrefService.h"
    29 #include "nsIPrefBranch.h"
    30 #include "nsIFile.h"
    31 #include "nsIOService.h"
    32 #include "nsDirectoryServiceDefs.h"
    33 #include "nsAppDirectoryServiceDefs.h"
    34 #include "nsThreadUtils.h"
    35 #include "nsProxyRelease.h"
    36 #include "nsVoidArray.h"
    37 #include "nsDeleteDir.h"
    38 #include "nsNetCID.h"
    39 #include <math.h>  // for log()
    40 #include "mozilla/Services.h"
    41 #include "nsITimer.h"
    42 #include "mozIStorageService.h"
    44 #include "mozilla/net/NeckoCommon.h"
    45 #include "mozilla/VisualEventTracer.h"
    46 #include <algorithm>
    48 using namespace mozilla;
    50 /******************************************************************************
    51  * nsCacheProfilePrefObserver
    52  *****************************************************************************/
    53 #define DISK_CACHE_ENABLE_PREF      "browser.cache.disk.enable"
    54 #define DISK_CACHE_DIR_PREF         "browser.cache.disk.parent_directory"
    55 #define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
    56     "browser.cache.disk.smart_size.first_run"
    57 #define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
    58     "browser.cache.disk.smart_size.enabled"
    59 #define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
    60 #define DISK_CACHE_CAPACITY_PREF    "browser.cache.disk.capacity"
    61 #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
    62 #define DISK_CACHE_CAPACITY         256000
    64 #define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
    65     "browser.cache.disk.smart_size.use_old_max"
    67 #define OFFLINE_CACHE_ENABLE_PREF   "browser.cache.offline.enable"
    68 #define OFFLINE_CACHE_DIR_PREF      "browser.cache.offline.parent_directory"
    69 #define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
    70 #define OFFLINE_CACHE_CAPACITY      512000
    72 #define MEMORY_CACHE_ENABLE_PREF    "browser.cache.memory.enable"
    73 #define MEMORY_CACHE_CAPACITY_PREF  "browser.cache.memory.capacity"
    74 #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
    76 #define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
    77 #define CACHE_COMPRESSION_LEVEL     1
    79 #define SANITIZE_ON_SHUTDOWN_PREF   "privacy.sanitize.sanitizeOnShutdown"
    80 #define CLEAR_ON_SHUTDOWN_PREF      "privacy.clearOnShutdown.cache"
    82 static const char * observerList[] = { 
    83     "profile-before-change",
    84     "profile-do-change",
    85     NS_XPCOM_SHUTDOWN_OBSERVER_ID,
    86     "last-pb-context-exited",
    87     "suspend_process_notification",
    88     "resume_process_notification"
    89 };
    91 static const char * prefList[] = { 
    92     DISK_CACHE_ENABLE_PREF,
    93     DISK_CACHE_SMART_SIZE_ENABLED_PREF,
    94     DISK_CACHE_CAPACITY_PREF,
    95     DISK_CACHE_DIR_PREF,
    96     DISK_CACHE_MAX_ENTRY_SIZE_PREF,
    97     DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
    98     OFFLINE_CACHE_ENABLE_PREF,
    99     OFFLINE_CACHE_CAPACITY_PREF,
   100     OFFLINE_CACHE_DIR_PREF,
   101     MEMORY_CACHE_ENABLE_PREF,
   102     MEMORY_CACHE_CAPACITY_PREF,
   103     MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
   104     CACHE_COMPRESSION_LEVEL_PREF,
   105     SANITIZE_ON_SHUTDOWN_PREF,
   106     CLEAR_ON_SHUTDOWN_PREF
   107 };
   109 // Cache sizes, in KB
   110 const int32_t DEFAULT_CACHE_SIZE = 250 * 1024;  // 250 MB
   111 #ifdef ANDROID
   112 const int32_t MAX_CACHE_SIZE = 200 * 1024;      // 200 MB
   113 const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024;  // 200 MB
   114 #else
   115 const int32_t MAX_CACHE_SIZE = 350 * 1024;      // 350 MB
   116 const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; //   1 GB
   117 #endif
   118 // Default cache size was 50 MB for many years until FF 4:
   119 const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
   121 class nsCacheProfilePrefObserver : public nsIObserver
   122 {
   123 public:
   124     NS_DECL_THREADSAFE_ISUPPORTS
   125     NS_DECL_NSIOBSERVER
   127     nsCacheProfilePrefObserver()
   128         : mHaveProfile(false)
   129         , mDiskCacheEnabled(false)
   130         , mDiskCacheCapacity(0)
   131         , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
   132         , mSmartSizeEnabled(false)
   133         , mShouldUseOldMaxSmartSize(false)
   134         , mOfflineCacheEnabled(false)
   135         , mOfflineCacheCapacity(0)
   136         , mMemoryCacheEnabled(true)
   137         , mMemoryCacheCapacity(-1)
   138         , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
   139         , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
   140         , mSanitizeOnShutdown(false)
   141         , mClearCacheOnShutdown(false)
   142     {
   143     }
   145     virtual ~nsCacheProfilePrefObserver() {}
   147     nsresult        Install();
   148     void            Remove();
   149     nsresult        ReadPrefs(nsIPrefBranch* branch);
   151     bool            DiskCacheEnabled();
   152     int32_t         DiskCacheCapacity()         { return mDiskCacheCapacity; }
   153     void            SetDiskCacheCapacity(int32_t);
   154     int32_t         DiskCacheMaxEntrySize()     { return mDiskCacheMaxEntrySize; }
   155     nsIFile *       DiskCacheParentDirectory()  { return mDiskCacheParentDirectory; }
   156     bool            SmartSizeEnabled()          { return mSmartSizeEnabled; }
   158     bool            ShouldUseOldMaxSmartSize()        { return mShouldUseOldMaxSmartSize; }
   159     void            SetUseNewMaxSmartSize(bool useNew)     { mShouldUseOldMaxSmartSize = !useNew; }
   161     bool            OfflineCacheEnabled();
   162     int32_t         OfflineCacheCapacity()         { return mOfflineCacheCapacity; }
   163     nsIFile *       OfflineCacheParentDirectory()  { return mOfflineCacheParentDirectory; }
   165     bool            MemoryCacheEnabled();
   166     int32_t         MemoryCacheCapacity();
   167     int32_t         MemoryCacheMaxEntrySize()     { return mMemoryCacheMaxEntrySize; }
   169     int32_t         CacheCompressionLevel();
   171     bool            SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
   173     static uint32_t GetSmartCacheSize(const nsAString& cachePath,
   174                                       uint32_t currentSize,
   175                                       bool shouldUseOldMaxSmartSize);
   177     bool                    PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
   179 private:
   180     bool                    mHaveProfile;
   182     bool                    mDiskCacheEnabled;
   183     int32_t                 mDiskCacheCapacity; // in kilobytes
   184     int32_t                 mDiskCacheMaxEntrySize; // in kilobytes
   185     nsCOMPtr<nsIFile>       mDiskCacheParentDirectory;
   186     bool                    mSmartSizeEnabled;
   188     bool                    mShouldUseOldMaxSmartSize;
   190     bool                    mOfflineCacheEnabled;
   191     int32_t                 mOfflineCacheCapacity; // in kilobytes
   192     nsCOMPtr<nsIFile>       mOfflineCacheParentDirectory;
   194     bool                    mMemoryCacheEnabled;
   195     int32_t                 mMemoryCacheCapacity; // in kilobytes
   196     int32_t                 mMemoryCacheMaxEntrySize; // in kilobytes
   198     int32_t                 mCacheCompressionLevel;
   200     bool                    mSanitizeOnShutdown;
   201     bool                    mClearCacheOnShutdown;
   202 };
   204 NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
   206 class nsSetDiskSmartSizeCallback MOZ_FINAL : public nsITimerCallback
   207 {
   208 public:
   209     NS_DECL_THREADSAFE_ISUPPORTS
   211     NS_IMETHOD Notify(nsITimer* aTimer) {
   212         if (nsCacheService::gService) {
   213             nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
   214             nsCacheService::gService->SetDiskSmartSize_Locked();
   215             nsCacheService::gService->mSmartSizeTimer = nullptr;
   216         }
   217         return NS_OK;
   218     }
   219 };
   221 NS_IMPL_ISUPPORTS(nsSetDiskSmartSizeCallback, nsITimerCallback)
   223 // Runnable sent to main thread after the cache IO thread calculates available
   224 // disk space, so that there is no race in setting mDiskCacheCapacity.
   225 class nsSetSmartSizeEvent: public nsRunnable 
   226 {
   227 public:
   228     nsSetSmartSizeEvent(int32_t smartSize)
   229         : mSmartSize(smartSize) {}
   231     NS_IMETHOD Run() 
   232     {
   233         NS_ASSERTION(NS_IsMainThread(), 
   234                      "Setting smart size data off the main thread");
   236         // Main thread may have already called nsCacheService::Shutdown
   237         if (!nsCacheService::IsInitialized())
   238             return NS_ERROR_NOT_AVAILABLE;
   240         // Ensure smart sizing wasn't switched off while event was pending.
   241         // It is safe to access the observer without the lock since we are
   242         // on the main thread and the value changes only on the main thread.
   243         if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
   244             return NS_OK;
   246         nsCacheService::SetDiskCacheCapacity(mSmartSize);
   248         nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
   249         if (!ps ||
   250             NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
   251             NS_WARNING("Failed to set smart size pref");
   253         return NS_OK;
   254     }
   256 private:
   257     int32_t mSmartSize;
   258 };
   261 // Runnable sent from main thread to cacheIO thread
   262 class nsGetSmartSizeEvent: public nsRunnable
   263 {
   264 public:
   265     nsGetSmartSizeEvent(const nsAString& cachePath, uint32_t currentSize,
   266                         bool shouldUseOldMaxSmartSize)
   267       : mCachePath(cachePath)
   268       , mCurrentSize(currentSize)
   269       , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
   270     {}
   272     // Calculates user's disk space available on a background thread and
   273     // dispatches this value back to the main thread.
   274     NS_IMETHOD Run()
   275     {
   276         uint32_t size;
   277         size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
   278                                                              mCurrentSize,
   279                                                              mShouldUseOldMaxSmartSize);
   280         NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
   281         return NS_OK;
   282     }
   284 private:
   285     nsString mCachePath;
   286     uint32_t mCurrentSize;
   287     bool     mShouldUseOldMaxSmartSize;
   288 };
   290 class nsBlockOnCacheThreadEvent : public nsRunnable {
   291 public:
   292     nsBlockOnCacheThreadEvent()
   293     {
   294     }
   295     NS_IMETHOD Run()
   296     {
   297         nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
   298 #ifdef PR_LOGGING
   299         CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
   300 #endif
   301         nsCacheService::gService->mCondVar.Notify();
   302         return NS_OK;
   303     }
   304 };
   307 nsresult
   308 nsCacheProfilePrefObserver::Install()
   309 {
   310     // install profile-change observer
   311     nsCOMPtr<nsIObserverService> observerService =
   312         mozilla::services::GetObserverService();
   313     if (!observerService)
   314         return NS_ERROR_FAILURE;
   316     nsresult rv, rv2 = NS_OK;
   317     for (unsigned int i=0; i<ArrayLength(observerList); i++) {
   318         rv = observerService->AddObserver(this, observerList[i], false);
   319         if (NS_FAILED(rv)) 
   320             rv2 = rv;
   321     }
   323     // install preferences observer
   324     nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   325     if (!branch) return NS_ERROR_FAILURE;
   327     for (unsigned int i=0; i<ArrayLength(prefList); i++) {
   328         rv = branch->AddObserver(prefList[i], this, false);
   329         if (NS_FAILED(rv))
   330             rv2 = rv;
   331     }
   333     // Determine if we have a profile already
   334     //     Install() is called *after* the profile-after-change notification
   335     //     when there is only a single profile, or it is specified on the
   336     //     commandline at startup.
   337     //     In that case, we detect the presence of a profile by the existence
   338     //     of the NS_APP_USER_PROFILE_50_DIR directory.
   340     nsCOMPtr<nsIFile> directory;
   341     rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   342                                 getter_AddRefs(directory));
   343     if (NS_SUCCEEDED(rv))
   344         mHaveProfile = true;
   346     rv = ReadPrefs(branch);
   347     NS_ENSURE_SUCCESS(rv, rv);
   349     return rv2;
   350 }
   353 void
   354 nsCacheProfilePrefObserver::Remove()
   355 {
   356     // remove Observer Service observers
   357     nsCOMPtr<nsIObserverService> obs =
   358         mozilla::services::GetObserverService();
   359     if (obs) {
   360         for (unsigned int i=0; i<ArrayLength(observerList); i++) {
   361             obs->RemoveObserver(this, observerList[i]);
   362         }
   363     }
   365     // remove Pref Service observers
   366     nsCOMPtr<nsIPrefBranch> prefs =
   367         do_GetService(NS_PREFSERVICE_CONTRACTID);
   368     if (!prefs)
   369         return;
   370     for (unsigned int i=0; i<ArrayLength(prefList); i++)
   371         prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
   372 }
   374 void
   375 nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
   376 {
   377     mDiskCacheCapacity = std::max(0, capacity);
   378 }
   381 NS_IMETHODIMP
   382 nsCacheProfilePrefObserver::Observe(nsISupports *     subject,
   383                                     const char *      topic,
   384                                     const char16_t * data_unicode)
   385 {
   386     nsresult rv;
   387     NS_ConvertUTF16toUTF8 data(data_unicode);
   388     CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get()));
   390     if (!nsCacheService::IsInitialized()) {
   391         if (!strcmp("resume_process_notification", topic)) {
   392             // A suspended process has a closed cache, so re-open it here.
   393             nsCacheService::GlobalInstance()->Init();
   394         }
   395         return NS_OK;
   396     }
   398     if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
   399         // xpcom going away, shutdown cache service
   400         nsCacheService::GlobalInstance()->Shutdown();
   401     } else if (!strcmp("profile-before-change", topic)) {
   402         // profile before change
   403         mHaveProfile = false;
   405         // XXX shutdown devices
   406         nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse",
   407                                                   data.get()));
   409     } else if (!strcmp("suspend_process_notification", topic)) {
   410         // A suspended process may never return, so shutdown the cache to reduce
   411         // cache corruption.
   412         nsCacheService::GlobalInstance()->Shutdown();
   413     } else if (!strcmp("profile-do-change", topic)) {
   414         // profile after change
   415         mHaveProfile = true;
   416         nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
   417         ReadPrefs(branch);
   418         nsCacheService::OnProfileChanged();
   420     } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
   422         // ignore pref changes until we're done switch profiles
   423         if (!mHaveProfile)  
   424             return NS_OK;
   426         nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
   427         if (NS_FAILED(rv))  
   428             return rv;
   430         // which preference changed?
   431         if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
   433             rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
   434                                      &mDiskCacheEnabled);
   435             if (NS_FAILED(rv))  
   436                 return rv;
   437             nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
   439         } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
   441             int32_t capacity = 0;
   442             rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
   443             if (NS_FAILED(rv))  
   444                 return rv;
   445             mDiskCacheCapacity = std::max(0, capacity);
   446             nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
   448         // Update the cache capacity when smart sizing is turned on/off 
   449         } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
   450             // Is the update because smartsizing was turned on, or off?
   451             rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
   452                                      &mSmartSizeEnabled);
   453             if (NS_FAILED(rv)) 
   454                 return rv;
   455             int32_t newCapacity = 0;
   456             if (mSmartSizeEnabled) {
   457                 nsCacheService::SetDiskSmartSize();
   458             } else {
   459                 // Smart sizing switched off: use user specified size
   460                 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
   461                 if (NS_FAILED(rv)) 
   462                     return rv;
   463                 mDiskCacheCapacity = std::max(0, newCapacity);
   464                 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
   465             }
   466         } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
   467             rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
   468                                      &mShouldUseOldMaxSmartSize);
   469             if (NS_FAILED(rv))
   470                 return rv;
   471         } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
   472             int32_t newMaxSize;
   473             rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
   474                                     &newMaxSize);
   475             if (NS_FAILED(rv)) 
   476                 return rv;
   478             mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
   479             nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
   481 #if 0            
   482         } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
   483             // XXX We probaby don't want to respond to this pref except after
   484             // XXX profile changes.  Ideally, there should be somekind of user
   485             // XXX notification that the pref change won't take effect until
   486             // XXX the next time the profile changes (browser launch)
   487 #endif            
   488         } else
   490         // which preference changed?
   491         if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
   493             rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
   494                                      &mOfflineCacheEnabled);
   495             if (NS_FAILED(rv))  return rv;
   496             nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
   498         } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
   500             int32_t capacity = 0;
   501             rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
   502             if (NS_FAILED(rv))  return rv;
   503             mOfflineCacheCapacity = std::max(0, capacity);
   504             nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
   505 #if 0
   506         } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
   507             // XXX We probaby don't want to respond to this pref except after
   508             // XXX profile changes.  Ideally, there should be some kind of user
   509             // XXX notification that the pref change won't take effect until
   510             // XXX the next time the profile changes (browser launch)
   511 #endif
   512         } else
   514         if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
   516             rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
   517                                      &mMemoryCacheEnabled);
   518             if (NS_FAILED(rv))  
   519                 return rv;
   520             nsCacheService::SetMemoryCache();
   522         } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
   524             mMemoryCacheCapacity = -1;
   525             (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
   526                                       &mMemoryCacheCapacity);
   527             nsCacheService::SetMemoryCache();
   528         } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
   529             int32_t newMaxSize;
   530             rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
   531                                      &newMaxSize);
   532             if (NS_FAILED(rv)) 
   533                 return rv;
   535             mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
   536             nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
   537         } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
   538             mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
   539             (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
   540                                      &mCacheCompressionLevel);
   541             mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
   542             mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
   543         } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
   544             rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
   545                                      &mSanitizeOnShutdown);
   546             if (NS_FAILED(rv))
   547                 return rv;
   548             nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
   549         } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
   550             rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
   551                                      &mClearCacheOnShutdown);
   552             if (NS_FAILED(rv))
   553                 return rv;
   554             nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
   555         }
   556     } else if (!strcmp("last-pb-context-exited", topic)) {
   557         nsCacheService::LeavePrivateBrowsing();
   558     }
   560     return NS_OK;
   561 }
   563 // Returns default ("smart") size (in KB) of cache, given available disk space
   564 // (also in KB)
   565 static uint32_t
   566 SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
   567 {
   568     uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
   570     if (availKB > 100 * 1024 * 1024)
   571         return maxSize;  // skip computing if we're over 100 GB
   573     // Grow/shrink in 10 MB units, deliberately, so that in the common case we
   574     // don't shrink cache and evict items every time we startup (it's important
   575     // that we don't slow down startup benchmarks).
   576     uint32_t sz10MBs = 0;
   577     uint32_t avail10MBs = availKB / (1024*10);
   579     // .5% of space above 25 GB
   580     if (avail10MBs > 2500) {
   581         sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
   582         avail10MBs = 2500;
   583     }
   584     // 1% of space between 7GB -> 25 GB
   585     if (avail10MBs > 700) {
   586         sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
   587         avail10MBs = 700;
   588     }
   589     // 5% of space between 500 MB -> 7 GB
   590     if (avail10MBs > 50) {
   591         sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
   592         avail10MBs = 50;
   593     }
   595 #ifdef ANDROID
   596     // On Android, smaller/older devices may have very little storage and
   597     // device owners may be sensitive to storage footprint: Use a smaller
   598     // percentage of available space and a smaller minimum.
   600     // 20% of space up to 500 MB (10 MB min)
   601     sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
   602 #else
   603     // 40% of space up to 500 MB (50 MB min)
   604     sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
   605 #endif
   607     return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
   608 }
   610  /* Computes our best guess for the default size of the user's disk cache, 
   611   * based on the amount of space they have free on their hard drive. 
   612   * We use a tiered scheme: the more space available, 
   613   * the larger the disk cache will be. However, we do not want
   614   * to enable the disk cache to grow to an unbounded size, so the larger the
   615   * user's available space is, the smaller of a percentage we take. We set a
   616   * lower bound of 50MB and an upper bound of 1GB.  
   617   *
   618   *@param:  None.
   619   *@return: The size that the user's disk cache should default to, in kBytes.
   620   */
   621 uint32_t
   622 nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
   623                                               uint32_t currentSize,
   624                                               bool shouldUseOldMaxSmartSize)
   625 {
   626     // Check for free space on device where cache directory lives
   627     nsresult rv;
   628     nsCOMPtr<nsIFile> 
   629         cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
   630     if (NS_FAILED(rv) || !cacheDirectory)
   631         return DEFAULT_CACHE_SIZE;
   632     rv = cacheDirectory->InitWithPath(cachePath);
   633     if (NS_FAILED(rv))
   634         return DEFAULT_CACHE_SIZE;
   635     int64_t bytesAvailable;
   636     rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
   637     if (NS_FAILED(rv))
   638         return DEFAULT_CACHE_SIZE;
   640     return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
   641                                                 currentSize),
   642                           shouldUseOldMaxSmartSize);
   643 }
   645 /* Determine if we are permitted to dynamically size the user's disk cache based
   646  * on their disk space available. We may do this so long as the pref 
   647  * smart_size.enabled is true.
   648  */
   649 bool
   650 nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
   651                                                  firstRun)
   652 {
   653     nsresult rv;
   654     if (firstRun) {
   655         // check if user has set cache size in the past
   656         bool userSet;
   657         rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
   658         if (NS_FAILED(rv)) userSet = true;
   659         if (userSet) {
   660             int32_t oldCapacity;
   661             // If user explicitly set cache size to be smaller than old default
   662             // of 50 MB, then keep user's value. Otherwise use smart sizing.
   663             rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
   664             if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
   665                 mSmartSizeEnabled = false;
   666                 branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
   667                                     mSmartSizeEnabled);
   668                 return mSmartSizeEnabled;
   669             }
   670         }
   671         // Set manual setting to MAX cache size as starting val for any
   672         // adjustment by user: (bug 559942 comment 65)
   673         int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
   674         branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
   675     }
   677     rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
   678                              &mSmartSizeEnabled);
   679     if (NS_FAILED(rv))
   680         mSmartSizeEnabled = false;
   681     return mSmartSizeEnabled;
   682 }
   685 nsresult
   686 nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
   687 {
   688     nsresult rv = NS_OK;
   690     // read disk cache device prefs
   691     mDiskCacheEnabled = true;  // presume disk cache is enabled
   692     (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
   694     mDiskCacheCapacity = DISK_CACHE_CAPACITY;
   695     (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
   696     mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
   698     (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
   699                               &mDiskCacheMaxEntrySize);
   700     mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
   702     (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF,     // ignore error
   703                                    NS_GET_IID(nsIFile),
   704                                    getter_AddRefs(mDiskCacheParentDirectory));
   706     (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
   707                                &mShouldUseOldMaxSmartSize);
   709     if (!mDiskCacheParentDirectory) {
   710         nsCOMPtr<nsIFile>  directory;
   712         // try to get the disk cache parent directory
   713         rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
   714                                     getter_AddRefs(directory));
   715         if (NS_FAILED(rv)) {
   716             // try to get the profile directory (there may not be a profile yet)
   717             nsCOMPtr<nsIFile> profDir;
   718             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   719                                    getter_AddRefs(profDir));
   720             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
   721                                    getter_AddRefs(directory));
   722             if (!directory)
   723                 directory = profDir;
   724             else if (profDir) {
   725                 nsCacheService::MoveOrRemoveDiskCache(profDir, directory, 
   726                                                       "Cache");
   727             }
   728         }
   729         // use file cache in build tree only if asked, to avoid cache dir litter
   730         if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
   731             rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
   732                                         getter_AddRefs(directory));
   733         }
   734         if (directory)
   735             mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
   736     }
   737     if (mDiskCacheParentDirectory) {
   738         bool firstSmartSizeRun;
   739         rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF, 
   740                                  &firstSmartSizeRun); 
   741         if (NS_FAILED(rv)) 
   742             firstSmartSizeRun = false;
   743         if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
   744             // Avoid evictions: use previous cache size until smart size event
   745             // updates mDiskCacheCapacity
   746             rv = branch->GetIntPref(firstSmartSizeRun ?
   747                                     DISK_CACHE_CAPACITY_PREF :
   748                                     DISK_CACHE_SMART_SIZE_PREF,
   749                                     &mDiskCacheCapacity);
   750             if (NS_FAILED(rv))
   751                 mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
   752         }
   754         if (firstSmartSizeRun) {
   755             // It is no longer our first run
   756             rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF, 
   757                                      false);
   758             if (NS_FAILED(rv)) 
   759                 NS_WARNING("Failed setting first_run pref in ReadPrefs.");
   760         }
   761     }
   763     // read offline cache device prefs
   764     mOfflineCacheEnabled = true;  // presume offline cache is enabled
   765     (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
   766                               &mOfflineCacheEnabled);
   768     mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
   769     (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
   770                              &mOfflineCacheCapacity);
   771     mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
   773     (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF,     // ignore error
   774                                    NS_GET_IID(nsIFile),
   775                                    getter_AddRefs(mOfflineCacheParentDirectory));
   777     if (!mOfflineCacheParentDirectory) {
   778         nsCOMPtr<nsIFile>  directory;
   780         // try to get the offline cache parent directory
   781         rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
   782                                     getter_AddRefs(directory));
   783         if (NS_FAILED(rv)) {
   784             // try to get the profile directory (there may not be a profile yet)
   785             nsCOMPtr<nsIFile> profDir;
   786             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
   787                                    getter_AddRefs(profDir));
   788             NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
   789                                    getter_AddRefs(directory));
   790             if (!directory)
   791                 directory = profDir;
   792             else if (profDir) {
   793                 nsCacheService::MoveOrRemoveDiskCache(profDir, directory, 
   794                                                       "OfflineCache");
   795             }
   796         }
   797 #if DEBUG
   798         if (!directory) {
   799             // use current process directory during development
   800             rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
   801                                         getter_AddRefs(directory));
   802         }
   803 #endif
   804         if (directory)
   805             mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
   806     }
   808     // read memory cache device prefs
   809     (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
   811     mMemoryCacheCapacity = -1;
   812     (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
   813                               &mMemoryCacheCapacity);
   815     (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
   816                               &mMemoryCacheMaxEntrySize);
   817     mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
   819     // read cache compression level pref
   820     mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
   821     (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
   822                              &mCacheCompressionLevel);
   823     mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
   824     mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
   826     // read cache shutdown sanitization prefs
   827     (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
   828                                &mSanitizeOnShutdown);
   829     (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
   830                                &mClearCacheOnShutdown);
   832     return rv;
   833 }
   835 nsresult
   836 nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
   837 {
   838     if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
   839     return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
   840 }
   842 nsresult
   843 nsCacheService::SyncWithCacheIOThread()
   844 {
   845     gService->mLock.AssertCurrentThreadOwns();
   846     if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
   848     nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
   850     // dispatch event - it will notify the monitor when it's done
   851     nsresult rv =
   852         gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
   853     if (NS_FAILED(rv)) {
   854         NS_WARNING("Failed dispatching block-event");
   855         return NS_ERROR_UNEXPECTED;
   856     }
   858     // wait until notified, then return
   859     rv = gService->mCondVar.Wait();
   861     return rv;
   862 }
   865 bool
   866 nsCacheProfilePrefObserver::DiskCacheEnabled()
   867 {
   868     if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory))  return false;
   869     return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
   870 }
   873 bool
   874 nsCacheProfilePrefObserver::OfflineCacheEnabled()
   875 {
   876     if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
   877         return false;
   879     return mOfflineCacheEnabled;
   880 }
   883 bool
   884 nsCacheProfilePrefObserver::MemoryCacheEnabled()
   885 {
   886     if (mMemoryCacheCapacity == 0)  return false;
   887     return mMemoryCacheEnabled;
   888 }
   891 /**
   892  * MemoryCacheCapacity
   893  *
   894  * If the browser.cache.memory.capacity preference is positive, we use that
   895  * value for the amount of memory available for the cache.
   896  *
   897  * If browser.cache.memory.capacity is zero, the memory cache is disabled.
   898  * 
   899  * If browser.cache.memory.capacity is negative or not present, we use a
   900  * formula that grows less than linearly with the amount of system memory, 
   901  * with an upper limit on the cache size. No matter how much physical RAM is
   902  * present, the default cache size would not exceed 32 MB. This maximum would
   903  * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
   904  *
   905  *   RAM   Cache
   906  *   ---   -----
   907  *   32 Mb   2 Mb
   908  *   64 Mb   4 Mb
   909  *  128 Mb   6 Mb
   910  *  256 Mb  10 Mb
   911  *  512 Mb  14 Mb
   912  * 1024 Mb  18 Mb
   913  * 2048 Mb  24 Mb
   914  * 4096 Mb  30 Mb
   915  *
   916  * The equation for this is (for cache size C and memory size K (kbytes)):
   917  *  x = log2(K) - 14
   918  *  C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
   919  *  if (C > 32) C = 32
   920  */
   922 int32_t
   923 nsCacheProfilePrefObserver::MemoryCacheCapacity()
   924 {
   925     int32_t capacity = mMemoryCacheCapacity;
   926     if (capacity >= 0) {
   927         CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
   928         return capacity;
   929     }
   931     static uint64_t bytes = PR_GetPhysicalMemorySize();
   932     CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
   934     // If getting the physical memory failed, arbitrarily assume
   935     // 32 MB of RAM. We use a low default to have a reasonable
   936     // size on all the devices we support.
   937     if (bytes == 0)
   938         bytes = 32 * 1024 * 1024;
   940     // Conversion from unsigned int64_t to double doesn't work on all platforms.
   941     // We need to truncate the value at INT64_MAX to make sure we don't
   942     // overflow.
   943     if (bytes > INT64_MAX)
   944         bytes = INT64_MAX;
   946     uint64_t kbytes = bytes >> 10;
   948     double kBytesD = double(kbytes);
   950     double x = log(kBytesD)/log(2.0) - 14;
   951     if (x > 0) {
   952         capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
   953         if (capacity > 32)
   954             capacity = 32;
   955         capacity   *= 1024;
   956     } else {
   957         capacity    = 0;
   958     }
   960     return capacity;
   961 }
   963 int32_t
   964 nsCacheProfilePrefObserver::CacheCompressionLevel()
   965 {
   966     return mCacheCompressionLevel;
   967 }
   969 /******************************************************************************
   970  * nsProcessRequestEvent
   971  *****************************************************************************/
   973 class nsProcessRequestEvent : public nsRunnable {
   974 public:
   975     nsProcessRequestEvent(nsCacheRequest *aRequest)
   976     {
   977         MOZ_EVENT_TRACER_NAME_OBJECT(aRequest, aRequest->mKey.get());
   978         MOZ_EVENT_TRACER_WAIT(aRequest, "net::cache::ProcessRequest");
   979         mRequest = aRequest;
   980     }
   982     NS_IMETHOD Run()
   983     {
   984         nsresult rv;
   986         NS_ASSERTION(mRequest->mListener,
   987                      "Sync OpenCacheEntry() posted to background thread!");
   989         nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
   990         rv = nsCacheService::gService->ProcessRequest(mRequest,
   991                                                       false,
   992                                                       nullptr);
   994         // Don't delete the request if it was queued
   995         if (!(mRequest->IsBlocking() &&
   996             rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
   997             delete mRequest;
   999         return NS_OK;
  1002 protected:
  1003     virtual ~nsProcessRequestEvent() {}
  1005 private:
  1006     nsCacheRequest *mRequest;
  1007 };
  1009 /******************************************************************************
  1010  * nsDoomEvent
  1011  *****************************************************************************/
  1013 class nsDoomEvent : public nsRunnable {
  1014 public:
  1015     nsDoomEvent(nsCacheSession *session,
  1016                 const nsACString &key,
  1017                 nsICacheListener *listener)
  1019         mKey = *session->ClientID();
  1020         mKey.Append(':');
  1021         mKey.Append(key);
  1022         mStoragePolicy = session->StoragePolicy();
  1023         mListener = listener;
  1024         mThread = do_GetCurrentThread();
  1025         // We addref the listener here and release it in nsNotifyDoomListener
  1026         // on the callers thread. If posting of nsNotifyDoomListener event fails
  1027         // we leak the listener which is better than releasing it on a wrong
  1028         // thread.
  1029         NS_IF_ADDREF(mListener);
  1032     NS_IMETHOD Run()
  1034         nsCacheServiceAutoLock lock(LOCK_TELEM(NSDOOMEVENT_RUN));
  1036         bool foundActive = true;
  1037         nsresult status = NS_ERROR_NOT_AVAILABLE;
  1038         nsCacheEntry *entry;
  1039         entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
  1040         if (!entry) {
  1041             bool collision = false;
  1042             foundActive = false;
  1043             entry = nsCacheService::gService->SearchCacheDevices(&mKey,
  1044                                                                  mStoragePolicy,
  1045                                                                  &collision);
  1048         if (entry) {
  1049             status = NS_OK;
  1050             nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
  1053         if (mListener) {
  1054             mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
  1055                               NS_DISPATCH_NORMAL);
  1056             // posted event will release the reference on the correct thread
  1057             mListener = nullptr;
  1060         return NS_OK;
  1063 private:
  1064     nsCString             mKey;
  1065     nsCacheStoragePolicy  mStoragePolicy;
  1066     nsICacheListener     *mListener;
  1067     nsCOMPtr<nsIThread>   mThread;
  1068 };
  1070 /******************************************************************************
  1071  * nsCacheService
  1072  *****************************************************************************/
  1073 nsCacheService *   nsCacheService::gService = nullptr;
  1075 NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal,
  1076                   nsIMemoryReporter)
  1078 nsCacheService::nsCacheService()
  1079     : mObserver(nullptr),
  1080       mLock("nsCacheService.mLock"),
  1081       mCondVar(mLock, "nsCacheService.mCondVar"),
  1082       mTimeStampLock("nsCacheService.mTimeStampLock"),
  1083       mInitialized(false),
  1084       mClearingEntries(false),
  1085       mEnableMemoryDevice(true),
  1086       mEnableDiskDevice(true),
  1087       mMemoryDevice(nullptr),
  1088       mDiskDevice(nullptr),
  1089       mOfflineDevice(nullptr),
  1090       mTotalEntries(0),
  1091       mCacheHits(0),
  1092       mCacheMisses(0),
  1093       mMaxKeyLength(0),
  1094       mMaxDataSize(0),
  1095       mMaxMetaSize(0),
  1096       mDeactivateFailures(0),
  1097       mDeactivatedUnboundEntries(0)
  1099     NS_ASSERTION(gService==nullptr, "multiple nsCacheService instances!");
  1100     gService = this;
  1102     // create list of cache devices
  1103     PR_INIT_CLIST(&mDoomedEntries);
  1106 nsCacheService::~nsCacheService()
  1108     if (mInitialized) // Shutdown hasn't been called yet.
  1109         (void) Shutdown();
  1111     if (mObserver) {
  1112         mObserver->Remove();
  1113         NS_RELEASE(mObserver);
  1116     gService = nullptr;
  1120 nsresult
  1121 nsCacheService::Init()
  1123     // Thie method must be called on the main thread because mCacheIOThread must
  1124     // only be modified on the main thread.
  1125     if (!NS_IsMainThread()) {
  1126         NS_ERROR("nsCacheService::Init called off the main thread");
  1127         return NS_ERROR_NOT_SAME_THREAD;
  1130     NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
  1131     if (mInitialized)
  1132         return NS_ERROR_ALREADY_INITIALIZED;
  1134     if (mozilla::net::IsNeckoChild()) {
  1135         return NS_ERROR_UNEXPECTED;
  1138     CACHE_LOG_INIT();
  1140     nsresult rv;
  1142     mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
  1143     NS_ENSURE_SUCCESS(rv, rv);
  1145     MOZ_EVENT_TRACER_NAME_OBJECT(nsCacheService::gService, "nsCacheService");
  1147     rv = NS_NewNamedThread("Cache I/O",
  1148                            getter_AddRefs(mCacheIOThread));
  1149     if (NS_FAILED(rv)) {
  1150         NS_RUNTIMEABORT("Can't create cache IO thread");
  1153     rv = nsDeleteDir::Init();
  1154     if (NS_FAILED(rv)) {
  1155         NS_WARNING("Can't initialize nsDeleteDir");
  1158     // initialize hashtable for active cache entries
  1159     rv = mActiveEntries.Init();
  1160     if (NS_FAILED(rv)) return rv;
  1162     // create profile/preference observer
  1163     if (!mObserver) {
  1164       mObserver = new nsCacheProfilePrefObserver();
  1165       NS_ADDREF(mObserver);
  1166       mObserver->Install();
  1169     mEnableDiskDevice    = mObserver->DiskCacheEnabled();
  1170     mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
  1171     mEnableMemoryDevice  = mObserver->MemoryCacheEnabled();
  1173     RegisterWeakMemoryReporter(this);
  1175     mInitialized = true;
  1176     return NS_OK;
  1179 // static
  1180 PLDHashOperator
  1181 nsCacheService::ShutdownCustomCacheDeviceEnum(const nsAString& aProfileDir,
  1182                                               nsRefPtr<nsOfflineCacheDevice>& aDevice,
  1183                                               void* aUserArg)
  1185     aDevice->Shutdown();
  1186     return PL_DHASH_REMOVE;
  1189 void
  1190 nsCacheService::Shutdown()
  1192     // This method must be called on the main thread because mCacheIOThread must
  1193     // only be modified on the main thread.
  1194     if (!NS_IsMainThread()) {
  1195         NS_RUNTIMEABORT("nsCacheService::Shutdown called off the main thread");
  1198     nsCOMPtr<nsIThread> cacheIOThread;
  1199     Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
  1201     bool shouldSanitize = false;
  1202     nsCOMPtr<nsIFile> parentDir;
  1205         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
  1206         NS_ASSERTION(mInitialized,
  1207             "can't shutdown nsCacheService unless it has been initialized.");
  1208         if (!mInitialized)
  1209             return;
  1211         mClearingEntries = true;
  1212         DoomActiveEntries(nullptr);
  1215     CloseAllStreams();
  1217     UnregisterWeakMemoryReporter(this);
  1220         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
  1221         NS_ASSERTION(mInitialized, "Bad state");
  1223         mInitialized = false;
  1225         // Clear entries
  1226         ClearDoomList();
  1228         if (mSmartSizeTimer) {
  1229             mSmartSizeTimer->Cancel();
  1230             mSmartSizeTimer = nullptr;
  1233         // Make sure to wait for any pending cache-operations before
  1234         // proceeding with destructive actions (bug #620660)
  1235         (void) SyncWithCacheIOThread();
  1237         // obtain the disk cache directory in case we need to sanitize it
  1238         parentDir = mObserver->DiskCacheParentDirectory();
  1239         shouldSanitize = mObserver->SanitizeAtShutdown();
  1241         // deallocate memory and disk caches
  1242         delete mMemoryDevice;
  1243         mMemoryDevice = nullptr;
  1245         delete mDiskDevice;
  1246         mDiskDevice = nullptr;
  1248         if (mOfflineDevice)
  1249             mOfflineDevice->Shutdown();
  1251         NS_IF_RELEASE(mOfflineDevice);
  1253         mCustomOfflineDevices.Enumerate(&nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
  1255 #ifdef PR_LOGGING
  1256         LogCacheStatistics();
  1257 #endif
  1259         mClearingEntries = false;
  1260         mCacheIOThread.swap(cacheIOThread);
  1263     if (cacheIOThread)
  1264         nsShutdownThread::BlockingShutdown(cacheIOThread);
  1266     if (shouldSanitize) {
  1267         nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
  1268         if (NS_SUCCEEDED(rv)) {
  1269             bool exists;
  1270             if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
  1271                 nsDeleteDir::DeleteDir(parentDir, false);
  1273         Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
  1274         nsDeleteDir::Shutdown(shouldSanitize);
  1275     } else {
  1276         Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
  1277         nsDeleteDir::Shutdown(shouldSanitize);
  1282 nsresult
  1283 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
  1285     nsresult  rv;
  1287     if (aOuter != nullptr)
  1288         return NS_ERROR_NO_AGGREGATION;
  1290     nsCacheService * cacheService = new nsCacheService();
  1291     if (cacheService == nullptr)
  1292         return NS_ERROR_OUT_OF_MEMORY;
  1294     NS_ADDREF(cacheService);
  1295     rv = cacheService->Init();
  1296     if (NS_SUCCEEDED(rv)) {
  1297         rv = cacheService->QueryInterface(aIID, aResult);
  1299     NS_RELEASE(cacheService);
  1300     return rv;
  1304 NS_IMETHODIMP
  1305 nsCacheService::CreateSession(const char *          clientID,
  1306                               nsCacheStoragePolicy  storagePolicy, 
  1307                               bool                  streamBased,
  1308                               nsICacheSession     **result)
  1310     *result = nullptr;
  1312     if (this == nullptr)  return NS_ERROR_NOT_AVAILABLE;
  1314     nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
  1315     if (!session)  return NS_ERROR_OUT_OF_MEMORY;
  1317     NS_ADDREF(*result = session);
  1319     return NS_OK;
  1323 nsresult
  1324 nsCacheService::EvictEntriesForSession(nsCacheSession * session)
  1326     NS_ASSERTION(gService, "nsCacheService::gService is null.");
  1327     return gService->EvictEntriesForClient(session->ClientID()->get(),
  1328                                  session->StoragePolicy());
  1331 namespace {
  1333 class EvictionNotifierRunnable : public nsRunnable
  1335 public:
  1336     EvictionNotifierRunnable(nsISupports* aSubject)
  1337         : mSubject(aSubject)
  1338     { }
  1340     NS_DECL_NSIRUNNABLE
  1342 private:
  1343     nsCOMPtr<nsISupports> mSubject;
  1344 };
  1346 NS_IMETHODIMP
  1347 EvictionNotifierRunnable::Run()
  1349     nsCOMPtr<nsIObserverService> obsSvc =
  1350         mozilla::services::GetObserverService();
  1351     if (obsSvc) {
  1352         obsSvc->NotifyObservers(mSubject,
  1353                                 NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
  1354                                 nullptr);
  1356     return NS_OK;
  1359 } // anonymous namespace
  1361 nsresult
  1362 nsCacheService::EvictEntriesForClient(const char *          clientID,
  1363                                       nsCacheStoragePolicy  storagePolicy)
  1365     nsRefPtr<EvictionNotifierRunnable> r =
  1366         new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
  1367     NS_DispatchToMainThread(r);
  1369     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
  1370     nsresult res = NS_OK;
  1372     if (storagePolicy == nsICache::STORE_ANYWHERE ||
  1373         storagePolicy == nsICache::STORE_ON_DISK) {
  1375         if (mEnableDiskDevice) {
  1376             nsresult rv = NS_OK;
  1377             if (!mDiskDevice)
  1378                 rv = CreateDiskDevice();
  1379             if (mDiskDevice)
  1380                 rv = mDiskDevice->EvictEntries(clientID);
  1381             if (NS_FAILED(rv))
  1382                 res = rv;
  1386     // Only clear the offline cache if it has been specifically asked for.
  1387     if (storagePolicy == nsICache::STORE_OFFLINE) {
  1388         if (mEnableOfflineDevice) {
  1389             nsresult rv = NS_OK;
  1390             if (!mOfflineDevice)
  1391                 rv = CreateOfflineDevice();
  1392             if (mOfflineDevice)
  1393                 rv = mOfflineDevice->EvictEntries(clientID);
  1394             if (NS_FAILED(rv))
  1395                 res = rv;
  1399     if (storagePolicy == nsICache::STORE_ANYWHERE ||
  1400         storagePolicy == nsICache::STORE_IN_MEMORY) {
  1401         // If there is no memory device, there is no need to evict it...
  1402         if (mMemoryDevice) {
  1403             nsresult rv = mMemoryDevice->EvictEntries(clientID);
  1404             if (NS_FAILED(rv))
  1405                 res = rv;
  1409     return res;
  1413 nsresult        
  1414 nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy  storagePolicy,
  1415                                           bool *              result)
  1417     if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
  1418     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
  1420     *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
  1421     return NS_OK;
  1425 nsresult
  1426 nsCacheService::DoomEntry(nsCacheSession   *session,
  1427                           const nsACString &key,
  1428                           nsICacheListener *listener)
  1430     CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
  1431                      session, PromiseFlatCString(key).get()));
  1432     NS_ASSERTION(gService, "nsCacheService::gService is null.");
  1434     if (!gService->mInitialized)
  1435         return NS_ERROR_NOT_INITIALIZED;
  1437     return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
  1441 bool          
  1442 nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy  storagePolicy)
  1444     if (gService->mEnableMemoryDevice &&
  1445         (storagePolicy == nsICache::STORE_ANYWHERE ||
  1446          storagePolicy == nsICache::STORE_IN_MEMORY)) {
  1447         return true;
  1449     if (gService->mEnableDiskDevice &&
  1450         (storagePolicy == nsICache::STORE_ANYWHERE ||
  1451          storagePolicy == nsICache::STORE_ON_DISK)) {
  1452         return true;
  1454     if (gService->mEnableOfflineDevice &&
  1455         storagePolicy == nsICache::STORE_OFFLINE) {
  1456         return true;
  1459     return false;
  1462 NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
  1464     NS_ENSURE_ARG_POINTER(visitor);
  1466     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
  1468     if (!(mEnableDiskDevice || mEnableMemoryDevice))
  1469         return NS_ERROR_NOT_AVAILABLE;
  1471     // XXX record the fact that a visitation is in progress, 
  1472     // XXX i.e. keep list of visitors in progress.
  1474     nsresult rv = NS_OK;
  1475     // If there is no memory device, there are then also no entries to visit...
  1476     if (mMemoryDevice) {
  1477         rv = mMemoryDevice->Visit(visitor);
  1478         if (NS_FAILED(rv)) return rv;
  1481     if (mEnableDiskDevice) {
  1482         if (!mDiskDevice) {
  1483             rv = CreateDiskDevice();
  1484             if (NS_FAILED(rv)) return rv;
  1486         rv = mDiskDevice->Visit(visitor);
  1487         if (NS_FAILED(rv)) return rv;
  1490     if (mEnableOfflineDevice) {
  1491         if (!mOfflineDevice) {
  1492             rv = CreateOfflineDevice();
  1493             if (NS_FAILED(rv)) return rv;
  1495         rv = mOfflineDevice->Visit(visitor);
  1496         if (NS_FAILED(rv)) return rv;
  1499     // XXX notify any shutdown process that visitation is complete for THIS visitor.
  1500     // XXX keep queue of visitors
  1502     return NS_OK;
  1505 void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification()
  1507     MOZ_ASSERT(NS_IsMainThread());
  1508     nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
  1509     if (obsvc) {
  1510         obsvc->NotifyObservers(nullptr,
  1511                                "network-clear-cache-stored-anywhere",
  1512                                nullptr);
  1516 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
  1518     if (storagePolicy == nsICache::STORE_ANYWHERE) {
  1519         // if not called on main thread, dispatch the notification to the main thread to notify observers
  1520         if (!NS_IsMainThread()) { 
  1521             nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this,
  1522                                                                &nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
  1523             NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
  1524         } else {
  1525             // else you're already on main thread - notify observers
  1526             FireClearNetworkCacheStoredAnywhereNotification(); 
  1530     NS_IMETHODIMP r;
  1531     r = EvictEntriesForClient(nullptr, storagePolicy);
  1533     // XXX: Bloody hack until we get this notifier in FF14.0:
  1534     // https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsICacheListener#onCacheEntryDoomed%28%29
  1535     if (storagePolicy == nsICache::STORE_ANYWHERE &&
  1536             NS_IsMainThread() && gService && gService->mInitialized) {
  1537         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
  1538         gService->mClearingEntries = true;
  1539         gService->DoomActiveEntries(nullptr);
  1540         gService->ClearDoomList();
  1541         (void) SyncWithCacheIOThread();
  1542         gService->mClearingEntries = false;
  1544     return r; 
  1547 NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
  1549     NS_ENSURE_ARG_POINTER(aCacheIOTarget);
  1551     // Because mCacheIOThread can only be changed on the main thread, it can be
  1552     // read from the main thread without the lock. This is useful to prevent
  1553     // blocking the main thread on other cache operations.
  1554     if (!NS_IsMainThread()) {
  1555         Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
  1558     nsresult rv;
  1559     if (mCacheIOThread) {
  1560         NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
  1561         rv = NS_OK;
  1562     } else {
  1563         *aCacheIOTarget = nullptr;
  1564         rv = NS_ERROR_NOT_AVAILABLE;
  1567     if (!NS_IsMainThread()) {
  1568         Unlock();
  1571     return rv;
  1574 /* nsICacheServiceInternal
  1575  * readonly attribute double lockHeldTime;
  1576 */
  1577 NS_IMETHODIMP nsCacheService::GetLockHeldTime(double *aLockHeldTime)
  1579     MutexAutoLock lock(mTimeStampLock);
  1581     if (mLockAcquiredTimeStamp.IsNull()) {
  1582         *aLockHeldTime = 0.0;
  1584     else {
  1585         *aLockHeldTime = 
  1586             (TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
  1589     return NS_OK;
  1592 /**
  1593  * Internal Methods
  1594  */
  1595 nsresult
  1596 nsCacheService::CreateDiskDevice()
  1598     if (!mInitialized)      return NS_ERROR_NOT_AVAILABLE;
  1599     if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
  1600     if (mDiskDevice)        return NS_OK;
  1602     mDiskDevice = new nsDiskCacheDevice;
  1603     if (!mDiskDevice)       return NS_ERROR_OUT_OF_MEMORY;
  1605     // set the preferences
  1606     mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
  1607     mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
  1608     mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
  1610     nsresult rv = mDiskDevice->Init();
  1611     if (NS_FAILED(rv)) {
  1612 #if DEBUG
  1613         printf("###\n");
  1614         printf("### mDiskDevice->Init() failed (0x%.8x)\n",
  1615                static_cast<uint32_t>(rv));
  1616         printf("###    - disabling disk cache for this session.\n");
  1617         printf("###\n");
  1618 #endif
  1619         mEnableDiskDevice = false;
  1620         delete mDiskDevice;
  1621         mDiskDevice = nullptr;
  1622         return rv;
  1625     Telemetry::Accumulate(Telemetry::DISK_CACHE_SMART_SIZE_USING_OLD_MAX,
  1626                           mObserver->ShouldUseOldMaxSmartSize());
  1628     NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
  1630     // Disk device is usually created during the startup. Delay smart size
  1631     // calculation to avoid possible massive IO caused by eviction of entries
  1632     // in case the new smart size is smaller than current cache usage.
  1633     mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1634     if (NS_SUCCEEDED(rv)) {
  1635         rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
  1636                                                1000*60*3,
  1637                                                nsITimer::TYPE_ONE_SHOT);
  1638         if (NS_FAILED(rv)) {
  1639             NS_WARNING("Failed to post smart size timer");
  1640             mSmartSizeTimer = nullptr;
  1642     } else {
  1643         NS_WARNING("Can't create smart size timer");
  1645     // Ignore state of the timer and return success since the purpose of the
  1646     // method (create the disk-device) has been fulfilled
  1648     return NS_OK;
  1651 // Runnable sent from cache thread to main thread
  1652 class nsDisableOldMaxSmartSizePrefEvent: public nsRunnable
  1654 public:
  1655     nsDisableOldMaxSmartSizePrefEvent() {}
  1657     NS_IMETHOD Run()
  1659         // Main thread may have already called nsCacheService::Shutdown
  1660         if (!nsCacheService::IsInitialized())
  1661             return NS_ERROR_NOT_AVAILABLE;
  1663         nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
  1664         if (!branch) {
  1665             return NS_ERROR_NOT_AVAILABLE;
  1668         nsresult rv = branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
  1669         if (NS_FAILED(rv)) {
  1670             NS_WARNING("Failed to disable old max smart size");
  1671             return rv;
  1674         // It is safe to call SetDiskSmartSize_Locked() without holding the lock
  1675         // when we are on main thread and nsCacheService is initialized.
  1676         nsCacheService::gService->SetDiskSmartSize_Locked();
  1678         if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch, false)) {
  1679             rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
  1680             if (NS_FAILED(rv)) {
  1681                 NS_WARNING("Failed to set cache capacity pref");
  1685         return NS_OK;
  1687 };
  1689 void
  1690 nsCacheService::MarkStartingFresh()
  1692     if (!gService->mObserver->ShouldUseOldMaxSmartSize()) {
  1693         // Already using new max, nothing to do here
  1694         return;
  1697     gService->mObserver->SetUseNewMaxSmartSize(true);
  1699     // We always dispatch an event here because we don't want to deal with lock
  1700     // reentrance issues.
  1701     NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
  1704 nsresult
  1705 nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
  1707     if (!mOfflineDevice) {
  1708         nsresult rv = CreateOfflineDevice();
  1709         NS_ENSURE_SUCCESS(rv, rv);
  1712     NS_ADDREF(*aDevice = mOfflineDevice);
  1713     return NS_OK;
  1716 nsresult
  1717 nsCacheService::GetCustomOfflineDevice(nsIFile *aProfileDir,
  1718                                        int32_t aQuota,
  1719                                        nsOfflineCacheDevice **aDevice)
  1721     nsresult rv;
  1723     nsAutoString profilePath;
  1724     rv = aProfileDir->GetPath(profilePath);
  1725     NS_ENSURE_SUCCESS(rv, rv);
  1727     if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
  1728         rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
  1729         NS_ENSURE_SUCCESS(rv, rv);
  1731         (*aDevice)->SetAutoShutdown();
  1732         mCustomOfflineDevices.Put(profilePath, *aDevice);
  1735     return NS_OK;
  1738 nsresult
  1739 nsCacheService::CreateOfflineDevice()
  1741     CACHE_LOG_ALWAYS(("Creating default offline device"));
  1743     if (mOfflineDevice)        return NS_OK;
  1744     if (!nsCacheService::IsInitialized()) {
  1745         return NS_ERROR_NOT_AVAILABLE;
  1748     nsresult rv = CreateCustomOfflineDevice(
  1749         mObserver->OfflineCacheParentDirectory(),
  1750         mObserver->OfflineCacheCapacity(),
  1751         &mOfflineDevice);
  1752     NS_ENSURE_SUCCESS(rv, rv);
  1754     return NS_OK;
  1757 nsresult
  1758 nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
  1759                                           int32_t aQuota,
  1760                                           nsOfflineCacheDevice **aDevice)
  1762     NS_ENSURE_ARG(aProfileDir);
  1764 #if defined(PR_LOGGING)
  1765     nsAutoCString profilePath;
  1766     aProfileDir->GetNativePath(profilePath);
  1767     CACHE_LOG_ALWAYS(("Creating custom offline device, %s, %d",
  1768                       profilePath.BeginReading(), aQuota));
  1769 #endif
  1771     if (!mInitialized)         return NS_ERROR_NOT_AVAILABLE;
  1772     if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
  1774     *aDevice = new nsOfflineCacheDevice;
  1776     NS_ADDREF(*aDevice);
  1778     // set the preferences
  1779     (*aDevice)->SetCacheParentDirectory(aProfileDir);
  1780     (*aDevice)->SetCapacity(aQuota);
  1782     nsresult rv = (*aDevice)->InitWithSqlite(mStorageService);
  1783     if (NS_FAILED(rv)) {
  1784         CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8x)\n", rv));
  1785         CACHE_LOG_DEBUG(("    - disabling offline cache for this session.\n"));
  1787         NS_RELEASE(*aDevice);
  1789     return rv;
  1792 nsresult
  1793 nsCacheService::CreateMemoryDevice()
  1795     if (!mInitialized)        return NS_ERROR_NOT_AVAILABLE;
  1796     if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
  1797     if (mMemoryDevice)        return NS_OK;
  1799     mMemoryDevice = new nsMemoryCacheDevice;
  1800     if (!mMemoryDevice)       return NS_ERROR_OUT_OF_MEMORY;
  1802     // set preference
  1803     int32_t capacity = mObserver->MemoryCacheCapacity();
  1804     CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
  1805     mMemoryDevice->SetCapacity(capacity);
  1806     mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
  1808     nsresult rv = mMemoryDevice->Init();
  1809     if (NS_FAILED(rv)) {
  1810         NS_WARNING("Initialization of Memory Cache failed.");
  1811         delete mMemoryDevice;
  1812         mMemoryDevice = nullptr;
  1815     return rv;
  1818 nsresult
  1819 nsCacheService::RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice)
  1821     nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
  1822     if (!profileDir)
  1823         return NS_ERROR_UNEXPECTED;
  1825     nsAutoString profilePath;
  1826     nsresult rv = profileDir->GetPath(profilePath);
  1827     NS_ENSURE_SUCCESS(rv, rv);
  1829     mCustomOfflineDevices.Remove(profilePath);
  1830     return NS_OK;
  1833 nsresult
  1834 nsCacheService::CreateRequest(nsCacheSession *   session,
  1835                               const nsACString & clientKey,
  1836                               nsCacheAccessMode  accessRequested,
  1837                               bool               blockingMode,
  1838                               nsICacheListener * listener,
  1839                               nsCacheRequest **  request)
  1841     NS_ASSERTION(request, "CreateRequest: request is null");
  1843     nsAutoCString key(*session->ClientID());
  1844     key.Append(':');
  1845     key.Append(clientKey);
  1847     if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
  1849     // create request
  1850     *request = new nsCacheRequest(key, listener, accessRequested,
  1851                                   blockingMode, session);
  1853     if (!listener)  return NS_OK;  // we're sync, we're done.
  1855     // get the request's thread
  1856     (*request)->mThread = do_GetCurrentThread();
  1858     return NS_OK;
  1862 class nsCacheListenerEvent : public nsRunnable
  1864 public:
  1865     nsCacheListenerEvent(nsICacheListener *listener,
  1866                          nsICacheEntryDescriptor *descriptor,
  1867                          nsCacheAccessMode accessGranted,
  1868                          nsresult status)
  1869         : mListener(listener)      // transfers reference
  1870         , mDescriptor(descriptor)  // transfers reference (may be null)
  1871         , mAccessGranted(accessGranted)
  1872         , mStatus(status)
  1873     {}
  1875     NS_IMETHOD Run()
  1877         mozilla::eventtracer::AutoEventTracer tracer(
  1878             static_cast<nsIRunnable*>(this),
  1879             eventtracer::eExec,
  1880             eventtracer::eDone,
  1881             "net::cache::OnCacheEntryAvailable");
  1883         mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
  1885         NS_RELEASE(mListener);
  1886         NS_IF_RELEASE(mDescriptor);
  1887         return NS_OK;
  1890 private:
  1891     // We explicitly leak mListener or mDescriptor if Run is not called
  1892     // because otherwise we cannot guarantee that they are destroyed on
  1893     // the right thread.
  1895     nsICacheListener        *mListener;
  1896     nsICacheEntryDescriptor *mDescriptor;
  1897     nsCacheAccessMode        mAccessGranted;
  1898     nsresult                 mStatus;
  1899 };
  1902 nsresult
  1903 nsCacheService::NotifyListener(nsCacheRequest *          request,
  1904                                nsICacheEntryDescriptor * descriptor,
  1905                                nsCacheAccessMode         accessGranted,
  1906                                nsresult                  status)
  1908     NS_ASSERTION(request->mThread, "no thread set in async request!");
  1910     // Swap ownership, and release listener on target thread...
  1911     nsICacheListener *listener = request->mListener;
  1912     request->mListener = nullptr;
  1914     nsCOMPtr<nsIRunnable> ev =
  1915             new nsCacheListenerEvent(listener, descriptor,
  1916                                      accessGranted, status);
  1917     if (!ev) {
  1918         // Better to leak listener and descriptor if we fail because we don't
  1919         // want to destroy them inside the cache service lock or on potentially
  1920         // the wrong thread.
  1921         return NS_ERROR_OUT_OF_MEMORY;
  1924     MOZ_EVENT_TRACER_NAME_OBJECT(ev.get(), request->mKey.get());
  1925     MOZ_EVENT_TRACER_WAIT(ev.get(), "net::cache::OnCacheEntryAvailable");
  1926     return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
  1930 nsresult
  1931 nsCacheService::ProcessRequest(nsCacheRequest *           request,
  1932                                bool                       calledFromOpenCacheEntry,
  1933                                nsICacheEntryDescriptor ** result)
  1935     mozilla::eventtracer::AutoEventTracer tracer(
  1936         request,
  1937         eventtracer::eExec,
  1938         eventtracer::eDone,
  1939         "net::cache::ProcessRequest");
  1941     // !!! must be called with mLock held !!!
  1942     nsresult           rv;
  1943     nsCacheEntry *     entry = nullptr;
  1944     nsCacheEntry *     doomedEntry = nullptr;
  1945     nsCacheAccessMode  accessGranted = nsICache::ACCESS_NONE;
  1946     if (result) *result = nullptr;
  1948     while(1) {  // Activate entry loop
  1949         rv = ActivateEntry(request, &entry, &doomedEntry);  // get the entry for this request
  1950         if (NS_FAILED(rv))  break;
  1952         while(1) { // Request Access loop
  1953             NS_ASSERTION(entry, "no entry in Request Access loop!");
  1954             // entry->RequestAccess queues request on entry
  1955             rv = entry->RequestAccess(request, &accessGranted);
  1956             if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
  1958             if (request->IsBlocking()) {
  1959                 if (request->mListener) {
  1960                     // async exits - validate, doom, or close will resume
  1961                     return rv;
  1964                 // XXX this is probably wrong...
  1965                 Unlock();
  1966                 rv = request->WaitForValidation();
  1967                 Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
  1970             PR_REMOVE_AND_INIT_LINK(request);
  1971             if (NS_FAILED(rv)) break;   // non-blocking mode returns WAIT_FOR_VALIDATION error
  1972             // okay, we're ready to process this request, request access again
  1974         if (rv != NS_ERROR_CACHE_ENTRY_DOOMED)  break;
  1976         if (entry->IsNotInUse()) {
  1977             // this request was the last one keeping it around, so get rid of it
  1978             DeactivateEntry(entry);
  1980         // loop back around to look for another entry
  1983     if (NS_SUCCEEDED(rv) && request->mProfileDir) {
  1984         // Custom cache directory has been demanded.  Preset the cache device.
  1985         if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
  1986             // Failsafe check: this is implemented only for offline cache atm.
  1987             rv = NS_ERROR_FAILURE;
  1988         } else {
  1989             nsRefPtr<nsOfflineCacheDevice> customCacheDevice;
  1990             rv = GetCustomOfflineDevice(request->mProfileDir, -1,
  1991                                         getter_AddRefs(customCacheDevice));
  1992             if (NS_SUCCEEDED(rv))
  1993                 entry->SetCustomCacheDevice(customCacheDevice);
  1997     nsICacheEntryDescriptor *descriptor = nullptr;
  1999     if (NS_SUCCEEDED(rv))
  2000         rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
  2002     // If doomedEntry is set, ActivatEntry() doomed an existing entry and
  2003     // created a new one for that cache-key. However, any pending requests
  2004     // on the doomed entry were not processed and we need to do that here.
  2005     // This must be done after adding the created entry to list of active
  2006     // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
  2007     // (see bug ##561313). It is also important to do this after creating a
  2008     // descriptor for this request, or some other request may end up being
  2009     // executed first for the newly created entry.
  2010     // Finally, it is worth to emphasize that if doomedEntry is set,
  2011     // ActivateEntry() created a new entry for the request, which will be
  2012     // initialized by RequestAccess() and they both should have returned NS_OK.
  2013     if (doomedEntry) {
  2014         (void) ProcessPendingRequests(doomedEntry);
  2015         if (doomedEntry->IsNotInUse())
  2016             DeactivateEntry(doomedEntry);
  2017         doomedEntry = nullptr;
  2020     if (request->mListener) {  // Asynchronous
  2022         if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
  2023             return rv;  // skip notifying listener, just return rv to caller
  2025         // call listener to report error or descriptor
  2026         nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
  2027         if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
  2028             rv = rv2;  // trigger delete request
  2030     } else {        // Synchronous
  2031         *result = descriptor;
  2033     return rv;
  2037 nsresult
  2038 nsCacheService::OpenCacheEntry(nsCacheSession *           session,
  2039                                const nsACString &         key,
  2040                                nsCacheAccessMode          accessRequested,
  2041                                bool                       blockingMode,
  2042                                nsICacheListener *         listener,
  2043                                nsICacheEntryDescriptor ** result)
  2045     CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
  2046                      session, PromiseFlatCString(key).get(), accessRequested,
  2047                      blockingMode));
  2048     NS_ASSERTION(gService, "nsCacheService::gService is null.");
  2049     if (result)
  2050         *result = nullptr;
  2052     if (!gService->mInitialized)
  2053         return NS_ERROR_NOT_INITIALIZED;
  2055     nsCacheRequest * request = nullptr;
  2057     nsresult rv = gService->CreateRequest(session,
  2058                                           key,
  2059                                           accessRequested,
  2060                                           blockingMode,
  2061                                           listener,
  2062                                           &request);
  2063     if (NS_FAILED(rv))  return rv;
  2065     CACHE_LOG_DEBUG(("Created request %p\n", request));
  2067     // Process the request on the background thread if we are on the main thread
  2068     // and the the request is asynchronous
  2069     if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
  2070         nsCOMPtr<nsIRunnable> ev =
  2071             new nsProcessRequestEvent(request);
  2072         rv = DispatchToCacheIOThread(ev);
  2074         // delete request if we didn't post the event
  2075         if (NS_FAILED(rv))
  2076             delete request;
  2078     else {
  2080         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
  2081         rv = gService->ProcessRequest(request, true, result);
  2083         // delete requests that have completed
  2084         if (!(listener && blockingMode &&
  2085             (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
  2086             delete request;
  2089     return rv;
  2093 nsresult
  2094 nsCacheService::ActivateEntry(nsCacheRequest * request, 
  2095                               nsCacheEntry ** result,
  2096                               nsCacheEntry ** doomedEntry)
  2098     CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
  2099     if (!mInitialized || mClearingEntries)
  2100         return NS_ERROR_NOT_AVAILABLE;
  2102     mozilla::eventtracer::AutoEventTracer tracer(
  2103         request,
  2104         eventtracer::eExec,
  2105         eventtracer::eDone,
  2106         "net::cache::ActivateEntry");
  2108     nsresult        rv = NS_OK;
  2110     NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
  2111     if (result) *result = nullptr;
  2112     if (doomedEntry) *doomedEntry = nullptr;
  2113     if ((!request) || (!result) || (!doomedEntry))
  2114         return NS_ERROR_NULL_POINTER;
  2116     // check if the request can be satisfied
  2117     if (!mEnableMemoryDevice && !request->IsStreamBased())
  2118         return NS_ERROR_FAILURE;
  2119     if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
  2120         return NS_ERROR_FAILURE;
  2122     // search active entries (including those not bound to device)
  2123     nsCacheEntry *entry = mActiveEntries.GetEntry(&(request->mKey));
  2124     CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
  2126     if (!entry) {
  2127         // search cache devices for entry
  2128         bool collision = false;
  2129         entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(), &collision);
  2130         CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
  2131                          request, entry));
  2132         // When there is a hashkey collision just refuse to cache it...
  2133         if (collision) return NS_ERROR_CACHE_IN_USE;
  2135         if (entry)  entry->MarkInitialized();
  2136     } else {
  2137         NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
  2140     if (entry) {
  2141         ++mCacheHits;
  2142         entry->Fetched();
  2143     } else {
  2144         ++mCacheMisses;
  2147     if (entry &&
  2148         ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
  2149          ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
  2150           (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
  2151            request->WillDoomEntriesIfExpired()))))
  2154         // this is FORCE-WRITE request or the entry has expired
  2155         // we doom entry without processing pending requests, but store it in
  2156         // doomedEntry which causes pending requests to be processed below
  2157         rv = DoomEntry_Internal(entry, false);
  2158         *doomedEntry = entry;
  2159         if (NS_FAILED(rv)) {
  2160             // XXX what to do?  Increment FailedDooms counter?
  2162         entry = nullptr;
  2165     if (!entry) {
  2166         if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
  2167             // this is a READ-ONLY request
  2168             rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
  2169             goto error;
  2172         entry = new nsCacheEntry(request->mKey,
  2173                                  request->IsStreamBased(),
  2174                                  request->StoragePolicy());
  2175         if (!entry)
  2176             return NS_ERROR_OUT_OF_MEMORY;
  2178         if (request->IsPrivate())
  2179             entry->MarkPrivate();
  2181         entry->Fetched();
  2182         ++mTotalEntries;
  2184         // XXX  we could perform an early bind in some cases based on storage policy
  2187     if (!entry->IsActive()) {
  2188         rv = mActiveEntries.AddEntry(entry);
  2189         if (NS_FAILED(rv)) goto error;
  2190         CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
  2191         entry->MarkActive();  // mark entry active, because it's now in mActiveEntries
  2193     *result = entry;
  2194     return NS_OK;
  2196  error:
  2197     *result = nullptr;
  2198     delete entry;
  2199     return rv;
  2203 nsCacheEntry *
  2204 nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
  2206     Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
  2207     nsCacheEntry * entry = nullptr;
  2209     MOZ_EVENT_TRACER_NAME_OBJECT(key, key->BeginReading());
  2210     eventtracer::AutoEventTracer searchCacheDevices(
  2211         key,
  2212         eventtracer::eExec,
  2213         eventtracer::eDone,
  2214         "net::cache::SearchCacheDevices");
  2216     CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
  2218     *collision = false;
  2219     if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
  2220         // If there is no memory device, then there is nothing to search...
  2221         if (mMemoryDevice) {
  2222             entry = mMemoryDevice->FindEntry(key, collision);
  2223             CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
  2224                              "collision: %d\n", key->get(), entry, collision));
  2228     if (!entry && 
  2229         ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
  2231         if (mEnableDiskDevice) {
  2232             if (!mDiskDevice) {
  2233                 nsresult rv = CreateDiskDevice();
  2234                 if (NS_FAILED(rv))
  2235                     return nullptr;
  2238             entry = mDiskDevice->FindEntry(key, collision);
  2242     if (!entry && (policy == nsICache::STORE_OFFLINE ||
  2243                    (policy == nsICache::STORE_ANYWHERE &&
  2244                     gIOService->IsOffline()))) {
  2246         if (mEnableOfflineDevice) {
  2247             if (!mOfflineDevice) {
  2248                 nsresult rv = CreateOfflineDevice();
  2249                 if (NS_FAILED(rv))
  2250                     return nullptr;
  2253             entry = mOfflineDevice->FindEntry(key, collision);
  2257     return entry;
  2261 nsCacheDevice *
  2262 nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
  2264     nsCacheDevice * device = entry->CacheDevice();
  2265     // return device if found, possibly null if the entry is doomed i.e prevent
  2266     // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
  2267     if (device || entry->IsDoomed())  return device;
  2269     int64_t predictedDataSize = entry->PredictedDataSize();
  2270     if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
  2271         // this is the default
  2272         if (!mDiskDevice) {
  2273             (void)CreateDiskDevice();  // ignore the error (check for mDiskDevice instead)
  2276         if (mDiskDevice) {
  2277             // Bypass the cache if Content-Length says the entry will be too big
  2278             if (predictedDataSize != -1 &&
  2279                 mDiskDevice->EntryIsTooBig(predictedDataSize)) {
  2280                 DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
  2281                 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
  2282                 return nullptr;
  2285             entry->MarkBinding();  // enter state of binding
  2286             nsresult rv = mDiskDevice->BindEntry(entry);
  2287             entry->ClearBinding(); // exit state of binding
  2288             if (NS_SUCCEEDED(rv))
  2289                 device = mDiskDevice;
  2293     // if we can't use mDiskDevice, try mMemoryDevice
  2294     if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {        
  2295         if (!mMemoryDevice) {
  2296             (void)CreateMemoryDevice();  // ignore the error (check for mMemoryDevice instead)
  2298         if (mMemoryDevice) {
  2299             // Bypass the cache if Content-Length says entry will be too big
  2300             if (predictedDataSize != -1 &&
  2301                 mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
  2302                 DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
  2303                 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
  2304                 return nullptr;
  2307             entry->MarkBinding();  // enter state of binding
  2308             nsresult rv = mMemoryDevice->BindEntry(entry);
  2309             entry->ClearBinding(); // exit state of binding
  2310             if (NS_SUCCEEDED(rv))
  2311                 device = mMemoryDevice;
  2315     if (!device && entry->IsStreamData() &&
  2316         entry->IsAllowedOffline() && mEnableOfflineDevice) {
  2317         if (!mOfflineDevice) {
  2318             (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
  2321         device = entry->CustomCacheDevice()
  2322                ? entry->CustomCacheDevice()
  2323                : mOfflineDevice;
  2325         if (device) {
  2326             entry->MarkBinding();
  2327             nsresult rv = device->BindEntry(entry);
  2328             entry->ClearBinding();
  2329             if (NS_FAILED(rv))
  2330                 device = nullptr;
  2334     if (device) 
  2335         entry->SetCacheDevice(device);
  2336     return device;
  2339 nsresult
  2340 nsCacheService::DoomEntry(nsCacheEntry * entry)
  2342     return gService->DoomEntry_Internal(entry, true);
  2346 nsresult
  2347 nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
  2348                                    bool doProcessPendingRequests)
  2350     if (entry->IsDoomed())  return NS_OK;
  2352     CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
  2353     nsresult  rv = NS_OK;
  2354     entry->MarkDoomed();
  2356     NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
  2357     nsCacheDevice * device = entry->CacheDevice();
  2358     if (device)  device->DoomEntry(entry);
  2360     if (entry->IsActive()) {
  2361         // remove from active entries
  2362         mActiveEntries.RemoveEntry(entry);
  2363         CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
  2364         entry->MarkInactive();
  2367     // put on doom list to wait for descriptors to close
  2368     NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
  2369     PR_APPEND_LINK(entry, &mDoomedEntries);
  2371     // handle pending requests only if we're supposed to
  2372     if (doProcessPendingRequests) {
  2373         // tell pending requests to get on with their lives...
  2374         rv = ProcessPendingRequests(entry);
  2376         // All requests have been removed, but there may still be open descriptors
  2377         if (entry->IsNotInUse()) {
  2378             DeactivateEntry(entry); // tell device to get rid of it
  2381     return rv;
  2385 void
  2386 nsCacheService::OnProfileShutdown(bool cleanse)
  2388     if (!gService)  return;
  2389     if (!gService->mInitialized) {
  2390         // The cache service has been shut down, but someone is still holding
  2391         // a reference to it. Ignore this call.
  2392         return;
  2395         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
  2396         gService->mClearingEntries = true;
  2397         gService->DoomActiveEntries(nullptr);
  2400     gService->CloseAllStreams();
  2402     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
  2403     gService->ClearDoomList();
  2405     // Make sure to wait for any pending cache-operations before
  2406     // proceeding with destructive actions (bug #620660)
  2407     (void) SyncWithCacheIOThread();
  2409     if (gService->mDiskDevice && gService->mEnableDiskDevice) {
  2410         if (cleanse)
  2411             gService->mDiskDevice->EvictEntries(nullptr);
  2413         gService->mDiskDevice->Shutdown();
  2415     gService->mEnableDiskDevice = false;
  2417     if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
  2418         if (cleanse)
  2419             gService->mOfflineDevice->EvictEntries(nullptr);
  2421         gService->mOfflineDevice->Shutdown();
  2423     gService->mCustomOfflineDevices.Enumerate(
  2424         &nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
  2426     gService->mEnableOfflineDevice = false;
  2428     if (gService->mMemoryDevice) {
  2429         // clear memory cache
  2430         gService->mMemoryDevice->EvictEntries(nullptr);
  2433     gService->mClearingEntries = false;
  2437 void
  2438 nsCacheService::OnProfileChanged()
  2440     if (!gService)  return;
  2442     CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
  2444     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
  2446     gService->mEnableDiskDevice    = gService->mObserver->DiskCacheEnabled();
  2447     gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
  2448     gService->mEnableMemoryDevice  = gService->mObserver->MemoryCacheEnabled();
  2450     if (gService->mDiskDevice) {
  2451         gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
  2452         gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
  2454         // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
  2455         nsresult rv = gService->mDiskDevice->Init();
  2456         if (NS_FAILED(rv)) {
  2457             NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
  2458             gService->mEnableDiskDevice = false;
  2459             // XXX delete mDiskDevice?
  2463     if (gService->mOfflineDevice) {
  2464         gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
  2465         gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
  2467         // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
  2468         nsresult rv = gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
  2469         if (NS_FAILED(rv)) {
  2470             NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
  2471             gService->mEnableOfflineDevice = false;
  2472             // XXX delete mOfflineDevice?
  2476     // If memoryDevice exists, reset its size to the new profile
  2477     if (gService->mMemoryDevice) {
  2478         if (gService->mEnableMemoryDevice) {
  2479             // make sure that capacity is reset to the right value
  2480             int32_t capacity = gService->mObserver->MemoryCacheCapacity();
  2481             CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
  2482                              capacity));
  2483             gService->mMemoryDevice->SetCapacity(capacity);
  2484         } else {
  2485             // tell memory device to evict everything
  2486             CACHE_LOG_DEBUG(("memory device disabled\n"));
  2487             gService->mMemoryDevice->SetCapacity(0);
  2488             // Don't delete memory device, because some entries may be active still...
  2494 void
  2495 nsCacheService::SetDiskCacheEnabled(bool    enabled)
  2497     if (!gService)  return;
  2498     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
  2499     gService->mEnableDiskDevice = enabled;
  2503 void
  2504 nsCacheService::SetDiskCacheCapacity(int32_t  capacity)
  2506     if (!gService)  return;
  2507     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
  2509     if (gService->mDiskDevice) {
  2510         gService->mDiskDevice->SetCapacity(capacity);
  2513     gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
  2516 void
  2517 nsCacheService::SetDiskCacheMaxEntrySize(int32_t  maxSize)
  2519     if (!gService)  return;
  2520     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
  2522     if (gService->mDiskDevice) {
  2523         gService->mDiskDevice->SetMaxEntrySize(maxSize);
  2527 void
  2528 nsCacheService::SetMemoryCacheMaxEntrySize(int32_t  maxSize)
  2530     if (!gService)  return;
  2531     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
  2533     if (gService->mMemoryDevice) {
  2534         gService->mMemoryDevice->SetMaxEntrySize(maxSize);
  2538 void
  2539 nsCacheService::SetOfflineCacheEnabled(bool    enabled)
  2541     if (!gService)  return;
  2542     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
  2543     gService->mEnableOfflineDevice = enabled;
  2546 void
  2547 nsCacheService::SetOfflineCacheCapacity(int32_t  capacity)
  2549     if (!gService)  return;
  2550     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
  2552     if (gService->mOfflineDevice) {
  2553         gService->mOfflineDevice->SetCapacity(capacity);
  2556     gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
  2560 void
  2561 nsCacheService::SetMemoryCache()
  2563     if (!gService)  return;
  2565     CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
  2567     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
  2569     gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
  2571     if (gService->mEnableMemoryDevice) {
  2572         if (gService->mMemoryDevice) {
  2573             int32_t capacity = gService->mObserver->MemoryCacheCapacity();
  2574             // make sure that capacity is reset to the right value
  2575             CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
  2576                              capacity));
  2577             gService->mMemoryDevice->SetCapacity(capacity);
  2579     } else {
  2580         if (gService->mMemoryDevice) {
  2581             // tell memory device to evict everything
  2582             CACHE_LOG_DEBUG(("memory device disabled\n"));
  2583             gService->mMemoryDevice->SetCapacity(0);
  2584             // Don't delete memory device, because some entries may be active still...
  2590 /******************************************************************************
  2591  * static methods for nsCacheEntryDescriptor
  2592  *****************************************************************************/
  2593 void
  2594 nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
  2596     // ask entry to remove descriptor
  2597     nsCacheEntry * entry = descriptor->CacheEntry();
  2598     bool doomEntry;
  2599     bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
  2601     if (!entry->IsValid()) {
  2602         gService->ProcessPendingRequests(entry);
  2605     if (doomEntry) {
  2606         gService->DoomEntry_Internal(entry, true);
  2607         return;
  2610     if (!stillActive) {
  2611         gService->DeactivateEntry(entry);
  2616 nsresult        
  2617 nsCacheService::GetFileForEntry(nsCacheEntry *         entry,
  2618                                 nsIFile **             result)
  2620     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
  2621     if (!device)  return  NS_ERROR_UNEXPECTED;
  2623     return device->GetFileForEntry(entry, result);
  2627 nsresult
  2628 nsCacheService::OpenInputStreamForEntry(nsCacheEntry *     entry,
  2629                                         nsCacheAccessMode  mode,
  2630                                         uint32_t           offset,
  2631                                         nsIInputStream  ** result)
  2633     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
  2634     if (!device)  return  NS_ERROR_UNEXPECTED;
  2636     return device->OpenInputStreamForEntry(entry, mode, offset, result);
  2639 nsresult
  2640 nsCacheService::OpenOutputStreamForEntry(nsCacheEntry *     entry,
  2641                                          nsCacheAccessMode  mode,
  2642                                          uint32_t           offset,
  2643                                          nsIOutputStream ** result)
  2645     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
  2646     if (!device)  return  NS_ERROR_UNEXPECTED;
  2648     return device->OpenOutputStreamForEntry(entry, mode, offset, result);
  2652 nsresult
  2653 nsCacheService::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
  2655     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
  2656     if (!device)  return  NS_ERROR_UNEXPECTED;
  2658     return device->OnDataSizeChange(entry, deltaSize);
  2661 void
  2662 nsCacheService::LockAcquired()
  2664     MutexAutoLock lock(mTimeStampLock);
  2665     mLockAcquiredTimeStamp = TimeStamp::Now();
  2668 void
  2669 nsCacheService::LockReleased()
  2671     MutexAutoLock lock(mTimeStampLock);
  2672     mLockAcquiredTimeStamp = TimeStamp();
  2675 void
  2676 nsCacheService::Lock(mozilla::Telemetry::ID mainThreadLockerID)
  2678     mozilla::Telemetry::ID lockerID;
  2679     mozilla::Telemetry::ID generalID;
  2681     if (NS_IsMainThread()) {
  2682         lockerID = mainThreadLockerID;
  2683         generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
  2684     } else {
  2685         lockerID = mozilla::Telemetry::HistogramCount;
  2686         generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
  2689     TimeStamp start(TimeStamp::Now());
  2690     MOZ_EVENT_TRACER_WAIT(nsCacheService::gService, "net::cache::lock");
  2692     gService->mLock.Lock();
  2693     gService->LockAcquired();
  2695     TimeStamp stop(TimeStamp::Now());
  2696     MOZ_EVENT_TRACER_EXEC(nsCacheService::gService, "net::cache::lock");
  2698     // Telemetry isn't thread safe on its own, but this is OK because we're
  2699     // protecting it with the cache lock. 
  2700     if (lockerID != mozilla::Telemetry::HistogramCount) {
  2701         mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
  2703     mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
  2706 void
  2707 nsCacheService::Unlock()
  2709     gService->mLock.AssertCurrentThreadOwns();
  2711     nsTArray<nsISupports*> doomed;
  2712     doomed.SwapElements(gService->mDoomedObjects);
  2714     gService->LockReleased();
  2715     gService->mLock.Unlock();
  2717     MOZ_EVENT_TRACER_DONE(nsCacheService::gService, "net::cache::lock");
  2719     for (uint32_t i = 0; i < doomed.Length(); ++i)
  2720         doomed[i]->Release();
  2723 void
  2724 nsCacheService::ReleaseObject_Locked(nsISupports * obj,
  2725                                      nsIEventTarget * target)
  2727     gService->mLock.AssertCurrentThreadOwns();
  2729     bool isCur;
  2730     if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
  2731         gService->mDoomedObjects.AppendElement(obj);
  2732     } else {
  2733         NS_ProxyRelease(target, obj);
  2738 nsresult
  2739 nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
  2741     entry->SetData(element);
  2742     entry->TouchData();
  2743     return NS_OK;
  2747 nsresult
  2748 nsCacheService::ValidateEntry(nsCacheEntry * entry)
  2750     nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
  2751     if (!device)  return  NS_ERROR_UNEXPECTED;
  2753     entry->MarkValid();
  2754     nsresult rv = gService->ProcessPendingRequests(entry);
  2755     NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
  2756     // XXX what else should be done?
  2758     return rv;
  2762 int32_t
  2763 nsCacheService::CacheCompressionLevel()
  2765     int32_t level = gService->mObserver->CacheCompressionLevel();
  2766     return level;
  2770 void
  2771 nsCacheService::DeactivateEntry(nsCacheEntry * entry)
  2773     CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
  2774     nsresult  rv = NS_OK;
  2775     NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
  2776     nsCacheDevice * device = nullptr;
  2778     if (mMaxDataSize < entry->DataSize() )     mMaxDataSize = entry->DataSize();
  2779     if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
  2781     if (entry->IsDoomed()) {
  2782         // remove from Doomed list
  2783         PR_REMOVE_AND_INIT_LINK(entry);
  2784     } else if (entry->IsActive()) {
  2785         // remove from active entries
  2786         mActiveEntries.RemoveEntry(entry);
  2787         CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
  2788                          entry));
  2789         entry->MarkInactive();
  2791         // bind entry if necessary to store meta-data
  2792         device = EnsureEntryHasDevice(entry); 
  2793         if (!device) {
  2794             CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
  2795                              "entry %p\n",
  2796                              entry));
  2797             NS_WARNING("DeactivateEntry: unable to bind active entry\n");
  2798             return;
  2800     } else {
  2801         // if mInitialized == false,
  2802         // then we're shutting down and this state is okay.
  2803         NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
  2806     device = entry->CacheDevice();
  2807     if (device) {
  2808         rv = device->DeactivateEntry(entry);
  2809         if (NS_FAILED(rv)) {
  2810             // increment deactivate failure count
  2811             ++mDeactivateFailures;
  2813     } else {
  2814         // increment deactivating unbound entry statistic
  2815         ++mDeactivatedUnboundEntries;
  2816         delete entry; // because no one else will
  2821 nsresult
  2822 nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
  2824     mozilla::eventtracer::AutoEventTracer tracer(
  2825         entry,
  2826         eventtracer::eExec,
  2827         eventtracer::eDone,
  2828         "net::cache::ProcessPendingRequests");
  2830     nsresult            rv = NS_OK;
  2831     nsCacheRequest *    request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
  2832     nsCacheRequest *    nextRequest;
  2833     bool                newWriter = false;
  2835     CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
  2836                     (entry->IsInitialized()?"" : "Un"),
  2837                     (entry->IsDoomed()?"DOOMED" : ""),
  2838                     (entry->IsValid()? "V":"Inv"), entry));
  2840     if (request == &entry->mRequestQ)  return NS_OK;    // no queued requests
  2842     if (!entry->IsDoomed() && entry->IsInvalid()) {
  2843         // 1st descriptor closed w/o MarkValid()
  2844         NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
  2846 #if DEBUG
  2847         // verify no ACCESS_WRITE requests(shouldn't have any of these)
  2848         while (request != &entry->mRequestQ) {
  2849             NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
  2850                          "ACCESS_WRITE request should have been given a new entry");
  2851             request = (nsCacheRequest *)PR_NEXT_LINK(request);
  2853         request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);        
  2854 #endif
  2855         // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
  2856         while (request != &entry->mRequestQ) {
  2857             if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
  2858                 newWriter = true;
  2859                 CACHE_LOG_DEBUG(("  promoting request %p to 1st writer\n", request));
  2860                 break;
  2863             request = (nsCacheRequest *)PR_NEXT_LINK(request);
  2866         if (request == &entry->mRequestQ)   // no requests asked for ACCESS_READ_WRITE, back to top
  2867             request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
  2869         // XXX what should we do if there are only READ requests in queue?
  2870         // XXX serialize their accesses, give them only read access, but force them to check validate flag?
  2871         // XXX or do readers simply presume the entry is valid
  2872         // See fix for bug #467392 below
  2875     nsCacheAccessMode  accessGranted = nsICache::ACCESS_NONE;
  2877     while (request != &entry->mRequestQ) {
  2878         nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
  2879         CACHE_LOG_DEBUG(("  %sync request %p for %p\n",
  2880                         (request->mListener?"As":"S"), request, entry));
  2882         if (request->mListener) {
  2884             // Async request
  2885             PR_REMOVE_AND_INIT_LINK(request);
  2887             if (entry->IsDoomed()) {
  2888                 rv = ProcessRequest(request, false, nullptr);
  2889                 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
  2890                     rv = NS_OK;
  2891                 else
  2892                     delete request;
  2894                 if (NS_FAILED(rv)) {
  2895                     // XXX what to do?
  2897             } else if (entry->IsValid() || newWriter) {
  2898                 rv = entry->RequestAccess(request, &accessGranted);
  2899                 NS_ASSERTION(NS_SUCCEEDED(rv),
  2900                              "if entry is valid, RequestAccess must succeed.");
  2901                 // XXX if (newWriter)  NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
  2903                 // entry->CreateDescriptor dequeues request, and queues descriptor
  2904                 nsICacheEntryDescriptor *descriptor = nullptr;
  2905                 rv = entry->CreateDescriptor(request,
  2906                                              accessGranted,
  2907                                              &descriptor);
  2909                 // post call to listener to report error or descriptor
  2910                 rv = NotifyListener(request, descriptor, accessGranted, rv);
  2911                 delete request;
  2912                 if (NS_FAILED(rv)) {
  2913                     // XXX what to do?
  2916             } else {
  2917                 // read-only request to an invalid entry - need to wait for
  2918                 // the entry to become valid so we post an event to process
  2919                 // the request again later (bug #467392)
  2920                 nsCOMPtr<nsIRunnable> ev =
  2921                     new nsProcessRequestEvent(request);
  2922                 rv = DispatchToCacheIOThread(ev);
  2923                 if (NS_FAILED(rv)) {
  2924                     delete request; // avoid leak
  2927         } else {
  2929             // Synchronous request
  2930             request->WakeUp();
  2932         if (newWriter)  break;  // process remaining requests after validation
  2933         request = nextRequest;
  2936     return NS_OK;
  2939 bool
  2940 nsCacheService::IsDoomListEmpty()
  2942     nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
  2943     return &mDoomedEntries == entry;
  2946 void
  2947 nsCacheService::ClearDoomList()
  2949     nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
  2951     while (entry != &mDoomedEntries) {
  2952         nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
  2954         entry->DetachDescriptors();
  2955         DeactivateEntry(entry);
  2956         entry = next;
  2960 PLDHashOperator
  2961 nsCacheService::GetActiveEntries(PLDHashTable *    table,
  2962                                  PLDHashEntryHdr * hdr,
  2963                                  uint32_t          number,
  2964                                  void *            arg)
  2966     static_cast<nsVoidArray *>(arg)->AppendElement(
  2967         ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry);
  2968     return PL_DHASH_NEXT;
  2971 struct ActiveEntryArgs
  2973     nsTArray<nsCacheEntry*>* mActiveArray;
  2974     nsCacheService::DoomCheckFn mCheckFn;
  2975 };
  2977 void
  2978 nsCacheService::DoomActiveEntries(DoomCheckFn check)
  2980     nsAutoTArray<nsCacheEntry*, 8> array;
  2981     ActiveEntryArgs args = { &array, check };
  2983     mActiveEntries.VisitEntries(RemoveActiveEntry, &args);
  2985     uint32_t count = array.Length();
  2986     for (uint32_t i=0; i < count; ++i)
  2987         DoomEntry_Internal(array[i], true);
  2990 PLDHashOperator
  2991 nsCacheService::RemoveActiveEntry(PLDHashTable *    table,
  2992                                   PLDHashEntryHdr * hdr,
  2993                                   uint32_t          number,
  2994                                   void *            arg)
  2996     nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
  2997     NS_ASSERTION(entry, "### active entry = nullptr!");
  2999     ActiveEntryArgs* args = static_cast<ActiveEntryArgs*>(arg);
  3000     if (args->mCheckFn && !args->mCheckFn(entry))
  3001         return PL_DHASH_NEXT;
  3003     NS_ASSERTION(args->mActiveArray, "### array = nullptr!");
  3004     args->mActiveArray->AppendElement(entry);
  3006     // entry is being removed from the active entry list
  3007     entry->MarkInactive();
  3008     return PL_DHASH_REMOVE; // and continue enumerating
  3012 void
  3013 nsCacheService::CloseAllStreams()
  3015     nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
  3016     nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
  3019         nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
  3021         nsVoidArray entries;
  3023 #if DEBUG
  3024         // make sure there is no active entry
  3025         mActiveEntries.VisitEntries(GetActiveEntries, &entries);
  3026         NS_ASSERTION(entries.Count() == 0, "Bad state");
  3027 #endif
  3029         // Get doomed entries
  3030         nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
  3031         while (entry != &mDoomedEntries) {
  3032             nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
  3033             entries.AppendElement(entry);
  3034             entry = next;
  3037         // Iterate through all entries and collect input and output streams
  3038         for (int32_t i = 0 ; i < entries.Count() ; i++) {
  3039             entry = static_cast<nsCacheEntry *>(entries.ElementAt(i));
  3041             nsTArray<nsRefPtr<nsCacheEntryDescriptor> > descs;
  3042             entry->GetDescriptors(descs);
  3044             for (uint32_t j = 0 ; j < descs.Length() ; j++) {
  3045                 if (descs[j]->mOutputWrapper)
  3046                     outputs.AppendElement(descs[j]->mOutputWrapper);
  3048                 for (int32_t k = 0 ; k < descs[j]->mInputWrappers.Count() ; k++)
  3049                     inputs.AppendElement(static_cast<
  3050                         nsCacheEntryDescriptor::nsInputStreamWrapper *>(
  3051                         descs[j]->mInputWrappers[k]));
  3056     uint32_t i;
  3057     for (i = 0 ; i < inputs.Length() ; i++)
  3058         inputs[i]->Close();
  3060     for (i = 0 ; i < outputs.Length() ; i++)
  3061         outputs[i]->Close();
  3065 bool
  3066 nsCacheService::GetClearingEntries()
  3068     AssertOwnsLock();
  3069     return gService->mClearingEntries;
  3072 // static
  3073 void nsCacheService::GetCacheBaseDirectoty(nsIFile ** result)
  3075     *result = nullptr;
  3076     if (!gService || !gService->mObserver)
  3077         return;
  3079     nsCOMPtr<nsIFile> directory =
  3080         gService->mObserver->DiskCacheParentDirectory();
  3081     if (!directory)
  3082         return;
  3084     directory->Clone(result);
  3087 // static
  3088 void nsCacheService::GetDiskCacheDirectory(nsIFile ** result)
  3090     nsCOMPtr<nsIFile> directory;
  3091     GetCacheBaseDirectoty(getter_AddRefs(directory));
  3092     if (!directory)
  3093         return;
  3095     nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
  3096     if (NS_FAILED(rv))
  3097         return;
  3099     directory.forget(result);
  3102 // static
  3103 void nsCacheService::GetAppCacheDirectory(nsIFile ** result)
  3105     nsCOMPtr<nsIFile> directory;
  3106     GetCacheBaseDirectoty(getter_AddRefs(directory));
  3107     if (!directory)
  3108         return;
  3110     nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
  3111     if (NS_FAILED(rv))
  3112         return;
  3114     directory.forget(result);
  3118 #if defined(PR_LOGGING)
  3119 void
  3120 nsCacheService::LogCacheStatistics()
  3122     uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
  3123         ((double)(mCacheHits + mCacheMisses))) * 100);
  3124     CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
  3125     CACHE_LOG_ALWAYS(("    TotalEntries   = %d\n", mTotalEntries));
  3126     CACHE_LOG_ALWAYS(("    Cache Hits     = %d\n", mCacheHits));
  3127     CACHE_LOG_ALWAYS(("    Cache Misses   = %d\n", mCacheMisses));
  3128     CACHE_LOG_ALWAYS(("    Cache Hit %%    = %d%%\n", hitPercentage));
  3129     CACHE_LOG_ALWAYS(("    Max Key Length = %d\n", mMaxKeyLength));
  3130     CACHE_LOG_ALWAYS(("    Max Meta Size  = %d\n", mMaxMetaSize));
  3131     CACHE_LOG_ALWAYS(("    Max Data Size  = %d\n", mMaxDataSize));
  3132     CACHE_LOG_ALWAYS(("\n"));
  3133     CACHE_LOG_ALWAYS(("    Deactivate Failures         = %d\n",
  3134                       mDeactivateFailures));
  3135     CACHE_LOG_ALWAYS(("    Deactivated Unbound Entries = %d\n",
  3136                       mDeactivatedUnboundEntries));
  3138 #endif
  3140 nsresult
  3141 nsCacheService::SetDiskSmartSize()
  3143     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
  3145     if (!gService) return NS_ERROR_NOT_AVAILABLE;
  3147     return gService->SetDiskSmartSize_Locked();
  3150 nsresult
  3151 nsCacheService::SetDiskSmartSize_Locked()
  3153     nsresult rv;
  3155     if (mozilla::net::CacheObserver::UseNewCache()) {
  3156         return NS_ERROR_NOT_AVAILABLE;
  3159     if (!mObserver->DiskCacheParentDirectory())
  3160         return NS_ERROR_NOT_AVAILABLE;
  3162     if (!mDiskDevice)
  3163         return NS_ERROR_NOT_AVAILABLE;
  3165     if (!mObserver->SmartSizeEnabled())
  3166         return NS_ERROR_NOT_AVAILABLE;
  3168     nsAutoString cachePath;
  3169     rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
  3170     if (NS_SUCCEEDED(rv)) {
  3171         nsCOMPtr<nsIRunnable> event =
  3172             new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
  3173                                     mObserver->ShouldUseOldMaxSmartSize());
  3174         DispatchToCacheIOThread(event);
  3175     } else {
  3176         return NS_ERROR_FAILURE;
  3179     return NS_OK;
  3182 void
  3183 nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir, 
  3184                                       nsIFile *aNewCacheDir,
  3185                                       const char *aCacheSubdir)
  3187     bool same;
  3188     if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
  3189         return;
  3191     nsCOMPtr<nsIFile> aOldCacheSubdir;
  3192     aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
  3194     nsresult rv = aOldCacheSubdir->AppendNative(
  3195         nsDependentCString(aCacheSubdir));
  3196     if (NS_FAILED(rv))
  3197         return;
  3199     bool exists;
  3200     if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
  3201         return;
  3203     nsCOMPtr<nsIFile> aNewCacheSubdir;
  3204     aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
  3206     rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
  3207     if (NS_FAILED(rv))
  3208         return;
  3210     nsAutoCString newPath;
  3211     rv = aNewCacheSubdir->GetNativePath(newPath);
  3212     if (NS_FAILED(rv))
  3213         return;
  3215     if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
  3216         // New cache directory does not exist, try to move the old one here
  3217         // rename needs an empty target directory
  3219         // Make sure the parent of the target sub-dir exists
  3220         rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
  3221         if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
  3222             nsAutoCString oldPath;
  3223             rv = aOldCacheSubdir->GetNativePath(oldPath);
  3224             if (NS_FAILED(rv))
  3225                 return;
  3226             if (rename(oldPath.get(), newPath.get()) == 0)
  3227                 return;
  3231     // Delay delete by 1 minute to avoid IO thrash on startup.
  3232     nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
  3235 static bool
  3236 IsEntryPrivate(nsCacheEntry* entry)
  3238     return entry->IsPrivate();
  3241 void
  3242 nsCacheService::LeavePrivateBrowsing()
  3244     nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_LEAVEPRIVATEBROWSING));
  3246     gService->DoomActiveEntries(IsEntryPrivate);
  3248     if (gService->mMemoryDevice) {
  3249         // clear memory cache
  3250         gService->mMemoryDevice->EvictPrivateEntries();
  3254 MOZ_DEFINE_MALLOC_SIZE_OF(DiskCacheDeviceMallocSizeOf)
  3256 NS_IMETHODIMP
  3257 nsCacheService::CollectReports(nsIHandleReportCallback* aHandleReport,
  3258                                nsISupports* aData)
  3260     size_t disk = 0;
  3261     if (mDiskDevice) {
  3262         nsCacheServiceAutoLock
  3263             lock(LOCK_TELEM(NSCACHESERVICE_DISKDEVICEHEAPSIZE));
  3264         disk = mDiskDevice->SizeOfIncludingThis(DiskCacheDeviceMallocSizeOf);
  3267     size_t memory = mMemoryDevice ? mMemoryDevice->TotalSize() : 0;
  3269 #define REPORT(_path, _amount, _desc)                                         \
  3270     do {                                                                      \
  3271         nsresult rv;                                                          \
  3272         rv = aHandleReport->Callback(EmptyCString(),                          \
  3273                                      NS_LITERAL_CSTRING(_path),               \
  3274                                      KIND_HEAP, UNITS_BYTES, _amount,         \
  3275                                      NS_LITERAL_CSTRING(_desc), aData);       \
  3276         NS_ENSURE_SUCCESS(rv, rv);                                            \
  3277     } while (0)
  3279     REPORT("explicit/network/disk-cache", disk,
  3280            "Memory used by the network disk cache.");
  3282     REPORT("explicit/network/memory-cache", memory,
  3283            "Memory used by the network memory cache.");
  3285     return NS_OK;

mercurial