netwerk/cache2/CacheStorageService.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include "CacheLog.h"
michael@0 6 #include "CacheStorageService.h"
michael@0 7 #include "CacheFileIOManager.h"
michael@0 8 #include "CacheObserver.h"
michael@0 9 #include "CacheIndex.h"
michael@0 10
michael@0 11 #include "nsICacheStorageVisitor.h"
michael@0 12 #include "nsIObserverService.h"
michael@0 13 #include "CacheStorage.h"
michael@0 14 #include "AppCacheStorage.h"
michael@0 15 #include "CacheEntry.h"
michael@0 16 #include "CacheFileUtils.h"
michael@0 17
michael@0 18 #include "OldWrappers.h"
michael@0 19 #include "nsCacheService.h"
michael@0 20 #include "nsDeleteDir.h"
michael@0 21
michael@0 22 #include "nsIFile.h"
michael@0 23 #include "nsIURI.h"
michael@0 24 #include "nsCOMPtr.h"
michael@0 25 #include "nsAutoPtr.h"
michael@0 26 #include "nsNetCID.h"
michael@0 27 #include "nsServiceManagerUtils.h"
michael@0 28 #include "mozilla/TimeStamp.h"
michael@0 29 #include "mozilla/DebugOnly.h"
michael@0 30 #include "mozilla/VisualEventTracer.h"
michael@0 31 #include "mozilla/Services.h"
michael@0 32
michael@0 33 namespace mozilla {
michael@0 34 namespace net {
michael@0 35
michael@0 36 namespace {
michael@0 37
michael@0 38 void AppendMemoryStorageID(nsAutoCString &key)
michael@0 39 {
michael@0 40 key.Append('/');
michael@0 41 key.Append('M');
michael@0 42 }
michael@0 43
michael@0 44 }
michael@0 45
michael@0 46 // Not defining as static or class member of CacheStorageService since
michael@0 47 // it would otherwise need to include CacheEntry.h and that then would
michael@0 48 // need to be exported to make nsNetModule.cpp compilable.
michael@0 49 typedef nsClassHashtable<nsCStringHashKey, CacheEntryTable>
michael@0 50 GlobalEntryTables;
michael@0 51
michael@0 52 /**
michael@0 53 * Keeps tables of entries. There is one entries table for each distinct load
michael@0 54 * context type. The distinction is based on following load context info states:
michael@0 55 * <isPrivate|isAnon|appId|inBrowser> which builds a mapping key.
michael@0 56 *
michael@0 57 * Thread-safe to access, protected by the service mutex.
michael@0 58 */
michael@0 59 static GlobalEntryTables* sGlobalEntryTables;
michael@0 60
michael@0 61 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags)
michael@0 62 : mReportedMemoryConsumption(0)
michael@0 63 , mFlags(aFlags)
michael@0 64 {
michael@0 65 }
michael@0 66
michael@0 67 void
michael@0 68 CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize)
michael@0 69 {
michael@0 70 if (!(mFlags & DONT_REPORT) && CacheStorageService::Self()) {
michael@0 71 CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
michael@0 72 }
michael@0 73 }
michael@0 74
michael@0 75 CacheStorageService::MemoryPool::MemoryPool(EType aType)
michael@0 76 : mType(aType)
michael@0 77 , mMemorySize(0)
michael@0 78 {
michael@0 79 }
michael@0 80
michael@0 81 CacheStorageService::MemoryPool::~MemoryPool()
michael@0 82 {
michael@0 83 if (mMemorySize != 0) {
michael@0 84 NS_ERROR("Network cache reported memory consumption is not at 0, probably leaking?");
michael@0 85 }
michael@0 86 }
michael@0 87
michael@0 88 uint32_t const
michael@0 89 CacheStorageService::MemoryPool::Limit() const
michael@0 90 {
michael@0 91 switch (mType) {
michael@0 92 case DISK:
michael@0 93 return CacheObserver::MetadataMemoryLimit();
michael@0 94 case MEMORY:
michael@0 95 return CacheObserver::MemoryCacheCapacity();
michael@0 96 }
michael@0 97
michael@0 98 MOZ_CRASH("Bad pool type");
michael@0 99 return 0;
michael@0 100 }
michael@0 101
michael@0 102 NS_IMPL_ISUPPORTS(CacheStorageService,
michael@0 103 nsICacheStorageService,
michael@0 104 nsIMemoryReporter,
michael@0 105 nsITimerCallback)
michael@0 106
michael@0 107 CacheStorageService* CacheStorageService::sSelf = nullptr;
michael@0 108
michael@0 109 CacheStorageService::CacheStorageService()
michael@0 110 : mLock("CacheStorageService")
michael@0 111 , mShutdown(false)
michael@0 112 , mDiskPool(MemoryPool::DISK)
michael@0 113 , mMemoryPool(MemoryPool::MEMORY)
michael@0 114 {
michael@0 115 CacheFileIOManager::Init();
michael@0 116
michael@0 117 MOZ_ASSERT(!sSelf);
michael@0 118
michael@0 119 sSelf = this;
michael@0 120 sGlobalEntryTables = new GlobalEntryTables();
michael@0 121
michael@0 122 RegisterStrongMemoryReporter(this);
michael@0 123 }
michael@0 124
michael@0 125 CacheStorageService::~CacheStorageService()
michael@0 126 {
michael@0 127 LOG(("CacheStorageService::~CacheStorageService"));
michael@0 128 sSelf = nullptr;
michael@0 129 }
michael@0 130
michael@0 131 void CacheStorageService::Shutdown()
michael@0 132 {
michael@0 133 if (mShutdown)
michael@0 134 return;
michael@0 135
michael@0 136 LOG(("CacheStorageService::Shutdown - start"));
michael@0 137
michael@0 138 mShutdown = true;
michael@0 139
michael@0 140 nsCOMPtr<nsIRunnable> event =
michael@0 141 NS_NewRunnableMethod(this, &CacheStorageService::ShutdownBackground);
michael@0 142 Dispatch(event);
michael@0 143
michael@0 144 mozilla::MutexAutoLock lock(mLock);
michael@0 145 sGlobalEntryTables->Clear();
michael@0 146 delete sGlobalEntryTables;
michael@0 147 sGlobalEntryTables = nullptr;
michael@0 148
michael@0 149 LOG(("CacheStorageService::Shutdown - done"));
michael@0 150 }
michael@0 151
michael@0 152 void CacheStorageService::ShutdownBackground()
michael@0 153 {
michael@0 154 MOZ_ASSERT(IsOnManagementThread());
michael@0 155
michael@0 156 Pool(false).mFrecencyArray.Clear();
michael@0 157 Pool(false).mExpirationArray.Clear();
michael@0 158 Pool(true).mFrecencyArray.Clear();
michael@0 159 Pool(true).mExpirationArray.Clear();
michael@0 160 }
michael@0 161
michael@0 162 // Internal management methods
michael@0 163
michael@0 164 namespace { // anon
michael@0 165
michael@0 166 // WalkRunnable
michael@0 167 // Responsible to visit the storage and walk all entries on it asynchronously
michael@0 168
michael@0 169 class WalkRunnable : public nsRunnable
michael@0 170 {
michael@0 171 public:
michael@0 172 WalkRunnable(nsCSubstring const & aContextKey, bool aVisitEntries,
michael@0 173 bool aUsingDisk,
michael@0 174 nsICacheStorageVisitor* aVisitor)
michael@0 175 : mContextKey(aContextKey)
michael@0 176 , mCallback(aVisitor)
michael@0 177 , mSize(0)
michael@0 178 , mNotifyStorage(true)
michael@0 179 , mVisitEntries(aVisitEntries)
michael@0 180 , mUsingDisk(aUsingDisk)
michael@0 181 {
michael@0 182 MOZ_ASSERT(NS_IsMainThread());
michael@0 183 }
michael@0 184
michael@0 185 private:
michael@0 186 NS_IMETHODIMP Run()
michael@0 187 {
michael@0 188 if (CacheStorageService::IsOnManagementThread()) {
michael@0 189 LOG(("WalkRunnable::Run - collecting [this=%p, disk=%d]", this, (bool)mUsingDisk));
michael@0 190 // First, walk, count and grab all entries from the storage
michael@0 191 // TODO
michael@0 192 // - walk files on disk, when the storage is not private
michael@0 193 // - should create representative entries only for the time
michael@0 194 // of need
michael@0 195
michael@0 196 mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
michael@0 197
michael@0 198 if (!CacheStorageService::IsRunning())
michael@0 199 return NS_ERROR_NOT_INITIALIZED;
michael@0 200
michael@0 201 CacheEntryTable* entries;
michael@0 202 if (sGlobalEntryTables->Get(mContextKey, &entries))
michael@0 203 entries->EnumerateRead(&WalkRunnable::WalkStorage, this);
michael@0 204
michael@0 205 // Next, we dispatch to the main thread
michael@0 206 }
michael@0 207 else if (NS_IsMainThread()) {
michael@0 208 LOG(("WalkRunnable::Run - notifying [this=%p, disk=%d]", this, (bool)mUsingDisk));
michael@0 209 if (mNotifyStorage) {
michael@0 210 LOG((" storage"));
michael@0 211 // Second, notify overall storage info
michael@0 212 mCallback->OnCacheStorageInfo(mEntryArray.Length(), mSize);
michael@0 213 if (!mVisitEntries)
michael@0 214 return NS_OK; // done
michael@0 215
michael@0 216 mNotifyStorage = false;
michael@0 217 }
michael@0 218 else {
michael@0 219 LOG((" entry [left=%d]", mEntryArray.Length()));
michael@0 220 // Third, notify each entry until depleted.
michael@0 221 if (!mEntryArray.Length()) {
michael@0 222 mCallback->OnCacheEntryVisitCompleted();
michael@0 223 return NS_OK; // done
michael@0 224 }
michael@0 225
michael@0 226 mCallback->OnCacheEntryInfo(mEntryArray[0]);
michael@0 227 mEntryArray.RemoveElementAt(0);
michael@0 228
michael@0 229 // Dispatch to the main thread again
michael@0 230 }
michael@0 231 }
michael@0 232 else {
michael@0 233 MOZ_ASSERT(false);
michael@0 234 return NS_ERROR_FAILURE;
michael@0 235 }
michael@0 236
michael@0 237 NS_DispatchToMainThread(this);
michael@0 238 return NS_OK;
michael@0 239 }
michael@0 240
michael@0 241 virtual ~WalkRunnable()
michael@0 242 {
michael@0 243 if (mCallback)
michael@0 244 ProxyReleaseMainThread(mCallback);
michael@0 245 }
michael@0 246
michael@0 247 static PLDHashOperator
michael@0 248 WalkStorage(const nsACString& aKey,
michael@0 249 CacheEntry* aEntry,
michael@0 250 void* aClosure)
michael@0 251 {
michael@0 252 WalkRunnable* walker = static_cast<WalkRunnable*>(aClosure);
michael@0 253
michael@0 254 if (!walker->mUsingDisk && aEntry->IsUsingDiskLocked())
michael@0 255 return PL_DHASH_NEXT;
michael@0 256
michael@0 257 walker->mSize += aEntry->GetMetadataMemoryConsumption();
michael@0 258
michael@0 259 int64_t size;
michael@0 260 if (NS_SUCCEEDED(aEntry->GetDataSize(&size)))
michael@0 261 walker->mSize += size;
michael@0 262
michael@0 263 walker->mEntryArray.AppendElement(aEntry);
michael@0 264 return PL_DHASH_NEXT;
michael@0 265 }
michael@0 266
michael@0 267 nsCString mContextKey;
michael@0 268 nsCOMPtr<nsICacheStorageVisitor> mCallback;
michael@0 269 nsTArray<nsRefPtr<CacheEntry> > mEntryArray;
michael@0 270
michael@0 271 uint64_t mSize;
michael@0 272
michael@0 273 bool mNotifyStorage : 1;
michael@0 274 bool mVisitEntries : 1;
michael@0 275 bool mUsingDisk : 1;
michael@0 276 };
michael@0 277
michael@0 278 PLDHashOperator CollectPrivateContexts(const nsACString& aKey,
michael@0 279 CacheEntryTable* aTable,
michael@0 280 void* aClosure)
michael@0 281 {
michael@0 282 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
michael@0 283 if (info && info->IsPrivate()) {
michael@0 284 nsTArray<nsCString>* keys = static_cast<nsTArray<nsCString>*>(aClosure);
michael@0 285 keys->AppendElement(aKey);
michael@0 286 }
michael@0 287
michael@0 288 return PL_DHASH_NEXT;
michael@0 289 }
michael@0 290
michael@0 291 PLDHashOperator CollectContexts(const nsACString& aKey,
michael@0 292 CacheEntryTable* aTable,
michael@0 293 void* aClosure)
michael@0 294 {
michael@0 295 nsTArray<nsCString>* keys = static_cast<nsTArray<nsCString>*>(aClosure);
michael@0 296 keys->AppendElement(aKey);
michael@0 297
michael@0 298 return PL_DHASH_NEXT;
michael@0 299 }
michael@0 300
michael@0 301 } // anon
michael@0 302
michael@0 303 void CacheStorageService::DropPrivateBrowsingEntries()
michael@0 304 {
michael@0 305 mozilla::MutexAutoLock lock(mLock);
michael@0 306
michael@0 307 if (mShutdown)
michael@0 308 return;
michael@0 309
michael@0 310 nsTArray<nsCString> keys;
michael@0 311 sGlobalEntryTables->EnumerateRead(&CollectPrivateContexts, &keys);
michael@0 312
michael@0 313 for (uint32_t i = 0; i < keys.Length(); ++i)
michael@0 314 DoomStorageEntries(keys[i], nullptr, true, nullptr);
michael@0 315 }
michael@0 316
michael@0 317 // static
michael@0 318 void CacheStorageService::WipeCacheDirectory(uint32_t aVersion)
michael@0 319 {
michael@0 320 nsCOMPtr<nsIFile> cacheDir;
michael@0 321 switch (aVersion) {
michael@0 322 case 0:
michael@0 323 nsCacheService::GetDiskCacheDirectory(getter_AddRefs(cacheDir));
michael@0 324 break;
michael@0 325 case 1:
michael@0 326 CacheFileIOManager::GetCacheDirectory(getter_AddRefs(cacheDir));
michael@0 327 break;
michael@0 328 }
michael@0 329
michael@0 330 if (!cacheDir)
michael@0 331 return;
michael@0 332
michael@0 333 nsDeleteDir::DeleteDir(cacheDir, true, 30000);
michael@0 334 }
michael@0 335
michael@0 336 // Helper methods
michael@0 337
michael@0 338 // static
michael@0 339 bool CacheStorageService::IsOnManagementThread()
michael@0 340 {
michael@0 341 nsRefPtr<CacheStorageService> service = Self();
michael@0 342 if (!service)
michael@0 343 return false;
michael@0 344
michael@0 345 nsCOMPtr<nsIEventTarget> target = service->Thread();
michael@0 346 if (!target)
michael@0 347 return false;
michael@0 348
michael@0 349 bool currentThread;
michael@0 350 nsresult rv = target->IsOnCurrentThread(&currentThread);
michael@0 351 return NS_SUCCEEDED(rv) && currentThread;
michael@0 352 }
michael@0 353
michael@0 354 already_AddRefed<nsIEventTarget> CacheStorageService::Thread() const
michael@0 355 {
michael@0 356 return CacheFileIOManager::IOTarget();
michael@0 357 }
michael@0 358
michael@0 359 nsresult CacheStorageService::Dispatch(nsIRunnable* aEvent)
michael@0 360 {
michael@0 361 nsRefPtr<CacheIOThread> cacheIOThread = CacheFileIOManager::IOThread();
michael@0 362 if (!cacheIOThread)
michael@0 363 return NS_ERROR_NOT_AVAILABLE;
michael@0 364
michael@0 365 return cacheIOThread->Dispatch(aEvent, CacheIOThread::MANAGEMENT);
michael@0 366 }
michael@0 367
michael@0 368 // nsICacheStorageService
michael@0 369
michael@0 370 NS_IMETHODIMP CacheStorageService::MemoryCacheStorage(nsILoadContextInfo *aLoadContextInfo,
michael@0 371 nsICacheStorage * *_retval)
michael@0 372 {
michael@0 373 NS_ENSURE_ARG(aLoadContextInfo);
michael@0 374 NS_ENSURE_ARG(_retval);
michael@0 375
michael@0 376 nsCOMPtr<nsICacheStorage> storage;
michael@0 377 if (CacheObserver::UseNewCache()) {
michael@0 378 storage = new CacheStorage(aLoadContextInfo, false, false);
michael@0 379 }
michael@0 380 else {
michael@0 381 storage = new _OldStorage(aLoadContextInfo, false, false, false, nullptr);
michael@0 382 }
michael@0 383
michael@0 384 storage.forget(_retval);
michael@0 385 return NS_OK;
michael@0 386 }
michael@0 387
michael@0 388 NS_IMETHODIMP CacheStorageService::DiskCacheStorage(nsILoadContextInfo *aLoadContextInfo,
michael@0 389 bool aLookupAppCache,
michael@0 390 nsICacheStorage * *_retval)
michael@0 391 {
michael@0 392 NS_ENSURE_ARG(aLoadContextInfo);
michael@0 393 NS_ENSURE_ARG(_retval);
michael@0 394
michael@0 395 // TODO save some heap granularity - cache commonly used storages.
michael@0 396
michael@0 397 // When disk cache is disabled, still provide a storage, but just keep stuff
michael@0 398 // in memory.
michael@0 399 bool useDisk = CacheObserver::UseDiskCache();
michael@0 400
michael@0 401 nsCOMPtr<nsICacheStorage> storage;
michael@0 402 if (CacheObserver::UseNewCache()) {
michael@0 403 storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache);
michael@0 404 }
michael@0 405 else {
michael@0 406 storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
michael@0 407 }
michael@0 408
michael@0 409 storage.forget(_retval);
michael@0 410 return NS_OK;
michael@0 411 }
michael@0 412
michael@0 413 NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
michael@0 414 nsIApplicationCache *aApplicationCache,
michael@0 415 nsICacheStorage * *_retval)
michael@0 416 {
michael@0 417 NS_ENSURE_ARG(aLoadContextInfo);
michael@0 418 NS_ENSURE_ARG(_retval);
michael@0 419
michael@0 420 nsCOMPtr<nsICacheStorage> storage;
michael@0 421 if (CacheObserver::UseNewCache()) {
michael@0 422 // Using classification since cl believes we want to instantiate this method
michael@0 423 // having the same name as the desired class...
michael@0 424 storage = new mozilla::net::AppCacheStorage(aLoadContextInfo, aApplicationCache);
michael@0 425 }
michael@0 426 else {
michael@0 427 storage = new _OldStorage(aLoadContextInfo, true, false, true, aApplicationCache);
michael@0 428 }
michael@0 429
michael@0 430 storage.forget(_retval);
michael@0 431 return NS_OK;
michael@0 432 }
michael@0 433
michael@0 434 NS_IMETHODIMP CacheStorageService::Clear()
michael@0 435 {
michael@0 436 nsresult rv;
michael@0 437
michael@0 438 if (CacheObserver::UseNewCache()) {
michael@0 439 {
michael@0 440 mozilla::MutexAutoLock lock(mLock);
michael@0 441
michael@0 442 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 443
michael@0 444 nsTArray<nsCString> keys;
michael@0 445 sGlobalEntryTables->EnumerateRead(&CollectContexts, &keys);
michael@0 446
michael@0 447 for (uint32_t i = 0; i < keys.Length(); ++i)
michael@0 448 DoomStorageEntries(keys[i], nullptr, true, nullptr);
michael@0 449 }
michael@0 450
michael@0 451 rv = CacheFileIOManager::EvictAll();
michael@0 452 NS_ENSURE_SUCCESS(rv, rv);
michael@0 453 } else {
michael@0 454 nsCOMPtr<nsICacheService> serv =
michael@0 455 do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
michael@0 456 NS_ENSURE_SUCCESS(rv, rv);
michael@0 457
michael@0 458 rv = serv->EvictEntries(nsICache::STORE_ANYWHERE);
michael@0 459 NS_ENSURE_SUCCESS(rv, rv);
michael@0 460 }
michael@0 461
michael@0 462 return NS_OK;
michael@0 463 }
michael@0 464
michael@0 465 NS_IMETHODIMP CacheStorageService::PurgeFromMemory(uint32_t aWhat)
michael@0 466 {
michael@0 467 uint32_t what;
michael@0 468
michael@0 469 switch (aWhat) {
michael@0 470 case PURGE_DISK_DATA_ONLY:
michael@0 471 what = CacheEntry::PURGE_DATA_ONLY_DISK_BACKED;
michael@0 472 break;
michael@0 473
michael@0 474 case PURGE_DISK_ALL:
michael@0 475 what = CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED;
michael@0 476 break;
michael@0 477
michael@0 478 case PURGE_EVERYTHING:
michael@0 479 what = CacheEntry::PURGE_WHOLE;
michael@0 480 break;
michael@0 481
michael@0 482 default:
michael@0 483 return NS_ERROR_INVALID_ARG;
michael@0 484 }
michael@0 485
michael@0 486 nsCOMPtr<nsIRunnable> event =
michael@0 487 new PurgeFromMemoryRunnable(this, what);
michael@0 488
michael@0 489 return Dispatch(event);
michael@0 490 }
michael@0 491
michael@0 492 NS_IMETHODIMP CacheStorageService::AsyncGetDiskConsumption(
michael@0 493 nsICacheStorageConsumptionObserver* aObserver)
michael@0 494 {
michael@0 495 NS_ENSURE_ARG(aObserver);
michael@0 496
michael@0 497 nsresult rv;
michael@0 498
michael@0 499 if (CacheObserver::UseNewCache()) {
michael@0 500 rv = CacheIndex::AsyncGetDiskConsumption(aObserver);
michael@0 501 NS_ENSURE_SUCCESS(rv, rv);
michael@0 502 } else {
michael@0 503 rv = _OldGetDiskConsumption::Get(aObserver);
michael@0 504 NS_ENSURE_SUCCESS(rv, rv);
michael@0 505 }
michael@0 506
michael@0 507 return NS_OK;
michael@0 508 }
michael@0 509
michael@0 510 NS_IMETHODIMP CacheStorageService::GetIoTarget(nsIEventTarget** aEventTarget)
michael@0 511 {
michael@0 512 NS_ENSURE_ARG(aEventTarget);
michael@0 513
michael@0 514 if (CacheObserver::UseNewCache()) {
michael@0 515 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
michael@0 516 ioTarget.forget(aEventTarget);
michael@0 517 }
michael@0 518 else {
michael@0 519 nsresult rv;
michael@0 520
michael@0 521 nsCOMPtr<nsICacheService> serv =
michael@0 522 do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
michael@0 523 NS_ENSURE_SUCCESS(rv, rv);
michael@0 524
michael@0 525 rv = serv->GetCacheIOTarget(aEventTarget);
michael@0 526 NS_ENSURE_SUCCESS(rv, rv);
michael@0 527 }
michael@0 528
michael@0 529 return NS_OK;
michael@0 530 }
michael@0 531
michael@0 532 // Methods used by CacheEntry for management of in-memory structures.
michael@0 533
michael@0 534 namespace { // anon
michael@0 535
michael@0 536 class FrecencyComparator
michael@0 537 {
michael@0 538 public:
michael@0 539 bool Equals(CacheEntry* a, CacheEntry* b) const {
michael@0 540 return a->GetFrecency() == b->GetFrecency();
michael@0 541 }
michael@0 542 bool LessThan(CacheEntry* a, CacheEntry* b) const {
michael@0 543 return a->GetFrecency() < b->GetFrecency();
michael@0 544 }
michael@0 545 };
michael@0 546
michael@0 547 class ExpirationComparator
michael@0 548 {
michael@0 549 public:
michael@0 550 bool Equals(CacheEntry* a, CacheEntry* b) const {
michael@0 551 return a->GetExpirationTime() == b->GetExpirationTime();
michael@0 552 }
michael@0 553 bool LessThan(CacheEntry* a, CacheEntry* b) const {
michael@0 554 return a->GetExpirationTime() < b->GetExpirationTime();
michael@0 555 }
michael@0 556 };
michael@0 557
michael@0 558 } // anon
michael@0 559
michael@0 560 void
michael@0 561 CacheStorageService::RegisterEntry(CacheEntry* aEntry)
michael@0 562 {
michael@0 563 MOZ_ASSERT(IsOnManagementThread());
michael@0 564
michael@0 565 if (mShutdown || !aEntry->CanRegister())
michael@0 566 return;
michael@0 567
michael@0 568 LOG(("CacheStorageService::RegisterEntry [entry=%p]", aEntry));
michael@0 569
michael@0 570 MemoryPool& pool = Pool(aEntry->IsUsingDisk());
michael@0 571 pool.mFrecencyArray.InsertElementSorted(aEntry, FrecencyComparator());
michael@0 572 pool.mExpirationArray.InsertElementSorted(aEntry, ExpirationComparator());
michael@0 573
michael@0 574 aEntry->SetRegistered(true);
michael@0 575 }
michael@0 576
michael@0 577 void
michael@0 578 CacheStorageService::UnregisterEntry(CacheEntry* aEntry)
michael@0 579 {
michael@0 580 MOZ_ASSERT(IsOnManagementThread());
michael@0 581
michael@0 582 if (!aEntry->IsRegistered())
michael@0 583 return;
michael@0 584
michael@0 585 LOG(("CacheStorageService::UnregisterEntry [entry=%p]", aEntry));
michael@0 586
michael@0 587 MemoryPool& pool = Pool(aEntry->IsUsingDisk());
michael@0 588 mozilla::DebugOnly<bool> removedFrecency = pool.mFrecencyArray.RemoveElement(aEntry);
michael@0 589 mozilla::DebugOnly<bool> removedExpiration = pool.mExpirationArray.RemoveElement(aEntry);
michael@0 590
michael@0 591 MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
michael@0 592
michael@0 593 // Note: aEntry->CanRegister() since now returns false
michael@0 594 aEntry->SetRegistered(false);
michael@0 595 }
michael@0 596
michael@0 597 static bool
michael@0 598 AddExactEntry(CacheEntryTable* aEntries,
michael@0 599 nsCString const& aKey,
michael@0 600 CacheEntry* aEntry,
michael@0 601 bool aOverwrite)
michael@0 602 {
michael@0 603 nsRefPtr<CacheEntry> existingEntry;
michael@0 604 if (!aOverwrite && aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
michael@0 605 bool equals = existingEntry == aEntry;
michael@0 606 LOG(("AddExactEntry [entry=%p equals=%d]", aEntry, equals));
michael@0 607 return equals; // Already there...
michael@0 608 }
michael@0 609
michael@0 610 LOG(("AddExactEntry [entry=%p put]", aEntry));
michael@0 611 aEntries->Put(aKey, aEntry);
michael@0 612 return true;
michael@0 613 }
michael@0 614
michael@0 615 static bool
michael@0 616 RemoveExactEntry(CacheEntryTable* aEntries,
michael@0 617 nsCString const& aKey,
michael@0 618 CacheEntry* aEntry,
michael@0 619 bool aOverwrite)
michael@0 620 {
michael@0 621 nsRefPtr<CacheEntry> existingEntry;
michael@0 622 if (!aEntries->Get(aKey, getter_AddRefs(existingEntry))) {
michael@0 623 LOG(("RemoveExactEntry [entry=%p already gone]", aEntry));
michael@0 624 return false; // Already removed...
michael@0 625 }
michael@0 626
michael@0 627 if (!aOverwrite && existingEntry != aEntry) {
michael@0 628 LOG(("RemoveExactEntry [entry=%p already replaced]", aEntry));
michael@0 629 return false; // Already replaced...
michael@0 630 }
michael@0 631
michael@0 632 LOG(("RemoveExactEntry [entry=%p removed]", aEntry));
michael@0 633 aEntries->Remove(aKey);
michael@0 634 return true;
michael@0 635 }
michael@0 636
michael@0 637 bool
michael@0 638 CacheStorageService::RemoveEntry(CacheEntry* aEntry, bool aOnlyUnreferenced)
michael@0 639 {
michael@0 640 LOG(("CacheStorageService::RemoveEntry [entry=%p]", aEntry));
michael@0 641
michael@0 642 nsAutoCString entryKey;
michael@0 643 nsresult rv = aEntry->HashingKey(entryKey);
michael@0 644 if (NS_FAILED(rv)) {
michael@0 645 NS_ERROR("aEntry->HashingKey() failed?");
michael@0 646 return false;
michael@0 647 }
michael@0 648
michael@0 649 mozilla::MutexAutoLock lock(mLock);
michael@0 650
michael@0 651 if (mShutdown) {
michael@0 652 LOG((" after shutdown"));
michael@0 653 return false;
michael@0 654 }
michael@0 655
michael@0 656 if (aOnlyUnreferenced && aEntry->IsReferenced()) {
michael@0 657 LOG((" still referenced, not removing"));
michael@0 658 return false;
michael@0 659 }
michael@0 660
michael@0 661 CacheEntryTable* entries;
michael@0 662 if (sGlobalEntryTables->Get(aEntry->GetStorageID(), &entries))
michael@0 663 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
michael@0 664
michael@0 665 nsAutoCString memoryStorageID(aEntry->GetStorageID());
michael@0 666 AppendMemoryStorageID(memoryStorageID);
michael@0 667
michael@0 668 if (sGlobalEntryTables->Get(memoryStorageID, &entries))
michael@0 669 RemoveExactEntry(entries, entryKey, aEntry, false /* don't overwrite */);
michael@0 670
michael@0 671 return true;
michael@0 672 }
michael@0 673
michael@0 674 void
michael@0 675 CacheStorageService::RecordMemoryOnlyEntry(CacheEntry* aEntry,
michael@0 676 bool aOnlyInMemory,
michael@0 677 bool aOverwrite)
michael@0 678 {
michael@0 679 LOG(("CacheStorageService::RecordMemoryOnlyEntry [entry=%p, memory=%d, overwrite=%d]",
michael@0 680 aEntry, aOnlyInMemory, aOverwrite));
michael@0 681 // This method is responsible to put this entry to a special record hashtable
michael@0 682 // that contains only entries that are stored in memory.
michael@0 683 // Keep in mind that every entry, regardless of whether is in-memory-only or not
michael@0 684 // is always recorded in the storage master hash table, the one identified by
michael@0 685 // CacheEntry.StorageID().
michael@0 686
michael@0 687 mLock.AssertCurrentThreadOwns();
michael@0 688
michael@0 689 if (mShutdown) {
michael@0 690 LOG((" after shutdown"));
michael@0 691 return;
michael@0 692 }
michael@0 693
michael@0 694 nsresult rv;
michael@0 695
michael@0 696 nsAutoCString entryKey;
michael@0 697 rv = aEntry->HashingKey(entryKey);
michael@0 698 if (NS_FAILED(rv)) {
michael@0 699 NS_ERROR("aEntry->HashingKey() failed?");
michael@0 700 return;
michael@0 701 }
michael@0 702
michael@0 703 CacheEntryTable* entries = nullptr;
michael@0 704 nsAutoCString memoryStorageID(aEntry->GetStorageID());
michael@0 705 AppendMemoryStorageID(memoryStorageID);
michael@0 706
michael@0 707 if (!sGlobalEntryTables->Get(memoryStorageID, &entries)) {
michael@0 708 if (!aOnlyInMemory) {
michael@0 709 LOG((" not recorded as memory only"));
michael@0 710 return;
michael@0 711 }
michael@0 712
michael@0 713 entries = new CacheEntryTable(CacheEntryTable::MEMORY_ONLY);
michael@0 714 sGlobalEntryTables->Put(memoryStorageID, entries);
michael@0 715 LOG((" new memory-only storage table for %s", memoryStorageID.get()));
michael@0 716 }
michael@0 717
michael@0 718 if (aOnlyInMemory) {
michael@0 719 AddExactEntry(entries, entryKey, aEntry, aOverwrite);
michael@0 720 }
michael@0 721 else {
michael@0 722 RemoveExactEntry(entries, entryKey, aEntry, aOverwrite);
michael@0 723 }
michael@0 724 }
michael@0 725
michael@0 726 void
michael@0 727 CacheStorageService::OnMemoryConsumptionChange(CacheMemoryConsumer* aConsumer,
michael@0 728 uint32_t aCurrentMemoryConsumption)
michael@0 729 {
michael@0 730 LOG(("CacheStorageService::OnMemoryConsumptionChange [consumer=%p, size=%u]",
michael@0 731 aConsumer, aCurrentMemoryConsumption));
michael@0 732
michael@0 733 uint32_t savedMemorySize = aConsumer->mReportedMemoryConsumption;
michael@0 734 if (savedMemorySize == aCurrentMemoryConsumption)
michael@0 735 return;
michael@0 736
michael@0 737 // Exchange saved size with current one.
michael@0 738 aConsumer->mReportedMemoryConsumption = aCurrentMemoryConsumption;
michael@0 739
michael@0 740 bool usingDisk = !(aConsumer->mFlags & CacheMemoryConsumer::MEMORY_ONLY);
michael@0 741 bool overLimit = Pool(usingDisk).OnMemoryConsumptionChange(
michael@0 742 savedMemorySize, aCurrentMemoryConsumption);
michael@0 743
michael@0 744 if (!overLimit)
michael@0 745 return;
michael@0 746
michael@0 747 // It's likely the timer has already been set when we get here,
michael@0 748 // check outside the lock to save resources.
michael@0 749 if (mPurgeTimer)
michael@0 750 return;
michael@0 751
michael@0 752 // We don't know if this is called under the service lock or not,
michael@0 753 // hence rather dispatch.
michael@0 754 nsRefPtr<nsIEventTarget> cacheIOTarget = Thread();
michael@0 755 if (!cacheIOTarget)
michael@0 756 return;
michael@0 757
michael@0 758 // Dispatch as a priority task, we want to set the purge timer
michael@0 759 // ASAP to prevent vain redispatch of this event.
michael@0 760 nsCOMPtr<nsIRunnable> event =
michael@0 761 NS_NewRunnableMethod(this, &CacheStorageService::SchedulePurgeOverMemoryLimit);
michael@0 762 cacheIOTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
michael@0 763 }
michael@0 764
michael@0 765 bool
michael@0 766 CacheStorageService::MemoryPool::OnMemoryConsumptionChange(uint32_t aSavedMemorySize,
michael@0 767 uint32_t aCurrentMemoryConsumption)
michael@0 768 {
michael@0 769 mMemorySize -= aSavedMemorySize;
michael@0 770 mMemorySize += aCurrentMemoryConsumption;
michael@0 771
michael@0 772 LOG((" mMemorySize=%u (+%u,-%u)", uint32_t(mMemorySize), aCurrentMemoryConsumption, aSavedMemorySize));
michael@0 773
michael@0 774 // Bypass purging when memory has not grew up significantly
michael@0 775 if (aCurrentMemoryConsumption <= aSavedMemorySize)
michael@0 776 return false;
michael@0 777
michael@0 778 return mMemorySize > Limit();
michael@0 779 }
michael@0 780
michael@0 781 void
michael@0 782 CacheStorageService::SchedulePurgeOverMemoryLimit()
michael@0 783 {
michael@0 784 mozilla::MutexAutoLock lock(mLock);
michael@0 785
michael@0 786 if (mPurgeTimer)
michael@0 787 return;
michael@0 788
michael@0 789 mPurgeTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
michael@0 790 if (mPurgeTimer)
michael@0 791 mPurgeTimer->InitWithCallback(this, 1000, nsITimer::TYPE_ONE_SHOT);
michael@0 792 }
michael@0 793
michael@0 794 NS_IMETHODIMP
michael@0 795 CacheStorageService::Notify(nsITimer* aTimer)
michael@0 796 {
michael@0 797 if (aTimer == mPurgeTimer) {
michael@0 798 mPurgeTimer = nullptr;
michael@0 799
michael@0 800 nsCOMPtr<nsIRunnable> event =
michael@0 801 NS_NewRunnableMethod(this, &CacheStorageService::PurgeOverMemoryLimit);
michael@0 802 Dispatch(event);
michael@0 803 }
michael@0 804
michael@0 805 return NS_OK;
michael@0 806 }
michael@0 807
michael@0 808 void
michael@0 809 CacheStorageService::PurgeOverMemoryLimit()
michael@0 810 {
michael@0 811 MOZ_ASSERT(IsOnManagementThread());
michael@0 812
michael@0 813 LOG(("CacheStorageService::PurgeOverMemoryLimit"));
michael@0 814
michael@0 815 Pool(true).PurgeOverMemoryLimit();
michael@0 816 Pool(false).PurgeOverMemoryLimit();
michael@0 817 }
michael@0 818
michael@0 819 void
michael@0 820 CacheStorageService::MemoryPool::PurgeOverMemoryLimit()
michael@0 821 {
michael@0 822 #ifdef PR_LOGGING
michael@0 823 TimeStamp start(TimeStamp::Now());
michael@0 824 #endif
michael@0 825
michael@0 826 uint32_t const memoryLimit = Limit();
michael@0 827 if (mMemorySize > memoryLimit) {
michael@0 828 LOG((" memory data consumption over the limit, abandon expired entries"));
michael@0 829 PurgeExpired();
michael@0 830 }
michael@0 831
michael@0 832 bool frecencyNeedsSort = true;
michael@0 833
michael@0 834 // No longer makes sense since:
michael@0 835 // Memory entries are never purged partially, only as a whole when the memory
michael@0 836 // cache limit is overreached.
michael@0 837 // Disk entries throw the data away ASAP so that only metadata are kept.
michael@0 838 // TODO when this concept of two separate pools is found working, the code should
michael@0 839 // clean up.
michael@0 840 #if 0
michael@0 841 if (mMemorySize > memoryLimit) {
michael@0 842 LOG((" memory data consumption over the limit, abandon disk backed data"));
michael@0 843 PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_DATA_ONLY_DISK_BACKED);
michael@0 844 }
michael@0 845
michael@0 846 if (mMemorySize > memoryLimit) {
michael@0 847 LOG((" metadata consumtion over the limit, abandon disk backed entries"));
michael@0 848 PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED);
michael@0 849 }
michael@0 850 #endif
michael@0 851
michael@0 852 if (mMemorySize > memoryLimit) {
michael@0 853 LOG((" memory data consumption over the limit, abandon any entry"));
michael@0 854 PurgeByFrecency(frecencyNeedsSort, CacheEntry::PURGE_WHOLE);
michael@0 855 }
michael@0 856
michael@0 857 LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
michael@0 858 }
michael@0 859
michael@0 860 void
michael@0 861 CacheStorageService::MemoryPool::PurgeExpired()
michael@0 862 {
michael@0 863 MOZ_ASSERT(IsOnManagementThread());
michael@0 864
michael@0 865 mExpirationArray.Sort(ExpirationComparator());
michael@0 866 uint32_t now = NowInSeconds();
michael@0 867
michael@0 868 uint32_t const memoryLimit = Limit();
michael@0 869
michael@0 870 for (uint32_t i = 0; mMemorySize > memoryLimit && i < mExpirationArray.Length();) {
michael@0 871 if (CacheIOThread::YieldAndRerun())
michael@0 872 return;
michael@0 873
michael@0 874 nsRefPtr<CacheEntry> entry = mExpirationArray[i];
michael@0 875
michael@0 876 uint32_t expirationTime = entry->GetExpirationTime();
michael@0 877 if (expirationTime > 0 && expirationTime <= now) {
michael@0 878 LOG((" dooming expired entry=%p, exptime=%u (now=%u)",
michael@0 879 entry.get(), entry->GetExpirationTime(), now));
michael@0 880
michael@0 881 entry->PurgeAndDoom();
michael@0 882 continue;
michael@0 883 }
michael@0 884
michael@0 885 // not purged, move to the next one
michael@0 886 ++i;
michael@0 887 }
michael@0 888 }
michael@0 889
michael@0 890 void
michael@0 891 CacheStorageService::MemoryPool::PurgeByFrecency(bool &aFrecencyNeedsSort, uint32_t aWhat)
michael@0 892 {
michael@0 893 MOZ_ASSERT(IsOnManagementThread());
michael@0 894
michael@0 895 if (aFrecencyNeedsSort) {
michael@0 896 mFrecencyArray.Sort(FrecencyComparator());
michael@0 897 aFrecencyNeedsSort = false;
michael@0 898 }
michael@0 899
michael@0 900 uint32_t const memoryLimit = Limit();
michael@0 901
michael@0 902 for (uint32_t i = 0; mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
michael@0 903 if (CacheIOThread::YieldAndRerun())
michael@0 904 return;
michael@0 905
michael@0 906 nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
michael@0 907
michael@0 908 if (entry->Purge(aWhat)) {
michael@0 909 LOG((" abandoned (%d), entry=%p, frecency=%1.10f",
michael@0 910 aWhat, entry.get(), entry->GetFrecency()));
michael@0 911 continue;
michael@0 912 }
michael@0 913
michael@0 914 // not purged, move to the next one
michael@0 915 ++i;
michael@0 916 }
michael@0 917 }
michael@0 918
michael@0 919 void
michael@0 920 CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat)
michael@0 921 {
michael@0 922 LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
michael@0 923 MOZ_ASSERT(IsOnManagementThread());
michael@0 924
michael@0 925 for (uint32_t i = 0; i < mFrecencyArray.Length();) {
michael@0 926 if (CacheIOThread::YieldAndRerun())
michael@0 927 return;
michael@0 928
michael@0 929 nsRefPtr<CacheEntry> entry = mFrecencyArray[i];
michael@0 930
michael@0 931 if (entry->Purge(aWhat)) {
michael@0 932 LOG((" abandoned entry=%p", entry.get()));
michael@0 933 continue;
michael@0 934 }
michael@0 935
michael@0 936 // not purged, move to the next one
michael@0 937 ++i;
michael@0 938 }
michael@0 939 }
michael@0 940
michael@0 941 // Methods exposed to and used by CacheStorage.
michael@0 942
michael@0 943 nsresult
michael@0 944 CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
michael@0 945 nsIURI* aURI,
michael@0 946 const nsACString & aIdExtension,
michael@0 947 bool aCreateIfNotExist,
michael@0 948 bool aReplace,
michael@0 949 CacheEntryHandle** aResult)
michael@0 950 {
michael@0 951 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 952
michael@0 953 NS_ENSURE_ARG(aStorage);
michael@0 954
michael@0 955 nsAutoCString contextKey;
michael@0 956 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
michael@0 957
michael@0 958 return AddStorageEntry(contextKey, aURI, aIdExtension,
michael@0 959 aStorage->WriteToDisk(), aCreateIfNotExist, aReplace,
michael@0 960 aResult);
michael@0 961 }
michael@0 962
michael@0 963 nsresult
michael@0 964 CacheStorageService::AddStorageEntry(nsCSubstring const& aContextKey,
michael@0 965 nsIURI* aURI,
michael@0 966 const nsACString & aIdExtension,
michael@0 967 bool aWriteToDisk,
michael@0 968 bool aCreateIfNotExist,
michael@0 969 bool aReplace,
michael@0 970 CacheEntryHandle** aResult)
michael@0 971 {
michael@0 972 NS_ENSURE_ARG(aURI);
michael@0 973
michael@0 974 nsresult rv;
michael@0 975
michael@0 976 nsAutoCString entryKey;
michael@0 977 rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
michael@0 978 NS_ENSURE_SUCCESS(rv, rv);
michael@0 979
michael@0 980 LOG(("CacheStorageService::AddStorageEntry [entryKey=%s, contextKey=%s]",
michael@0 981 entryKey.get(), aContextKey.BeginReading()));
michael@0 982
michael@0 983 nsRefPtr<CacheEntry> entry;
michael@0 984 nsRefPtr<CacheEntryHandle> handle;
michael@0 985
michael@0 986 {
michael@0 987 mozilla::MutexAutoLock lock(mLock);
michael@0 988
michael@0 989 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 990
michael@0 991 // Ensure storage table
michael@0 992 CacheEntryTable* entries;
michael@0 993 if (!sGlobalEntryTables->Get(aContextKey, &entries)) {
michael@0 994 entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES);
michael@0 995 sGlobalEntryTables->Put(aContextKey, entries);
michael@0 996 LOG((" new storage entries table for context %s", aContextKey.BeginReading()));
michael@0 997 }
michael@0 998
michael@0 999 bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
michael@0 1000
michael@0 1001 // check whether the file is already doomed
michael@0 1002 if (entryExists && entry->IsFileDoomed() && !aReplace) {
michael@0 1003 LOG((" file already doomed, replacing the entry"));
michael@0 1004 aReplace = true;
michael@0 1005 }
michael@0 1006
michael@0 1007 // If truncate is demanded, delete and doom the current entry
michael@0 1008 if (entryExists && aReplace) {
michael@0 1009 entries->Remove(entryKey);
michael@0 1010
michael@0 1011 LOG((" dooming entry %p for %s because of OPEN_TRUNCATE", entry.get(), entryKey.get()));
michael@0 1012 // On purpose called under the lock to prevent races of doom and open on I/O thread
michael@0 1013 // No need to remove from both memory-only and all-entries tables. The new entry
michael@0 1014 // will overwrite the shadow entry in its ctor.
michael@0 1015 entry->DoomAlreadyRemoved();
michael@0 1016
michael@0 1017 entry = nullptr;
michael@0 1018 entryExists = false;
michael@0 1019 }
michael@0 1020
michael@0 1021 if (entryExists && entry->SetUsingDisk(aWriteToDisk)) {
michael@0 1022 RecordMemoryOnlyEntry(entry, !aWriteToDisk, true /* overwrite */);
michael@0 1023 }
michael@0 1024
michael@0 1025 // Ensure entry for the particular URL, if not read/only
michael@0 1026 if (!entryExists && (aCreateIfNotExist || aReplace)) {
michael@0 1027 // Entry is not in the hashtable or has just been truncated...
michael@0 1028 entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk);
michael@0 1029 entries->Put(entryKey, entry);
michael@0 1030 LOG((" new entry %p for %s", entry.get(), entryKey.get()));
michael@0 1031 }
michael@0 1032
michael@0 1033 if (entry) {
michael@0 1034 // Here, if this entry was not for a long time referenced by any consumer,
michael@0 1035 // gets again first 'handles count' reference.
michael@0 1036 handle = entry->NewHandle();
michael@0 1037 }
michael@0 1038 }
michael@0 1039
michael@0 1040 handle.forget(aResult);
michael@0 1041 return NS_OK;
michael@0 1042 }
michael@0 1043
michael@0 1044 namespace { // anon
michael@0 1045
michael@0 1046 class CacheEntryDoomByKeyCallback : public CacheFileIOListener
michael@0 1047 {
michael@0 1048 public:
michael@0 1049 NS_DECL_THREADSAFE_ISUPPORTS
michael@0 1050
michael@0 1051 CacheEntryDoomByKeyCallback(nsICacheEntryDoomCallback* aCallback)
michael@0 1052 : mCallback(aCallback) { }
michael@0 1053 virtual ~CacheEntryDoomByKeyCallback();
michael@0 1054
michael@0 1055 private:
michael@0 1056 NS_IMETHOD OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
michael@0 1057 NS_IMETHOD OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, nsresult aResult) { return NS_OK; }
michael@0 1058 NS_IMETHOD OnDataRead(CacheFileHandle *aHandle, char *aBuf, nsresult aResult) { return NS_OK; }
michael@0 1059 NS_IMETHOD OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult);
michael@0 1060 NS_IMETHOD OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
michael@0 1061 NS_IMETHOD OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) { return NS_OK; }
michael@0 1062
michael@0 1063 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
michael@0 1064 };
michael@0 1065
michael@0 1066 CacheEntryDoomByKeyCallback::~CacheEntryDoomByKeyCallback()
michael@0 1067 {
michael@0 1068 if (mCallback)
michael@0 1069 ProxyReleaseMainThread(mCallback);
michael@0 1070 }
michael@0 1071
michael@0 1072 NS_IMETHODIMP CacheEntryDoomByKeyCallback::OnFileDoomed(CacheFileHandle *aHandle,
michael@0 1073 nsresult aResult)
michael@0 1074 {
michael@0 1075 if (!mCallback)
michael@0 1076 return NS_OK;
michael@0 1077
michael@0 1078 mCallback->OnCacheEntryDoomed(aResult);
michael@0 1079 return NS_OK;
michael@0 1080 }
michael@0 1081
michael@0 1082 NS_IMPL_ISUPPORTS(CacheEntryDoomByKeyCallback, CacheFileIOListener);
michael@0 1083
michael@0 1084 } // anon
michael@0 1085
michael@0 1086 nsresult
michael@0 1087 CacheStorageService::DoomStorageEntry(CacheStorage const* aStorage,
michael@0 1088 nsIURI *aURI,
michael@0 1089 const nsACString & aIdExtension,
michael@0 1090 nsICacheEntryDoomCallback* aCallback)
michael@0 1091 {
michael@0 1092 LOG(("CacheStorageService::DoomStorageEntry"));
michael@0 1093
michael@0 1094 NS_ENSURE_ARG(aStorage);
michael@0 1095 NS_ENSURE_ARG(aURI);
michael@0 1096
michael@0 1097 nsAutoCString contextKey;
michael@0 1098 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
michael@0 1099
michael@0 1100 nsAutoCString entryKey;
michael@0 1101 nsresult rv = CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURI, entryKey);
michael@0 1102 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1103
michael@0 1104 nsRefPtr<CacheEntry> entry;
michael@0 1105 {
michael@0 1106 mozilla::MutexAutoLock lock(mLock);
michael@0 1107
michael@0 1108 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 1109
michael@0 1110 CacheEntryTable* entries;
michael@0 1111 if (sGlobalEntryTables->Get(contextKey, &entries)) {
michael@0 1112 if (entries->Get(entryKey, getter_AddRefs(entry))) {
michael@0 1113 if (aStorage->WriteToDisk() || !entry->IsUsingDiskLocked()) {
michael@0 1114 // When evicting from disk storage, purge
michael@0 1115 // When evicting from memory storage and the entry is memory-only, purge
michael@0 1116 LOG((" purging entry %p for %s [storage use disk=%d, entry use disk=%d]",
michael@0 1117 entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->IsUsingDiskLocked()));
michael@0 1118 entries->Remove(entryKey);
michael@0 1119 }
michael@0 1120 else {
michael@0 1121 // Otherwise, leave it
michael@0 1122 LOG((" leaving entry %p for %s [storage use disk=%d, entry use disk=%d]",
michael@0 1123 entry.get(), entryKey.get(), aStorage->WriteToDisk(), entry->IsUsingDiskLocked()));
michael@0 1124 entry = nullptr;
michael@0 1125 }
michael@0 1126 }
michael@0 1127 }
michael@0 1128 }
michael@0 1129
michael@0 1130 if (entry) {
michael@0 1131 LOG((" dooming entry %p for %s", entry.get(), entryKey.get()));
michael@0 1132 return entry->AsyncDoom(aCallback);
michael@0 1133 }
michael@0 1134
michael@0 1135 LOG((" no entry loaded for %s", entryKey.get()));
michael@0 1136
michael@0 1137 if (aStorage->WriteToDisk()) {
michael@0 1138 nsAutoCString contextKey;
michael@0 1139 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
michael@0 1140
michael@0 1141 rv = CacheEntry::HashingKey(contextKey, aIdExtension, aURI, entryKey);
michael@0 1142 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1143
michael@0 1144 LOG((" dooming file only for %s", entryKey.get()));
michael@0 1145
michael@0 1146 nsRefPtr<CacheEntryDoomByKeyCallback> callback(
michael@0 1147 new CacheEntryDoomByKeyCallback(aCallback));
michael@0 1148 rv = CacheFileIOManager::DoomFileByKey(entryKey, callback);
michael@0 1149 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1150
michael@0 1151 return NS_OK;
michael@0 1152 }
michael@0 1153
michael@0 1154 if (aCallback)
michael@0 1155 aCallback->OnCacheEntryDoomed(NS_ERROR_NOT_AVAILABLE);
michael@0 1156
michael@0 1157 return NS_OK;
michael@0 1158 }
michael@0 1159
michael@0 1160 nsresult
michael@0 1161 CacheStorageService::DoomStorageEntries(CacheStorage const* aStorage,
michael@0 1162 nsICacheEntryDoomCallback* aCallback)
michael@0 1163 {
michael@0 1164 LOG(("CacheStorageService::DoomStorageEntries"));
michael@0 1165
michael@0 1166 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 1167 NS_ENSURE_ARG(aStorage);
michael@0 1168
michael@0 1169 nsAutoCString contextKey;
michael@0 1170 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
michael@0 1171
michael@0 1172 mozilla::MutexAutoLock lock(mLock);
michael@0 1173
michael@0 1174 return DoomStorageEntries(contextKey, aStorage->LoadInfo(),
michael@0 1175 aStorage->WriteToDisk(), aCallback);
michael@0 1176 }
michael@0 1177
michael@0 1178 nsresult
michael@0 1179 CacheStorageService::DoomStorageEntries(nsCSubstring const& aContextKey,
michael@0 1180 nsILoadContextInfo* aContext,
michael@0 1181 bool aDiskStorage,
michael@0 1182 nsICacheEntryDoomCallback* aCallback)
michael@0 1183 {
michael@0 1184 mLock.AssertCurrentThreadOwns();
michael@0 1185
michael@0 1186 NS_ENSURE_TRUE(!mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 1187
michael@0 1188 nsAutoCString memoryStorageID(aContextKey);
michael@0 1189 AppendMemoryStorageID(memoryStorageID);
michael@0 1190
michael@0 1191 if (aDiskStorage) {
michael@0 1192 LOG((" dooming disk+memory storage of %s", aContextKey.BeginReading()));
michael@0 1193
michael@0 1194 // Just remove all entries, CacheFileIOManager will take care of the files.
michael@0 1195 sGlobalEntryTables->Remove(aContextKey);
michael@0 1196 sGlobalEntryTables->Remove(memoryStorageID);
michael@0 1197
michael@0 1198 if (aContext && !aContext->IsPrivate()) {
michael@0 1199 LOG((" dooming disk entries"));
michael@0 1200 CacheFileIOManager::EvictByContext(aContext);
michael@0 1201 }
michael@0 1202 } else {
michael@0 1203 LOG((" dooming memory-only storage of %s", aContextKey.BeginReading()));
michael@0 1204
michael@0 1205 class MemoryEntriesRemoval {
michael@0 1206 public:
michael@0 1207 static PLDHashOperator EvictEntry(const nsACString& aKey,
michael@0 1208 CacheEntry* aEntry,
michael@0 1209 void* aClosure)
michael@0 1210 {
michael@0 1211 CacheEntryTable* entries = static_cast<CacheEntryTable*>(aClosure);
michael@0 1212 nsCString key(aKey);
michael@0 1213 RemoveExactEntry(entries, key, aEntry, false);
michael@0 1214 return PL_DHASH_NEXT;
michael@0 1215 }
michael@0 1216 };
michael@0 1217
michael@0 1218 // Remove the memory entries table from the global tables.
michael@0 1219 // Since we store memory entries also in the disk entries table
michael@0 1220 // we need to remove the memory entries from the disk table one
michael@0 1221 // by one manually.
michael@0 1222 nsAutoPtr<CacheEntryTable> memoryEntries;
michael@0 1223 sGlobalEntryTables->RemoveAndForget(memoryStorageID, memoryEntries);
michael@0 1224
michael@0 1225 CacheEntryTable* entries;
michael@0 1226 sGlobalEntryTables->Get(aContextKey, &entries);
michael@0 1227 if (memoryEntries && entries)
michael@0 1228 memoryEntries->EnumerateRead(&MemoryEntriesRemoval::EvictEntry, entries);
michael@0 1229 }
michael@0 1230
michael@0 1231 // An artificial callback. This is a candidate for removal tho. In the new
michael@0 1232 // cache any 'doom' or 'evict' function ensures that the entry or entries
michael@0 1233 // being doomed is/are not accessible after the function returns. So there is
michael@0 1234 // probably no need for a callback - has no meaning. But for compatibility
michael@0 1235 // with the old cache that is still in the tree we keep the API similar to be
michael@0 1236 // able to make tests as well as other consumers work for now.
michael@0 1237 class Callback : public nsRunnable
michael@0 1238 {
michael@0 1239 public:
michael@0 1240 Callback(nsICacheEntryDoomCallback* aCallback) : mCallback(aCallback) { }
michael@0 1241 NS_IMETHODIMP Run()
michael@0 1242 {
michael@0 1243 mCallback->OnCacheEntryDoomed(NS_OK);
michael@0 1244 return NS_OK;
michael@0 1245 }
michael@0 1246 nsCOMPtr<nsICacheEntryDoomCallback> mCallback;
michael@0 1247 };
michael@0 1248
michael@0 1249 if (aCallback) {
michael@0 1250 nsRefPtr<nsRunnable> callback = new Callback(aCallback);
michael@0 1251 return NS_DispatchToCurrentThread(callback);
michael@0 1252 }
michael@0 1253
michael@0 1254 return NS_OK;
michael@0 1255 }
michael@0 1256
michael@0 1257 nsresult
michael@0 1258 CacheStorageService::WalkStorageEntries(CacheStorage const* aStorage,
michael@0 1259 bool aVisitEntries,
michael@0 1260 nsICacheStorageVisitor* aVisitor)
michael@0 1261 {
michael@0 1262 LOG(("CacheStorageService::WalkStorageEntries [cb=%p, visitentries=%d]", aVisitor, aVisitEntries));
michael@0 1263 NS_ENSURE_FALSE(mShutdown, NS_ERROR_NOT_INITIALIZED);
michael@0 1264
michael@0 1265 NS_ENSURE_ARG(aStorage);
michael@0 1266
michael@0 1267 nsAutoCString contextKey;
michael@0 1268 CacheFileUtils::AppendKeyPrefix(aStorage->LoadInfo(), contextKey);
michael@0 1269
michael@0 1270 nsRefPtr<WalkRunnable> event = new WalkRunnable(
michael@0 1271 contextKey, aVisitEntries, aStorage->WriteToDisk(), aVisitor);
michael@0 1272 return Dispatch(event);
michael@0 1273 }
michael@0 1274
michael@0 1275 void
michael@0 1276 CacheStorageService::CacheFileDoomed(nsILoadContextInfo* aLoadContextInfo,
michael@0 1277 const nsACString & aIdExtension,
michael@0 1278 const nsACString & aURISpec)
michael@0 1279 {
michael@0 1280 nsAutoCString contextKey;
michael@0 1281 CacheFileUtils::AppendKeyPrefix(aLoadContextInfo, contextKey);
michael@0 1282
michael@0 1283 nsAutoCString entryKey;
michael@0 1284 CacheEntry::HashingKey(EmptyCString(), aIdExtension, aURISpec, entryKey);
michael@0 1285
michael@0 1286 mozilla::MutexAutoLock lock(mLock);
michael@0 1287
michael@0 1288 if (mShutdown)
michael@0 1289 return;
michael@0 1290
michael@0 1291 CacheEntryTable* entries;
michael@0 1292 if (!sGlobalEntryTables->Get(contextKey, &entries))
michael@0 1293 return;
michael@0 1294
michael@0 1295 nsRefPtr<CacheEntry> entry;
michael@0 1296 if (!entries->Get(entryKey, getter_AddRefs(entry)))
michael@0 1297 return;
michael@0 1298
michael@0 1299 if (!entry->IsFileDoomed())
michael@0 1300 return;
michael@0 1301
michael@0 1302 if (entry->IsReferenced())
michael@0 1303 return;
michael@0 1304
michael@0 1305 // Need to remove under the lock to avoid possible race leading
michael@0 1306 // to duplication of the entry per its key.
michael@0 1307 RemoveExactEntry(entries, entryKey, entry, false);
michael@0 1308 entry->DoomAlreadyRemoved();
michael@0 1309 }
michael@0 1310
michael@0 1311 // nsIMemoryReporter
michael@0 1312
michael@0 1313 size_t
michael@0 1314 CacheStorageService::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
michael@0 1315 {
michael@0 1316 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 1317
michael@0 1318 size_t n = 0;
michael@0 1319 // The elemets are referenced by sGlobalEntryTables and are reported from there
michael@0 1320 n += Pool(true).mFrecencyArray.SizeOfExcludingThis(mallocSizeOf);
michael@0 1321 n += Pool(true).mExpirationArray.SizeOfExcludingThis(mallocSizeOf);
michael@0 1322 n += Pool(false).mFrecencyArray.SizeOfExcludingThis(mallocSizeOf);
michael@0 1323 n += Pool(false).mExpirationArray.SizeOfExcludingThis(mallocSizeOf);
michael@0 1324 // Entries reported manually in CacheStorageService::CollectReports callback
michael@0 1325 if (sGlobalEntryTables) {
michael@0 1326 n += sGlobalEntryTables->SizeOfIncludingThis(nullptr, mallocSizeOf);
michael@0 1327 }
michael@0 1328
michael@0 1329 return n;
michael@0 1330 }
michael@0 1331
michael@0 1332 size_t
michael@0 1333 CacheStorageService::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
michael@0 1334 {
michael@0 1335 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
michael@0 1336 }
michael@0 1337
michael@0 1338 namespace { // anon
michael@0 1339
michael@0 1340 class ReportStorageMemoryData
michael@0 1341 {
michael@0 1342 public:
michael@0 1343 nsIMemoryReporterCallback *mHandleReport;
michael@0 1344 nsISupports *mData;
michael@0 1345 };
michael@0 1346
michael@0 1347 size_t CollectEntryMemory(nsACString const & aKey,
michael@0 1348 nsRefPtr<mozilla::net::CacheEntry> const & aEntry,
michael@0 1349 mozilla::MallocSizeOf mallocSizeOf,
michael@0 1350 void * aClosure)
michael@0 1351 {
michael@0 1352 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 1353
michael@0 1354 CacheEntryTable* aTable = static_cast<CacheEntryTable*>(aClosure);
michael@0 1355
michael@0 1356 size_t n = 0;
michael@0 1357 n += aKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
michael@0 1358
michael@0 1359 // Bypass memory-only entries, those will be reported when iterating
michael@0 1360 // the memory only table. Memory-only entries are stored in both ALL_ENTRIES
michael@0 1361 // and MEMORY_ONLY hashtables.
michael@0 1362 if (aTable->Type() == CacheEntryTable::MEMORY_ONLY || aEntry->IsUsingDiskLocked())
michael@0 1363 n += aEntry->SizeOfIncludingThis(mallocSizeOf);
michael@0 1364
michael@0 1365 return n;
michael@0 1366 }
michael@0 1367
michael@0 1368 PLDHashOperator ReportStorageMemory(const nsACString& aKey,
michael@0 1369 CacheEntryTable* aTable,
michael@0 1370 void* aClosure)
michael@0 1371 {
michael@0 1372 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 1373
michael@0 1374 size_t size = aTable->SizeOfIncludingThis(&CollectEntryMemory,
michael@0 1375 CacheStorageService::MallocSizeOf,
michael@0 1376 aTable);
michael@0 1377
michael@0 1378 ReportStorageMemoryData& data = *static_cast<ReportStorageMemoryData*>(aClosure);
michael@0 1379 data.mHandleReport->Callback(
michael@0 1380 EmptyCString(),
michael@0 1381 nsPrintfCString("explicit/network/cache2/%s-storage(%s)",
michael@0 1382 aTable->Type() == CacheEntryTable::MEMORY_ONLY ? "memory" : "disk",
michael@0 1383 aKey.BeginReading()),
michael@0 1384 nsIMemoryReporter::KIND_HEAP,
michael@0 1385 nsIMemoryReporter::UNITS_BYTES,
michael@0 1386 size,
michael@0 1387 NS_LITERAL_CSTRING("Memory used by the cache storage."),
michael@0 1388 data.mData);
michael@0 1389
michael@0 1390 return PL_DHASH_NEXT;
michael@0 1391 }
michael@0 1392
michael@0 1393 } // anon
michael@0 1394
michael@0 1395 NS_IMETHODIMP
michael@0 1396 CacheStorageService::CollectReports(nsIMemoryReporterCallback* aHandleReport, nsISupports* aData)
michael@0 1397 {
michael@0 1398 nsresult rv;
michael@0 1399
michael@0 1400 rv = MOZ_COLLECT_REPORT(
michael@0 1401 "explicit/network/cache2/io", KIND_HEAP, UNITS_BYTES,
michael@0 1402 CacheFileIOManager::SizeOfIncludingThis(MallocSizeOf),
michael@0 1403 "Memory used by the cache IO manager.");
michael@0 1404 if (NS_WARN_IF(NS_FAILED(rv)))
michael@0 1405 return rv;
michael@0 1406
michael@0 1407 rv = MOZ_COLLECT_REPORT(
michael@0 1408 "explicit/network/cache2/index", KIND_HEAP, UNITS_BYTES,
michael@0 1409 CacheIndex::SizeOfIncludingThis(MallocSizeOf),
michael@0 1410 "Memory used by the cache index.");
michael@0 1411 if (NS_WARN_IF(NS_FAILED(rv)))
michael@0 1412 return rv;
michael@0 1413
michael@0 1414 MutexAutoLock lock(mLock);
michael@0 1415
michael@0 1416 // Report the service instance, this doesn't report entries, done lower
michael@0 1417 rv = MOZ_COLLECT_REPORT(
michael@0 1418 "explicit/network/cache2/service", KIND_HEAP, UNITS_BYTES,
michael@0 1419 SizeOfIncludingThis(MallocSizeOf),
michael@0 1420 "Memory used by the cache storage service.");
michael@0 1421 if (NS_WARN_IF(NS_FAILED(rv)))
michael@0 1422 return rv;
michael@0 1423
michael@0 1424 // Report all entries, each storage separately (by the context key)
michael@0 1425 //
michael@0 1426 // References are:
michael@0 1427 // sGlobalEntryTables to N CacheEntryTable
michael@0 1428 // CacheEntryTable to N CacheEntry
michael@0 1429 // CacheEntry to 1 CacheFile
michael@0 1430 // CacheFile to
michael@0 1431 // N CacheFileChunk (keeping the actual data)
michael@0 1432 // 1 CacheFileMetadata (keeping http headers etc.)
michael@0 1433 // 1 CacheFileOutputStream
michael@0 1434 // N CacheFileInputStream
michael@0 1435 ReportStorageMemoryData data;
michael@0 1436 data.mHandleReport = aHandleReport;
michael@0 1437 data.mData = aData;
michael@0 1438 sGlobalEntryTables->EnumerateRead(&ReportStorageMemory, &data);
michael@0 1439
michael@0 1440 return NS_OK;
michael@0 1441 }
michael@0 1442
michael@0 1443 } // net
michael@0 1444 } // mozilla

mercurial