mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 02:14:43 +00:00
00ed953e23
Purge HTTP disk cache using backgroundtasks Motivation: In the History settings preferences panel you may choose to `Clear History when Firefox closes` - which sets the `privacy.sanitize.sanitizeOnShutdown` pref. If the `Cache` checkbox is also checked it sets the `privacy.clearOnShutdown.cache` pref to true. When both of these prefs are true `CacheObserver::ClearCacheOnShutdown()` will return true, which will cause Firefox to clear the HTTP disk cache during shutdown. This will block shutdown for howeverlong this takes. If the user has a slow disk, or the user is on Windows and something is blocking the deletion of those files, this will trigger a shutdown hang making Firefox crash. This leads to a bad user experience as trying to start Firefox again during this time will show the "Firefox is already running message". Eventually a crash dump is produced. Bug 1356853 and bug 1682899 are caused by this specific issue. In order to avoid these crashes and a bad user experience we have a few options: 1. Completely disable the disk cache when a user checks this box. This option will degrade the user's browsing experience. 2. Don't delete the folder at shutdown Whether we do this by removing the checkbox or simply not respecting the pref value, users who already have this setting would be surprised if the cache folder stops being deleted. 3. Use a thread pool to delete the files While it's likely to speed up the deletion at least a little bit, this would introduce additional complexity while not completely fixing the issue. A slow disk will still block shutdown. 4. Delete the cache at shutdown using a separate process This is likely the best option. It has the advantage of not blocking shutdown while at the same time maintaining similar properties to the existing functionality: - The cache folder is deleted at shutdown. - Has the same behaviour if Firefox gets killed or crashes for different issues. - Behaves similarly if the OS is forcefully shutdown during or before we begin to purge. - Additionaly, because we rename the folder prior to dispatching the background task, if the purging isn't completed we - don't rebuild the cache from an incomplete folder on next restart - are able to resume the purging after Firefox startup A particularly special case is the Windows shutdown. If the user shuts down windows that will try to close Firefox. If the shutdown takes too long, the user will see the "Close anyway" button and may click it thus preventing the cache purge to complete. When using a background task we have a similar situation, but the button won't even appear. So after the next Firefox restart we will check if the cache needs to be purged. Largely, the new behaviour will be: - At shutdown we conditionally dispatch a background task to delete the folder - If creating the process fails (for some reason) we fallback to the old way of synchronously deleting the folder. - The task will then try to delete the folder in the background - If for some reason it fails, we will dispatch a new background task shortly after Firefox restarts to clean up the old folders. Differential Revision: https://phabricator.services.mozilla.com/D126339
255 lines
7.0 KiB
C++
255 lines
7.0 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "CacheObserver.h"
|
|
|
|
#include "CacheStorageService.h"
|
|
#include "CacheFileIOManager.h"
|
|
#include "LoadContextInfo.h"
|
|
#include "nsICacheStorage.h"
|
|
#include "nsIObserverService.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "mozilla/net/NeckoCommon.h"
|
|
#include "prsystem.h"
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
namespace mozilla::net {
|
|
|
|
StaticRefPtr<CacheObserver> CacheObserver::sSelf;
|
|
|
|
static float const kDefaultHalfLifeHours = 24.0F; // 24 hours
|
|
float CacheObserver::sHalfLifeHours = kDefaultHalfLifeHours;
|
|
|
|
// The default value will be overwritten as soon as the correct smart size is
|
|
// calculated by CacheFileIOManager::UpdateSmartCacheSize(). It's limited to 1GB
|
|
// just for case the size is never calculated which might in theory happen if
|
|
// GetDiskSpaceAvailable() always fails.
|
|
Atomic<uint32_t, Relaxed> CacheObserver::sSmartDiskCacheCapacity(1024 * 1024);
|
|
|
|
Atomic<PRIntervalTime> CacheObserver::sShutdownDemandedTime(
|
|
PR_INTERVAL_NO_TIMEOUT);
|
|
|
|
NS_IMPL_ISUPPORTS(CacheObserver, nsIObserver, nsISupportsWeakReference)
|
|
|
|
// static
|
|
nsresult CacheObserver::Init() {
|
|
if (IsNeckoChild()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (sSelf) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (!obs) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
sSelf = new CacheObserver();
|
|
|
|
obs->AddObserver(sSelf, "prefservice:after-app-defaults", true);
|
|
obs->AddObserver(sSelf, "profile-do-change", true);
|
|
obs->AddObserver(sSelf, "profile-before-change", true);
|
|
obs->AddObserver(sSelf, "xpcom-shutdown", true);
|
|
obs->AddObserver(sSelf, "last-pb-context-exited", true);
|
|
obs->AddObserver(sSelf, "memory-pressure", true);
|
|
obs->AddObserver(sSelf, "browser-delayed-startup-finished", true);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult CacheObserver::Shutdown() {
|
|
if (!sSelf) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
sSelf = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
void CacheObserver::AttachToPreferences() {
|
|
mozilla::Preferences::GetComplex(
|
|
"browser.cache.disk.parent_directory", NS_GET_IID(nsIFile),
|
|
getter_AddRefs(mCacheParentDirectoryOverride));
|
|
|
|
sHalfLifeHours = std::max(
|
|
0.01F, std::min(1440.0F, mozilla::Preferences::GetFloat(
|
|
"browser.cache.frecency_half_life_hours",
|
|
kDefaultHalfLifeHours)));
|
|
}
|
|
|
|
// static
|
|
uint32_t CacheObserver::MemoryCacheCapacity() {
|
|
if (StaticPrefs::browser_cache_memory_capacity() >= 0) {
|
|
return StaticPrefs::browser_cache_memory_capacity();
|
|
}
|
|
|
|
// Cache of the calculated memory capacity based on the system memory size in
|
|
// KB (C++11 guarantees local statics will be initialized once and in a
|
|
// thread-safe way.)
|
|
static int32_t sAutoMemoryCacheCapacity = ([] {
|
|
uint64_t bytes = PR_GetPhysicalMemorySize();
|
|
// If getting the physical memory failed, arbitrarily assume
|
|
// 32 MB of RAM. We use a low default to have a reasonable
|
|
// size on all the devices we support.
|
|
if (bytes == 0) {
|
|
bytes = 32 * 1024 * 1024;
|
|
}
|
|
// Conversion from unsigned int64_t to double doesn't work on all platforms.
|
|
// We need to truncate the value at INT64_MAX to make sure we don't
|
|
// overflow.
|
|
if (bytes > INT64_MAX) {
|
|
bytes = INT64_MAX;
|
|
}
|
|
uint64_t kbytes = bytes >> 10;
|
|
double kBytesD = double(kbytes);
|
|
double x = log(kBytesD) / log(2.0) - 14;
|
|
|
|
int32_t capacity = 0;
|
|
if (x > 0) {
|
|
// 0.1 is added here for rounding
|
|
capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1);
|
|
if (capacity > 32) {
|
|
capacity = 32;
|
|
}
|
|
capacity <<= 10;
|
|
}
|
|
return capacity;
|
|
})();
|
|
|
|
return sAutoMemoryCacheCapacity;
|
|
}
|
|
|
|
// static
|
|
void CacheObserver::SetSmartDiskCacheCapacity(uint32_t aCapacity) {
|
|
sSmartDiskCacheCapacity = aCapacity;
|
|
}
|
|
|
|
// static
|
|
uint32_t CacheObserver::DiskCacheCapacity() {
|
|
return SmartCacheSizeEnabled() ? sSmartDiskCacheCapacity
|
|
: StaticPrefs::browser_cache_disk_capacity();
|
|
}
|
|
|
|
// static
|
|
void CacheObserver::ParentDirOverride(nsIFile** aDir) {
|
|
if (NS_WARN_IF(!aDir)) return;
|
|
|
|
*aDir = nullptr;
|
|
|
|
if (!sSelf) return;
|
|
if (!sSelf->mCacheParentDirectoryOverride) return;
|
|
|
|
sSelf->mCacheParentDirectoryOverride->Clone(aDir);
|
|
}
|
|
|
|
// static
|
|
bool CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk) {
|
|
// If custom limit is set, check it.
|
|
int64_t preferredLimit =
|
|
aUsingDisk ? MaxDiskEntrySize() : MaxMemoryEntrySize();
|
|
|
|
// do not convert to bytes when the limit is -1, which means no limit
|
|
if (preferredLimit > 0) {
|
|
preferredLimit <<= 10;
|
|
}
|
|
|
|
if (preferredLimit != -1 && aSize > preferredLimit) return true;
|
|
|
|
// Otherwise (or when in the custom limit), check limit based on the global
|
|
// limit. It's 1/8 of the respective capacity.
|
|
int64_t derivedLimit =
|
|
aUsingDisk ? DiskCacheCapacity() : MemoryCacheCapacity();
|
|
derivedLimit <<= (10 - 3);
|
|
|
|
return aSize > derivedLimit;
|
|
}
|
|
|
|
// static
|
|
bool CacheObserver::IsPastShutdownIOLag() {
|
|
#ifdef DEBUG
|
|
return false;
|
|
#else
|
|
if (sShutdownDemandedTime == PR_INTERVAL_NO_TIMEOUT ||
|
|
MaxShutdownIOLag() == UINT32_MAX) {
|
|
return false;
|
|
}
|
|
|
|
static const PRIntervalTime kMaxShutdownIOLag =
|
|
PR_SecondsToInterval(MaxShutdownIOLag());
|
|
|
|
if ((PR_IntervalNow() - sShutdownDemandedTime) > kMaxShutdownIOLag) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CacheObserver::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!strcmp(aTopic, "prefservice:after-app-defaults")) {
|
|
CacheFileIOManager::Init();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "profile-do-change")) {
|
|
AttachToPreferences();
|
|
CacheFileIOManager::Init();
|
|
CacheFileIOManager::OnProfile();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "profile-change-net-teardown") ||
|
|
!strcmp(aTopic, "profile-before-change") ||
|
|
!strcmp(aTopic, "xpcom-shutdown")) {
|
|
if (sShutdownDemandedTime == PR_INTERVAL_NO_TIMEOUT) {
|
|
sShutdownDemandedTime = PR_IntervalNow();
|
|
}
|
|
|
|
RefPtr<CacheStorageService> service = CacheStorageService::Self();
|
|
if (service) {
|
|
service->Shutdown();
|
|
}
|
|
|
|
CacheFileIOManager::Shutdown();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "last-pb-context-exited")) {
|
|
RefPtr<CacheStorageService> service = CacheStorageService::Self();
|
|
if (service) {
|
|
service->DropPrivateBrowsingEntries();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "memory-pressure")) {
|
|
RefPtr<CacheStorageService> service = CacheStorageService::Self();
|
|
if (service) {
|
|
service->PurgeFromMemory(nsICacheStorageService::PURGE_EVERYTHING);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, "browser-delayed-startup-finished")) {
|
|
CacheFileIOManager::OnDelayedStartupFinished();
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(false, "Missing observer handler");
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla::net
|