netwerk/cache/nsDiskCacheDevice.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.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 *
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include <limits.h>
michael@0 8
michael@0 9 #include "mozilla/DebugOnly.h"
michael@0 10
michael@0 11 #include "nsCache.h"
michael@0 12 #include "nsIMemoryReporter.h"
michael@0 13
michael@0 14 // include files for ftruncate (or equivalent)
michael@0 15 #if defined(XP_UNIX)
michael@0 16 #include <unistd.h>
michael@0 17 #elif defined(XP_WIN)
michael@0 18 #include <windows.h>
michael@0 19 #else
michael@0 20 // XXX add necessary include file for ftruncate (or equivalent)
michael@0 21 #endif
michael@0 22
michael@0 23 #include "prthread.h"
michael@0 24
michael@0 25 #include "private/pprio.h"
michael@0 26
michael@0 27 #include "nsDiskCacheDevice.h"
michael@0 28 #include "nsDiskCacheEntry.h"
michael@0 29 #include "nsDiskCacheMap.h"
michael@0 30 #include "nsDiskCacheStreams.h"
michael@0 31
michael@0 32 #include "nsDiskCache.h"
michael@0 33
michael@0 34 #include "nsCacheService.h"
michael@0 35
michael@0 36 #include "nsDeleteDir.h"
michael@0 37
michael@0 38 #include "nsICacheVisitor.h"
michael@0 39 #include "nsReadableUtils.h"
michael@0 40 #include "nsIInputStream.h"
michael@0 41 #include "nsIOutputStream.h"
michael@0 42 #include "nsCRT.h"
michael@0 43 #include "nsCOMArray.h"
michael@0 44 #include "nsISimpleEnumerator.h"
michael@0 45
michael@0 46 #include "nsThreadUtils.h"
michael@0 47 #include "mozilla/MemoryReporting.h"
michael@0 48 #include "mozilla/Telemetry.h"
michael@0 49
michael@0 50 static const char DISK_CACHE_DEVICE_ID[] = { "disk" };
michael@0 51 using namespace mozilla;
michael@0 52
michael@0 53 class nsDiskCacheDeviceDeactivateEntryEvent : public nsRunnable {
michael@0 54 public:
michael@0 55 nsDiskCacheDeviceDeactivateEntryEvent(nsDiskCacheDevice *device,
michael@0 56 nsCacheEntry * entry,
michael@0 57 nsDiskCacheBinding * binding)
michael@0 58 : mCanceled(false),
michael@0 59 mEntry(entry),
michael@0 60 mDevice(device),
michael@0 61 mBinding(binding)
michael@0 62 {
michael@0 63 }
michael@0 64
michael@0 65 NS_IMETHOD Run()
michael@0 66 {
michael@0 67 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEDEVICEDEACTIVATEENTRYEVENT_RUN));
michael@0 68 #ifdef PR_LOGGING
michael@0 69 CACHE_LOG_DEBUG(("nsDiskCacheDeviceDeactivateEntryEvent[%p]\n", this));
michael@0 70 #endif
michael@0 71 if (!mCanceled) {
michael@0 72 (void) mDevice->DeactivateEntry_Private(mEntry, mBinding);
michael@0 73 }
michael@0 74 return NS_OK;
michael@0 75 }
michael@0 76
michael@0 77 void CancelEvent() { mCanceled = true; }
michael@0 78 private:
michael@0 79 bool mCanceled;
michael@0 80 nsCacheEntry *mEntry;
michael@0 81 nsDiskCacheDevice *mDevice;
michael@0 82 nsDiskCacheBinding *mBinding;
michael@0 83 };
michael@0 84
michael@0 85 class nsEvictDiskCacheEntriesEvent : public nsRunnable {
michael@0 86 public:
michael@0 87 nsEvictDiskCacheEntriesEvent(nsDiskCacheDevice *device)
michael@0 88 : mDevice(device) {}
michael@0 89
michael@0 90 NS_IMETHOD Run()
michael@0 91 {
michael@0 92 nsCacheServiceAutoLock lock(LOCK_TELEM(NSEVICTDISKCACHEENTRIESEVENT_RUN));
michael@0 93 mDevice->EvictDiskCacheEntries(mDevice->mCacheCapacity);
michael@0 94 return NS_OK;
michael@0 95 }
michael@0 96
michael@0 97 private:
michael@0 98 nsDiskCacheDevice *mDevice;
michael@0 99 };
michael@0 100
michael@0 101 /******************************************************************************
michael@0 102 * nsDiskCacheEvictor
michael@0 103 *
michael@0 104 * Helper class for nsDiskCacheDevice.
michael@0 105 *
michael@0 106 *****************************************************************************/
michael@0 107
michael@0 108 class nsDiskCacheEvictor : public nsDiskCacheRecordVisitor
michael@0 109 {
michael@0 110 public:
michael@0 111 nsDiskCacheEvictor( nsDiskCacheMap * cacheMap,
michael@0 112 nsDiskCacheBindery * cacheBindery,
michael@0 113 uint32_t targetSize,
michael@0 114 const char * clientID)
michael@0 115 : mCacheMap(cacheMap)
michael@0 116 , mBindery(cacheBindery)
michael@0 117 , mTargetSize(targetSize)
michael@0 118 , mClientID(clientID)
michael@0 119 {
michael@0 120 mClientIDSize = clientID ? strlen(clientID) : 0;
michael@0 121 }
michael@0 122
michael@0 123 virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord);
michael@0 124
michael@0 125 private:
michael@0 126 nsDiskCacheMap * mCacheMap;
michael@0 127 nsDiskCacheBindery * mBindery;
michael@0 128 uint32_t mTargetSize;
michael@0 129 const char * mClientID;
michael@0 130 uint32_t mClientIDSize;
michael@0 131 };
michael@0 132
michael@0 133
michael@0 134 int32_t
michael@0 135 nsDiskCacheEvictor::VisitRecord(nsDiskCacheRecord * mapRecord)
michael@0 136 {
michael@0 137 if (mCacheMap->TotalSize() < mTargetSize)
michael@0 138 return kStopVisitingRecords;
michael@0 139
michael@0 140 if (mClientID) {
michael@0 141 // we're just evicting records for a specific client
michael@0 142 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
michael@0 143 if (!diskEntry)
michael@0 144 return kVisitNextRecord; // XXX or delete record?
michael@0 145
michael@0 146 // Compare clientID's without malloc
michael@0 147 if ((diskEntry->mKeySize <= mClientIDSize) ||
michael@0 148 (diskEntry->Key()[mClientIDSize] != ':') ||
michael@0 149 (memcmp(diskEntry->Key(), mClientID, mClientIDSize) != 0)) {
michael@0 150 return kVisitNextRecord; // clientID doesn't match, skip it
michael@0 151 }
michael@0 152 }
michael@0 153
michael@0 154 nsDiskCacheBinding * binding = mBindery->FindActiveBinding(mapRecord->HashNumber());
michael@0 155 if (binding) {
michael@0 156 // If the entry is pending deactivation, cancel deactivation and doom
michael@0 157 // the entry
michael@0 158 if (binding->mDeactivateEvent) {
michael@0 159 binding->mDeactivateEvent->CancelEvent();
michael@0 160 binding->mDeactivateEvent = nullptr;
michael@0 161 }
michael@0 162 // We are currently using this entry, so all we can do is doom it.
michael@0 163 // Since we're enumerating the records, we don't want to call
michael@0 164 // DeleteRecord when nsCacheService::DoomEntry() calls us back.
michael@0 165 binding->mDoomed = true; // mark binding record as 'deleted'
michael@0 166 nsCacheService::DoomEntry(binding->mCacheEntry);
michael@0 167 } else {
michael@0 168 // entry not in use, just delete storage because we're enumerating the records
michael@0 169 (void) mCacheMap->DeleteStorage(mapRecord);
michael@0 170 }
michael@0 171
michael@0 172 return kDeleteRecordAndContinue; // this will REALLY delete the record
michael@0 173 }
michael@0 174
michael@0 175
michael@0 176 /******************************************************************************
michael@0 177 * nsDiskCacheDeviceInfo
michael@0 178 *****************************************************************************/
michael@0 179
michael@0 180 class nsDiskCacheDeviceInfo : public nsICacheDeviceInfo {
michael@0 181 public:
michael@0 182 NS_DECL_ISUPPORTS
michael@0 183 NS_DECL_NSICACHEDEVICEINFO
michael@0 184
michael@0 185 nsDiskCacheDeviceInfo(nsDiskCacheDevice* device)
michael@0 186 : mDevice(device)
michael@0 187 {
michael@0 188 }
michael@0 189
michael@0 190 virtual ~nsDiskCacheDeviceInfo() {}
michael@0 191
michael@0 192 private:
michael@0 193 nsDiskCacheDevice* mDevice;
michael@0 194 };
michael@0 195
michael@0 196 NS_IMPL_ISUPPORTS(nsDiskCacheDeviceInfo, nsICacheDeviceInfo)
michael@0 197
michael@0 198 /* readonly attribute string description; */
michael@0 199 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetDescription(char ** aDescription)
michael@0 200 {
michael@0 201 NS_ENSURE_ARG_POINTER(aDescription);
michael@0 202 *aDescription = NS_strdup("Disk cache device");
michael@0 203 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
michael@0 204 }
michael@0 205
michael@0 206 /* readonly attribute string usageReport; */
michael@0 207 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetUsageReport(char ** usageReport)
michael@0 208 {
michael@0 209 NS_ENSURE_ARG_POINTER(usageReport);
michael@0 210 nsCString buffer;
michael@0 211
michael@0 212 buffer.AssignLiteral(" <tr>\n"
michael@0 213 " <th>Cache Directory:</th>\n"
michael@0 214 " <td>");
michael@0 215 nsCOMPtr<nsIFile> cacheDir;
michael@0 216 nsAutoString path;
michael@0 217 mDevice->getCacheDirectory(getter_AddRefs(cacheDir));
michael@0 218 nsresult rv = cacheDir->GetPath(path);
michael@0 219 if (NS_SUCCEEDED(rv)) {
michael@0 220 AppendUTF16toUTF8(path, buffer);
michael@0 221 } else {
michael@0 222 buffer.AppendLiteral("directory unavailable");
michael@0 223 }
michael@0 224 buffer.AppendLiteral("</td>\n"
michael@0 225 " </tr>\n");
michael@0 226
michael@0 227 *usageReport = ToNewCString(buffer);
michael@0 228 if (!*usageReport) return NS_ERROR_OUT_OF_MEMORY;
michael@0 229
michael@0 230 return NS_OK;
michael@0 231 }
michael@0 232
michael@0 233 /* readonly attribute unsigned long entryCount; */
michael@0 234 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
michael@0 235 {
michael@0 236 NS_ENSURE_ARG_POINTER(aEntryCount);
michael@0 237 *aEntryCount = mDevice->getEntryCount();
michael@0 238 return NS_OK;
michael@0 239 }
michael@0 240
michael@0 241 /* readonly attribute unsigned long totalSize; */
michael@0 242 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
michael@0 243 {
michael@0 244 NS_ENSURE_ARG_POINTER(aTotalSize);
michael@0 245 // Returned unit's are in bytes
michael@0 246 *aTotalSize = mDevice->getCacheSize() * 1024;
michael@0 247 return NS_OK;
michael@0 248 }
michael@0 249
michael@0 250 /* readonly attribute unsigned long maximumSize; */
michael@0 251 NS_IMETHODIMP nsDiskCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
michael@0 252 {
michael@0 253 NS_ENSURE_ARG_POINTER(aMaximumSize);
michael@0 254 // Returned unit's are in bytes
michael@0 255 *aMaximumSize = mDevice->getCacheCapacity() * 1024;
michael@0 256 return NS_OK;
michael@0 257 }
michael@0 258
michael@0 259
michael@0 260 /******************************************************************************
michael@0 261 * nsDiskCache
michael@0 262 *****************************************************************************/
michael@0 263
michael@0 264 /**
michael@0 265 * nsDiskCache::Hash(const char * key, PLDHashNumber initval)
michael@0 266 *
michael@0 267 * See http://burtleburtle.net/bob/hash/evahash.html for more information
michael@0 268 * about this hash function.
michael@0 269 *
michael@0 270 * This algorithm of this method implies nsDiskCacheRecords will be stored
michael@0 271 * in a certain order on disk. If the algorithm changes, existing cache
michael@0 272 * map files may become invalid, and therefore the kCurrentVersion needs
michael@0 273 * to be revised.
michael@0 274 */
michael@0 275
michael@0 276 static inline void hashmix(uint32_t& a, uint32_t& b, uint32_t& c)
michael@0 277 {
michael@0 278 a -= b; a -= c; a ^= (c>>13);
michael@0 279 b -= c; b -= a; b ^= (a<<8);
michael@0 280 c -= a; c -= b; c ^= (b>>13);
michael@0 281 a -= b; a -= c; a ^= (c>>12);
michael@0 282 b -= c; b -= a; b ^= (a<<16);
michael@0 283 c -= a; c -= b; c ^= (b>>5);
michael@0 284 a -= b; a -= c; a ^= (c>>3);
michael@0 285 b -= c; b -= a; b ^= (a<<10);
michael@0 286 c -= a; c -= b; c ^= (b>>15);
michael@0 287 }
michael@0 288
michael@0 289 PLDHashNumber
michael@0 290 nsDiskCache::Hash(const char * key, PLDHashNumber initval)
michael@0 291 {
michael@0 292 const uint8_t *k = reinterpret_cast<const uint8_t*>(key);
michael@0 293 uint32_t a, b, c, len, length;
michael@0 294
michael@0 295 length = strlen(key);
michael@0 296 /* Set up the internal state */
michael@0 297 len = length;
michael@0 298 a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
michael@0 299 c = initval; /* variable initialization of internal state */
michael@0 300
michael@0 301 /*---------------------------------------- handle most of the key */
michael@0 302 while (len >= 12)
michael@0 303 {
michael@0 304 a += k[0] + (uint32_t(k[1])<<8) + (uint32_t(k[2])<<16) + (uint32_t(k[3])<<24);
michael@0 305 b += k[4] + (uint32_t(k[5])<<8) + (uint32_t(k[6])<<16) + (uint32_t(k[7])<<24);
michael@0 306 c += k[8] + (uint32_t(k[9])<<8) + (uint32_t(k[10])<<16) + (uint32_t(k[11])<<24);
michael@0 307 hashmix(a, b, c);
michael@0 308 k += 12; len -= 12;
michael@0 309 }
michael@0 310
michael@0 311 /*------------------------------------- handle the last 11 bytes */
michael@0 312 c += length;
michael@0 313 switch(len) { /* all the case statements fall through */
michael@0 314 case 11: c += (uint32_t(k[10])<<24);
michael@0 315 case 10: c += (uint32_t(k[9])<<16);
michael@0 316 case 9 : c += (uint32_t(k[8])<<8);
michael@0 317 /* the low-order byte of c is reserved for the length */
michael@0 318 case 8 : b += (uint32_t(k[7])<<24);
michael@0 319 case 7 : b += (uint32_t(k[6])<<16);
michael@0 320 case 6 : b += (uint32_t(k[5])<<8);
michael@0 321 case 5 : b += k[4];
michael@0 322 case 4 : a += (uint32_t(k[3])<<24);
michael@0 323 case 3 : a += (uint32_t(k[2])<<16);
michael@0 324 case 2 : a += (uint32_t(k[1])<<8);
michael@0 325 case 1 : a += k[0];
michael@0 326 /* case 0: nothing left to add */
michael@0 327 }
michael@0 328 hashmix(a, b, c);
michael@0 329
michael@0 330 return c;
michael@0 331 }
michael@0 332
michael@0 333 nsresult
michael@0 334 nsDiskCache::Truncate(PRFileDesc * fd, uint32_t newEOF)
michael@0 335 {
michael@0 336 // use modified SetEOF from nsFileStreams::SetEOF()
michael@0 337
michael@0 338 #if defined(XP_UNIX)
michael@0 339 if (ftruncate(PR_FileDesc2NativeHandle(fd), newEOF) != 0) {
michael@0 340 NS_ERROR("ftruncate failed");
michael@0 341 return NS_ERROR_FAILURE;
michael@0 342 }
michael@0 343
michael@0 344 #elif defined(XP_WIN)
michael@0 345 int32_t cnt = PR_Seek(fd, newEOF, PR_SEEK_SET);
michael@0 346 if (cnt == -1) return NS_ERROR_FAILURE;
michael@0 347 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(fd))) {
michael@0 348 NS_ERROR("SetEndOfFile failed");
michael@0 349 return NS_ERROR_FAILURE;
michael@0 350 }
michael@0 351
michael@0 352 #else
michael@0 353 // add implementations for other platforms here
michael@0 354 #endif
michael@0 355 return NS_OK;
michael@0 356 }
michael@0 357
michael@0 358
michael@0 359 /******************************************************************************
michael@0 360 * nsDiskCacheDevice
michael@0 361 *****************************************************************************/
michael@0 362
michael@0 363 nsDiskCacheDevice::nsDiskCacheDevice()
michael@0 364 : mCacheCapacity(0)
michael@0 365 , mMaxEntrySize(-1) // -1 means "no limit"
michael@0 366 , mInitialized(false)
michael@0 367 , mClearingDiskCache(false)
michael@0 368 {
michael@0 369 }
michael@0 370
michael@0 371 nsDiskCacheDevice::~nsDiskCacheDevice()
michael@0 372 {
michael@0 373 Shutdown();
michael@0 374 }
michael@0 375
michael@0 376
michael@0 377 /**
michael@0 378 * methods of nsCacheDevice
michael@0 379 */
michael@0 380 nsresult
michael@0 381 nsDiskCacheDevice::Init()
michael@0 382 {
michael@0 383 nsresult rv;
michael@0 384
michael@0 385 if (Initialized()) {
michael@0 386 NS_ERROR("Disk cache already initialized!");
michael@0 387 return NS_ERROR_UNEXPECTED;
michael@0 388 }
michael@0 389
michael@0 390 if (!mCacheDirectory)
michael@0 391 return NS_ERROR_FAILURE;
michael@0 392
michael@0 393 rv = mBindery.Init();
michael@0 394 if (NS_FAILED(rv))
michael@0 395 return rv;
michael@0 396
michael@0 397 nsDeleteDir::RemoveOldTrashes(mCacheDirectory);
michael@0 398
michael@0 399 // Open Disk Cache
michael@0 400 rv = OpenDiskCache();
michael@0 401 if (NS_FAILED(rv)) {
michael@0 402 (void) mCacheMap.Close(false);
michael@0 403 return rv;
michael@0 404 }
michael@0 405
michael@0 406 mInitialized = true;
michael@0 407 return NS_OK;
michael@0 408 }
michael@0 409
michael@0 410
michael@0 411 /**
michael@0 412 * NOTE: called while holding the cache service lock
michael@0 413 */
michael@0 414 nsresult
michael@0 415 nsDiskCacheDevice::Shutdown()
michael@0 416 {
michael@0 417 nsCacheService::AssertOwnsLock();
michael@0 418
michael@0 419 nsresult rv = Shutdown_Private(true);
michael@0 420 if (NS_FAILED(rv))
michael@0 421 return rv;
michael@0 422
michael@0 423 return NS_OK;
michael@0 424 }
michael@0 425
michael@0 426
michael@0 427 nsresult
michael@0 428 nsDiskCacheDevice::Shutdown_Private(bool flush)
michael@0 429 {
michael@0 430 CACHE_LOG_DEBUG(("CACHE: disk Shutdown_Private [%u]\n", flush));
michael@0 431
michael@0 432 if (Initialized()) {
michael@0 433 // check cache limits in case we need to evict.
michael@0 434 EvictDiskCacheEntries(mCacheCapacity);
michael@0 435
michael@0 436 // At this point there may be a number of pending cache-requests on the
michael@0 437 // cache-io thread. Wait for all these to run before we wipe out our
michael@0 438 // datastructures (see bug #620660)
michael@0 439 (void) nsCacheService::SyncWithCacheIOThread();
michael@0 440
michael@0 441 // write out persistent information about the cache.
michael@0 442 (void) mCacheMap.Close(flush);
michael@0 443
michael@0 444 mBindery.Reset();
michael@0 445
michael@0 446 mInitialized = false;
michael@0 447 }
michael@0 448
michael@0 449 return NS_OK;
michael@0 450 }
michael@0 451
michael@0 452
michael@0 453 const char *
michael@0 454 nsDiskCacheDevice::GetDeviceID()
michael@0 455 {
michael@0 456 return DISK_CACHE_DEVICE_ID;
michael@0 457 }
michael@0 458
michael@0 459 /**
michael@0 460 * FindEntry -
michael@0 461 *
michael@0 462 * cases: key not in disk cache, hash number free
michael@0 463 * key not in disk cache, hash number used
michael@0 464 * key in disk cache
michael@0 465 *
michael@0 466 * NOTE: called while holding the cache service lock
michael@0 467 */
michael@0 468 nsCacheEntry *
michael@0 469 nsDiskCacheDevice::FindEntry(nsCString * key, bool *collision)
michael@0 470 {
michael@0 471 Telemetry::AutoTimer<Telemetry::CACHE_DISK_SEARCH_2> timer;
michael@0 472 if (!Initialized()) return nullptr; // NS_ERROR_NOT_INITIALIZED
michael@0 473 if (mClearingDiskCache) return nullptr;
michael@0 474 nsDiskCacheRecord record;
michael@0 475 nsDiskCacheBinding * binding = nullptr;
michael@0 476 PLDHashNumber hashNumber = nsDiskCache::Hash(key->get());
michael@0 477
michael@0 478 *collision = false;
michael@0 479
michael@0 480 binding = mBindery.FindActiveBinding(hashNumber);
michael@0 481 if (binding && !binding->mCacheEntry->Key()->Equals(*key)) {
michael@0 482 *collision = true;
michael@0 483 return nullptr;
michael@0 484 } else if (binding && binding->mDeactivateEvent) {
michael@0 485 binding->mDeactivateEvent->CancelEvent();
michael@0 486 binding->mDeactivateEvent = nullptr;
michael@0 487 CACHE_LOG_DEBUG(("CACHE: reusing deactivated entry %p " \
michael@0 488 "req-key=%s entry-key=%s\n",
michael@0 489 binding->mCacheEntry, key, binding->mCacheEntry->Key()));
michael@0 490
michael@0 491 return binding->mCacheEntry; // just return this one, observing that
michael@0 492 // FindActiveBinding() does not return
michael@0 493 // bindings to doomed entries
michael@0 494 }
michael@0 495 binding = nullptr;
michael@0 496
michael@0 497 // lookup hash number in cache map
michael@0 498 nsresult rv = mCacheMap.FindRecord(hashNumber, &record);
michael@0 499 if (NS_FAILED(rv)) return nullptr; // XXX log error?
michael@0 500
michael@0 501 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
michael@0 502 if (!diskEntry) return nullptr;
michael@0 503
michael@0 504 // compare key to be sure
michael@0 505 if (!key->Equals(diskEntry->Key())) {
michael@0 506 *collision = true;
michael@0 507 return nullptr;
michael@0 508 }
michael@0 509
michael@0 510 nsCacheEntry * entry = diskEntry->CreateCacheEntry(this);
michael@0 511 if (entry) {
michael@0 512 binding = mBindery.CreateBinding(entry, &record);
michael@0 513 if (!binding) {
michael@0 514 delete entry;
michael@0 515 entry = nullptr;
michael@0 516 }
michael@0 517 }
michael@0 518
michael@0 519 if (!entry) {
michael@0 520 (void) mCacheMap.DeleteStorage(&record);
michael@0 521 (void) mCacheMap.DeleteRecord(&record);
michael@0 522 }
michael@0 523
michael@0 524 return entry;
michael@0 525 }
michael@0 526
michael@0 527
michael@0 528 /**
michael@0 529 * NOTE: called while holding the cache service lock
michael@0 530 */
michael@0 531 nsresult
michael@0 532 nsDiskCacheDevice::DeactivateEntry(nsCacheEntry * entry)
michael@0 533 {
michael@0 534 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
michael@0 535 if (!IsValidBinding(binding))
michael@0 536 return NS_ERROR_UNEXPECTED;
michael@0 537
michael@0 538 CACHE_LOG_DEBUG(("CACHE: disk DeactivateEntry [%p %x]\n",
michael@0 539 entry, binding->mRecord.HashNumber()));
michael@0 540
michael@0 541 nsDiskCacheDeviceDeactivateEntryEvent *event =
michael@0 542 new nsDiskCacheDeviceDeactivateEntryEvent(this, entry, binding);
michael@0 543
michael@0 544 // ensure we can cancel the event via the binding later if necessary
michael@0 545 binding->mDeactivateEvent = event;
michael@0 546
michael@0 547 DebugOnly<nsresult> rv = nsCacheService::DispatchToCacheIOThread(event);
michael@0 548 NS_ASSERTION(NS_SUCCEEDED(rv), "DeactivateEntry: Failed dispatching "
michael@0 549 "deactivation event");
michael@0 550 return NS_OK;
michael@0 551 }
michael@0 552
michael@0 553 /**
michael@0 554 * NOTE: called while holding the cache service lock
michael@0 555 */
michael@0 556 nsresult
michael@0 557 nsDiskCacheDevice::DeactivateEntry_Private(nsCacheEntry * entry,
michael@0 558 nsDiskCacheBinding * binding)
michael@0 559 {
michael@0 560 nsresult rv = NS_OK;
michael@0 561 if (entry->IsDoomed()) {
michael@0 562 // delete data, entry, record from disk for entry
michael@0 563 rv = mCacheMap.DeleteStorage(&binding->mRecord);
michael@0 564
michael@0 565 } else {
michael@0 566 // save stuff to disk for entry
michael@0 567 rv = mCacheMap.WriteDiskCacheEntry(binding);
michael@0 568 if (NS_FAILED(rv)) {
michael@0 569 // clean up as best we can
michael@0 570 (void) mCacheMap.DeleteStorage(&binding->mRecord);
michael@0 571 (void) mCacheMap.DeleteRecord(&binding->mRecord);
michael@0 572 binding->mDoomed = true; // record is no longer in cache map
michael@0 573 }
michael@0 574 }
michael@0 575
michael@0 576 mBindery.RemoveBinding(binding); // extract binding from collision detection stuff
michael@0 577 delete entry; // which will release binding
michael@0 578 return rv;
michael@0 579 }
michael@0 580
michael@0 581
michael@0 582 /**
michael@0 583 * BindEntry()
michael@0 584 * no hash number collision -> no problem
michael@0 585 * collision
michael@0 586 * record not active -> evict, no problem
michael@0 587 * record is active
michael@0 588 * record is already doomed -> record shouldn't have been in map, no problem
michael@0 589 * record is not doomed -> doom, and replace record in map
michael@0 590 *
michael@0 591 * walk matching hashnumber list to find lowest generation number
michael@0 592 * take generation number from other (data/meta) location,
michael@0 593 * or walk active list
michael@0 594 *
michael@0 595 * NOTE: called while holding the cache service lock
michael@0 596 */
michael@0 597 nsresult
michael@0 598 nsDiskCacheDevice::BindEntry(nsCacheEntry * entry)
michael@0 599 {
michael@0 600 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
michael@0 601 if (mClearingDiskCache) return NS_ERROR_NOT_AVAILABLE;
michael@0 602 nsresult rv = NS_OK;
michael@0 603 nsDiskCacheRecord record, oldRecord;
michael@0 604 nsDiskCacheBinding *binding;
michael@0 605 PLDHashNumber hashNumber = nsDiskCache::Hash(entry->Key()->get());
michael@0 606
michael@0 607 // Find out if there is already an active binding for this hash. If yes it
michael@0 608 // should have another key since BindEntry() shouldn't be called twice for
michael@0 609 // the same entry. Doom the old entry, the new one will get another
michael@0 610 // generation number so files won't collide.
michael@0 611 binding = mBindery.FindActiveBinding(hashNumber);
michael@0 612 if (binding) {
michael@0 613 NS_ASSERTION(!binding->mCacheEntry->Key()->Equals(*entry->Key()),
michael@0 614 "BindEntry called for already bound entry!");
michael@0 615 // If the entry is pending deactivation, cancel deactivation
michael@0 616 if (binding->mDeactivateEvent) {
michael@0 617 binding->mDeactivateEvent->CancelEvent();
michael@0 618 binding->mDeactivateEvent = nullptr;
michael@0 619 }
michael@0 620 nsCacheService::DoomEntry(binding->mCacheEntry);
michael@0 621 binding = nullptr;
michael@0 622 }
michael@0 623
michael@0 624 // Lookup hash number in cache map. There can be a colliding inactive entry.
michael@0 625 // See bug #321361 comment 21 for the scenario. If there is such entry,
michael@0 626 // delete it.
michael@0 627 rv = mCacheMap.FindRecord(hashNumber, &record);
michael@0 628 if (NS_SUCCEEDED(rv)) {
michael@0 629 nsDiskCacheEntry * diskEntry = mCacheMap.ReadDiskCacheEntry(&record);
michael@0 630 if (diskEntry) {
michael@0 631 // compare key to be sure
michael@0 632 if (!entry->Key()->Equals(diskEntry->Key())) {
michael@0 633 mCacheMap.DeleteStorage(&record);
michael@0 634 rv = mCacheMap.DeleteRecord(&record);
michael@0 635 if (NS_FAILED(rv)) return rv;
michael@0 636 }
michael@0 637 }
michael@0 638 record = nsDiskCacheRecord();
michael@0 639 }
michael@0 640
michael@0 641 // create a new record for this entry
michael@0 642 record.SetHashNumber(nsDiskCache::Hash(entry->Key()->get()));
michael@0 643 record.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
michael@0 644
michael@0 645 CACHE_LOG_DEBUG(("CACHE: disk BindEntry [%p %x]\n",
michael@0 646 entry, record.HashNumber()));
michael@0 647
michael@0 648 if (!entry->IsDoomed()) {
michael@0 649 // if entry isn't doomed, add it to the cache map
michael@0 650 rv = mCacheMap.AddRecord(&record, &oldRecord); // deletes old record, if any
michael@0 651 if (NS_FAILED(rv)) return rv;
michael@0 652
michael@0 653 uint32_t oldHashNumber = oldRecord.HashNumber();
michael@0 654 if (oldHashNumber) {
michael@0 655 // gotta evict this one first
michael@0 656 nsDiskCacheBinding * oldBinding = mBindery.FindActiveBinding(oldHashNumber);
michael@0 657 if (oldBinding) {
michael@0 658 // XXX if debug : compare keys for hashNumber collision
michael@0 659
michael@0 660 if (!oldBinding->mCacheEntry->IsDoomed()) {
michael@0 661 // If the old entry is pending deactivation, cancel deactivation
michael@0 662 if (oldBinding->mDeactivateEvent) {
michael@0 663 oldBinding->mDeactivateEvent->CancelEvent();
michael@0 664 oldBinding->mDeactivateEvent = nullptr;
michael@0 665 }
michael@0 666 // we've got a live one!
michael@0 667 nsCacheService::DoomEntry(oldBinding->mCacheEntry);
michael@0 668 // storage will be delete when oldBinding->mCacheEntry is Deactivated
michael@0 669 }
michael@0 670 } else {
michael@0 671 // delete storage
michael@0 672 // XXX if debug : compare keys for hashNumber collision
michael@0 673 rv = mCacheMap.DeleteStorage(&oldRecord);
michael@0 674 if (NS_FAILED(rv)) return rv; // XXX delete record we just added?
michael@0 675 }
michael@0 676 }
michael@0 677 }
michael@0 678
michael@0 679 // Make sure this entry has its associated nsDiskCacheBinding attached.
michael@0 680 binding = mBindery.CreateBinding(entry, &record);
michael@0 681 NS_ASSERTION(binding, "nsDiskCacheDevice::BindEntry");
michael@0 682 if (!binding) return NS_ERROR_OUT_OF_MEMORY;
michael@0 683 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad cache map record");
michael@0 684
michael@0 685 return NS_OK;
michael@0 686 }
michael@0 687
michael@0 688
michael@0 689 /**
michael@0 690 * NOTE: called while holding the cache service lock
michael@0 691 */
michael@0 692 void
michael@0 693 nsDiskCacheDevice::DoomEntry(nsCacheEntry * entry)
michael@0 694 {
michael@0 695 CACHE_LOG_DEBUG(("CACHE: disk DoomEntry [%p]\n", entry));
michael@0 696
michael@0 697 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
michael@0 698 NS_ASSERTION(binding, "DoomEntry: binding == nullptr");
michael@0 699 if (!binding)
michael@0 700 return;
michael@0 701
michael@0 702 if (!binding->mDoomed) {
michael@0 703 // so it can't be seen by FindEntry() ever again.
michael@0 704 #ifdef DEBUG
michael@0 705 nsresult rv =
michael@0 706 #endif
michael@0 707 mCacheMap.DeleteRecord(&binding->mRecord);
michael@0 708 NS_ASSERTION(NS_SUCCEEDED(rv),"DeleteRecord failed.");
michael@0 709 binding->mDoomed = true; // record in no longer in cache map
michael@0 710 }
michael@0 711 }
michael@0 712
michael@0 713
michael@0 714 /**
michael@0 715 * NOTE: called while holding the cache service lock
michael@0 716 */
michael@0 717 nsresult
michael@0 718 nsDiskCacheDevice::OpenInputStreamForEntry(nsCacheEntry * entry,
michael@0 719 nsCacheAccessMode mode,
michael@0 720 uint32_t offset,
michael@0 721 nsIInputStream ** result)
michael@0 722 {
michael@0 723 CACHE_LOG_DEBUG(("CACHE: disk OpenInputStreamForEntry [%p %x %u]\n",
michael@0 724 entry, mode, offset));
michael@0 725
michael@0 726 NS_ENSURE_ARG_POINTER(entry);
michael@0 727 NS_ENSURE_ARG_POINTER(result);
michael@0 728
michael@0 729 nsresult rv;
michael@0 730 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
michael@0 731 if (!IsValidBinding(binding))
michael@0 732 return NS_ERROR_UNEXPECTED;
michael@0 733
michael@0 734 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
michael@0 735
michael@0 736 rv = binding->EnsureStreamIO();
michael@0 737 if (NS_FAILED(rv)) return rv;
michael@0 738
michael@0 739 return binding->mStreamIO->GetInputStream(offset, result);
michael@0 740 }
michael@0 741
michael@0 742
michael@0 743 /**
michael@0 744 * NOTE: called while holding the cache service lock
michael@0 745 */
michael@0 746 nsresult
michael@0 747 nsDiskCacheDevice::OpenOutputStreamForEntry(nsCacheEntry * entry,
michael@0 748 nsCacheAccessMode mode,
michael@0 749 uint32_t offset,
michael@0 750 nsIOutputStream ** result)
michael@0 751 {
michael@0 752 CACHE_LOG_DEBUG(("CACHE: disk OpenOutputStreamForEntry [%p %x %u]\n",
michael@0 753 entry, mode, offset));
michael@0 754
michael@0 755 NS_ENSURE_ARG_POINTER(entry);
michael@0 756 NS_ENSURE_ARG_POINTER(result);
michael@0 757
michael@0 758 nsresult rv;
michael@0 759 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
michael@0 760 if (!IsValidBinding(binding))
michael@0 761 return NS_ERROR_UNEXPECTED;
michael@0 762
michael@0 763 NS_ASSERTION(binding->mCacheEntry == entry, "binding & entry don't point to each other");
michael@0 764
michael@0 765 rv = binding->EnsureStreamIO();
michael@0 766 if (NS_FAILED(rv)) return rv;
michael@0 767
michael@0 768 return binding->mStreamIO->GetOutputStream(offset, result);
michael@0 769 }
michael@0 770
michael@0 771
michael@0 772 /**
michael@0 773 * NOTE: called while holding the cache service lock
michael@0 774 */
michael@0 775 nsresult
michael@0 776 nsDiskCacheDevice::GetFileForEntry(nsCacheEntry * entry,
michael@0 777 nsIFile ** result)
michael@0 778 {
michael@0 779 NS_ENSURE_ARG_POINTER(result);
michael@0 780 *result = nullptr;
michael@0 781
michael@0 782 nsresult rv;
michael@0 783
michael@0 784 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
michael@0 785 if (!IsValidBinding(binding))
michael@0 786 return NS_ERROR_UNEXPECTED;
michael@0 787
michael@0 788 // check/set binding->mRecord for separate file, sync w/mCacheMap
michael@0 789 if (binding->mRecord.DataLocationInitialized()) {
michael@0 790 if (binding->mRecord.DataFile() != 0)
michael@0 791 return NS_ERROR_NOT_AVAILABLE; // data not stored as separate file
michael@0 792
michael@0 793 NS_ASSERTION(binding->mRecord.DataFileGeneration() == binding->mGeneration, "error generations out of sync");
michael@0 794 } else {
michael@0 795 binding->mRecord.SetDataFileGeneration(binding->mGeneration);
michael@0 796 binding->mRecord.SetDataFileSize(0); // 1k minimum
michael@0 797 if (!binding->mDoomed) {
michael@0 798 // record stored in cache map, so update it
michael@0 799 rv = mCacheMap.UpdateRecord(&binding->mRecord);
michael@0 800 if (NS_FAILED(rv)) return rv;
michael@0 801 }
michael@0 802 }
michael@0 803
michael@0 804 nsCOMPtr<nsIFile> file;
michael@0 805 rv = mCacheMap.GetFileForDiskCacheRecord(&binding->mRecord,
michael@0 806 nsDiskCache::kData,
michael@0 807 false,
michael@0 808 getter_AddRefs(file));
michael@0 809 if (NS_FAILED(rv)) return rv;
michael@0 810
michael@0 811 NS_IF_ADDREF(*result = file);
michael@0 812 return NS_OK;
michael@0 813 }
michael@0 814
michael@0 815
michael@0 816 /**
michael@0 817 * This routine will get called every time an open descriptor is written to.
michael@0 818 *
michael@0 819 * NOTE: called while holding the cache service lock
michael@0 820 */
michael@0 821 nsresult
michael@0 822 nsDiskCacheDevice::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
michael@0 823 {
michael@0 824 CACHE_LOG_DEBUG(("CACHE: disk OnDataSizeChange [%p %d]\n",
michael@0 825 entry, deltaSize));
michael@0 826
michael@0 827 // If passed a negative value, then there's nothing to do.
michael@0 828 if (deltaSize < 0)
michael@0 829 return NS_OK;
michael@0 830
michael@0 831 nsDiskCacheBinding * binding = GetCacheEntryBinding(entry);
michael@0 832 if (!IsValidBinding(binding))
michael@0 833 return NS_ERROR_UNEXPECTED;
michael@0 834
michael@0 835 NS_ASSERTION(binding->mRecord.ValidRecord(), "bad record");
michael@0 836
michael@0 837 uint32_t newSize = entry->DataSize() + deltaSize;
michael@0 838 uint32_t newSizeK = ((newSize + 0x3FF) >> 10);
michael@0 839
michael@0 840 // If the new size is larger than max. file size or larger than
michael@0 841 // 1/8 the cache capacity (which is in KiB's), doom the entry and abort.
michael@0 842 if (EntryIsTooBig(newSize)) {
michael@0 843 #ifdef DEBUG
michael@0 844 nsresult rv =
michael@0 845 #endif
michael@0 846 nsCacheService::DoomEntry(entry);
michael@0 847 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
michael@0 848 return NS_ERROR_ABORT;
michael@0 849 }
michael@0 850
michael@0 851 uint32_t sizeK = ((entry->DataSize() + 0x03FF) >> 10); // round up to next 1k
michael@0 852
michael@0 853 // In total count we ignore anything over kMaxDataSizeK (bug #651100), so
michael@0 854 // the target capacity should be calculated the same way.
michael@0 855 if (sizeK > kMaxDataSizeK) sizeK = kMaxDataSizeK;
michael@0 856 if (newSizeK > kMaxDataSizeK) newSizeK = kMaxDataSizeK;
michael@0 857
michael@0 858 // pre-evict entries to make space for new data
michael@0 859 uint32_t targetCapacity = mCacheCapacity > (newSizeK - sizeK)
michael@0 860 ? mCacheCapacity - (newSizeK - sizeK)
michael@0 861 : 0;
michael@0 862 EvictDiskCacheEntries(targetCapacity);
michael@0 863
michael@0 864 return NS_OK;
michael@0 865 }
michael@0 866
michael@0 867
michael@0 868 /******************************************************************************
michael@0 869 * EntryInfoVisitor
michael@0 870 *****************************************************************************/
michael@0 871 class EntryInfoVisitor : public nsDiskCacheRecordVisitor
michael@0 872 {
michael@0 873 public:
michael@0 874 EntryInfoVisitor(nsDiskCacheMap * cacheMap,
michael@0 875 nsICacheVisitor * visitor)
michael@0 876 : mCacheMap(cacheMap)
michael@0 877 , mVisitor(visitor)
michael@0 878 {}
michael@0 879
michael@0 880 virtual int32_t VisitRecord(nsDiskCacheRecord * mapRecord)
michael@0 881 {
michael@0 882 // XXX optimization: do we have this record in memory?
michael@0 883
michael@0 884 // read in the entry (metadata)
michael@0 885 nsDiskCacheEntry * diskEntry = mCacheMap->ReadDiskCacheEntry(mapRecord);
michael@0 886 if (!diskEntry) {
michael@0 887 return kVisitNextRecord;
michael@0 888 }
michael@0 889
michael@0 890 // create nsICacheEntryInfo
michael@0 891 nsDiskCacheEntryInfo * entryInfo = new nsDiskCacheEntryInfo(DISK_CACHE_DEVICE_ID, diskEntry);
michael@0 892 if (!entryInfo) {
michael@0 893 return kStopVisitingRecords;
michael@0 894 }
michael@0 895 nsCOMPtr<nsICacheEntryInfo> ref(entryInfo);
michael@0 896
michael@0 897 bool keepGoing;
michael@0 898 (void)mVisitor->VisitEntry(DISK_CACHE_DEVICE_ID, entryInfo, &keepGoing);
michael@0 899 return keepGoing ? kVisitNextRecord : kStopVisitingRecords;
michael@0 900 }
michael@0 901
michael@0 902 private:
michael@0 903 nsDiskCacheMap * mCacheMap;
michael@0 904 nsICacheVisitor * mVisitor;
michael@0 905 };
michael@0 906
michael@0 907
michael@0 908 nsresult
michael@0 909 nsDiskCacheDevice::Visit(nsICacheVisitor * visitor)
michael@0 910 {
michael@0 911 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
michael@0 912 nsDiskCacheDeviceInfo* deviceInfo = new nsDiskCacheDeviceInfo(this);
michael@0 913 nsCOMPtr<nsICacheDeviceInfo> ref(deviceInfo);
michael@0 914
michael@0 915 bool keepGoing;
michael@0 916 nsresult rv = visitor->VisitDevice(DISK_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
michael@0 917 if (NS_FAILED(rv)) return rv;
michael@0 918
michael@0 919 if (keepGoing) {
michael@0 920 EntryInfoVisitor infoVisitor(&mCacheMap, visitor);
michael@0 921 return mCacheMap.VisitRecords(&infoVisitor);
michael@0 922 }
michael@0 923
michael@0 924 return NS_OK;
michael@0 925 }
michael@0 926
michael@0 927 // Max allowed size for an entry is currently MIN(mMaxEntrySize, 1/8 CacheCapacity)
michael@0 928 bool
michael@0 929 nsDiskCacheDevice::EntryIsTooBig(int64_t entrySize)
michael@0 930 {
michael@0 931 if (mMaxEntrySize == -1) // no limit
michael@0 932 return entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
michael@0 933 else
michael@0 934 return entrySize > mMaxEntrySize ||
michael@0 935 entrySize > (static_cast<int64_t>(mCacheCapacity) * 1024 / 8);
michael@0 936 }
michael@0 937
michael@0 938 nsresult
michael@0 939 nsDiskCacheDevice::EvictEntries(const char * clientID)
michael@0 940 {
michael@0 941 CACHE_LOG_DEBUG(("CACHE: disk EvictEntries [%s]\n", clientID));
michael@0 942
michael@0 943 if (!Initialized()) return NS_ERROR_NOT_INITIALIZED;
michael@0 944 nsresult rv;
michael@0 945
michael@0 946 if (clientID == nullptr) {
michael@0 947 // we're clearing the entire disk cache
michael@0 948 rv = ClearDiskCache();
michael@0 949 if (rv != NS_ERROR_CACHE_IN_USE)
michael@0 950 return rv;
michael@0 951 }
michael@0 952
michael@0 953 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, 0, clientID);
michael@0 954 rv = mCacheMap.VisitRecords(&evictor);
michael@0 955
michael@0 956 if (clientID == nullptr) // we tried to clear the entire cache
michael@0 957 rv = mCacheMap.Trim(); // so trim cache block files (if possible)
michael@0 958 return rv;
michael@0 959 }
michael@0 960
michael@0 961
michael@0 962 /**
michael@0 963 * private methods
michael@0 964 */
michael@0 965
michael@0 966 nsresult
michael@0 967 nsDiskCacheDevice::OpenDiskCache()
michael@0 968 {
michael@0 969 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_OPEN> timer;
michael@0 970 // if we don't have a cache directory, create one and open it
michael@0 971 bool exists;
michael@0 972 nsresult rv = mCacheDirectory->Exists(&exists);
michael@0 973 if (NS_FAILED(rv))
michael@0 974 return rv;
michael@0 975
michael@0 976 if (exists) {
michael@0 977 // Try opening cache map file.
michael@0 978 nsDiskCache::CorruptCacheInfo corruptInfo;
michael@0 979 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, true);
michael@0 980
michael@0 981 if (NS_SUCCEEDED(rv)) {
michael@0 982 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
michael@0 983 corruptInfo);
michael@0 984 } else if (rv == NS_ERROR_ALREADY_INITIALIZED) {
michael@0 985 NS_WARNING("nsDiskCacheDevice::OpenDiskCache: already open!");
michael@0 986 } else {
michael@0 987 // Consider cache corrupt: delete it
michael@0 988 Telemetry::Accumulate(Telemetry::DISK_CACHE_CORRUPT_DETAILS,
michael@0 989 corruptInfo);
michael@0 990 // delay delete by 1 minute to avoid IO thrash at startup
michael@0 991 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true, 60000);
michael@0 992 if (NS_FAILED(rv))
michael@0 993 return rv;
michael@0 994 exists = false;
michael@0 995 }
michael@0 996 }
michael@0 997
michael@0 998 // if we don't have a cache directory, create one and open it
michael@0 999 if (!exists) {
michael@0 1000 nsCacheService::MarkStartingFresh();
michael@0 1001 rv = mCacheDirectory->Create(nsIFile::DIRECTORY_TYPE, 0777);
michael@0 1002 CACHE_LOG_PATH(PR_LOG_ALWAYS, "\ncreate cache directory: %s\n", mCacheDirectory);
michael@0 1003 CACHE_LOG_ALWAYS(("mCacheDirectory->Create() = %x\n", rv));
michael@0 1004 if (NS_FAILED(rv))
michael@0 1005 return rv;
michael@0 1006
michael@0 1007 // reopen the cache map
michael@0 1008 nsDiskCache::CorruptCacheInfo corruptInfo;
michael@0 1009 rv = mCacheMap.Open(mCacheDirectory, &corruptInfo, false);
michael@0 1010 if (NS_FAILED(rv))
michael@0 1011 return rv;
michael@0 1012 }
michael@0 1013
michael@0 1014 return NS_OK;
michael@0 1015 }
michael@0 1016
michael@0 1017
michael@0 1018 nsresult
michael@0 1019 nsDiskCacheDevice::ClearDiskCache()
michael@0 1020 {
michael@0 1021 if (mBindery.ActiveBindings())
michael@0 1022 return NS_ERROR_CACHE_IN_USE;
michael@0 1023
michael@0 1024 mClearingDiskCache = true;
michael@0 1025
michael@0 1026 nsresult rv = Shutdown_Private(false); // false: don't bother flushing
michael@0 1027 if (NS_FAILED(rv))
michael@0 1028 return rv;
michael@0 1029
michael@0 1030 mClearingDiskCache = false;
michael@0 1031
michael@0 1032 // If the disk cache directory is already gone, then it's not an error if
michael@0 1033 // we fail to delete it ;-)
michael@0 1034 rv = nsDeleteDir::DeleteDir(mCacheDirectory, true);
michael@0 1035 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
michael@0 1036 return rv;
michael@0 1037
michael@0 1038 return Init();
michael@0 1039 }
michael@0 1040
michael@0 1041
michael@0 1042 nsresult
michael@0 1043 nsDiskCacheDevice::EvictDiskCacheEntries(uint32_t targetCapacity)
michael@0 1044 {
michael@0 1045 CACHE_LOG_DEBUG(("CACHE: disk EvictDiskCacheEntries [%u]\n",
michael@0 1046 targetCapacity));
michael@0 1047
michael@0 1048 NS_ASSERTION(targetCapacity > 0, "oops");
michael@0 1049
michael@0 1050 if (mCacheMap.TotalSize() < targetCapacity)
michael@0 1051 return NS_OK;
michael@0 1052
michael@0 1053 // targetCapacity is in KiB's
michael@0 1054 nsDiskCacheEvictor evictor(&mCacheMap, &mBindery, targetCapacity, nullptr);
michael@0 1055 return mCacheMap.EvictRecords(&evictor);
michael@0 1056 }
michael@0 1057
michael@0 1058
michael@0 1059 /**
michael@0 1060 * methods for prefs
michael@0 1061 */
michael@0 1062
michael@0 1063 void
michael@0 1064 nsDiskCacheDevice::SetCacheParentDirectory(nsIFile * parentDir)
michael@0 1065 {
michael@0 1066 nsresult rv;
michael@0 1067 bool exists;
michael@0 1068
michael@0 1069 if (Initialized()) {
michael@0 1070 NS_ASSERTION(false, "Cannot switch cache directory when initialized");
michael@0 1071 return;
michael@0 1072 }
michael@0 1073
michael@0 1074 if (!parentDir) {
michael@0 1075 mCacheDirectory = nullptr;
michael@0 1076 return;
michael@0 1077 }
michael@0 1078
michael@0 1079 // ensure parent directory exists
michael@0 1080 rv = parentDir->Exists(&exists);
michael@0 1081 if (NS_SUCCEEDED(rv) && !exists)
michael@0 1082 rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
michael@0 1083 if (NS_FAILED(rv)) return;
michael@0 1084
michael@0 1085 // ensure cache directory exists
michael@0 1086 nsCOMPtr<nsIFile> directory;
michael@0 1087
michael@0 1088 rv = parentDir->Clone(getter_AddRefs(directory));
michael@0 1089 if (NS_FAILED(rv)) return;
michael@0 1090 rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
michael@0 1091 if (NS_FAILED(rv)) return;
michael@0 1092
michael@0 1093 mCacheDirectory = do_QueryInterface(directory);
michael@0 1094 }
michael@0 1095
michael@0 1096
michael@0 1097 void
michael@0 1098 nsDiskCacheDevice::getCacheDirectory(nsIFile ** result)
michael@0 1099 {
michael@0 1100 *result = mCacheDirectory;
michael@0 1101 NS_IF_ADDREF(*result);
michael@0 1102 }
michael@0 1103
michael@0 1104
michael@0 1105 /**
michael@0 1106 * NOTE: called while holding the cache service lock
michael@0 1107 */
michael@0 1108 void
michael@0 1109 nsDiskCacheDevice::SetCapacity(uint32_t capacity)
michael@0 1110 {
michael@0 1111 // Units are KiB's
michael@0 1112 mCacheCapacity = capacity;
michael@0 1113 if (Initialized()) {
michael@0 1114 if (NS_IsMainThread()) {
michael@0 1115 // Do not evict entries on the main thread
michael@0 1116 nsCacheService::DispatchToCacheIOThread(
michael@0 1117 new nsEvictDiskCacheEntriesEvent(this));
michael@0 1118 } else {
michael@0 1119 // start evicting entries if the new size is smaller!
michael@0 1120 EvictDiskCacheEntries(mCacheCapacity);
michael@0 1121 }
michael@0 1122 }
michael@0 1123 // Let cache map know of the new capacity
michael@0 1124 mCacheMap.NotifyCapacityChange(capacity);
michael@0 1125 }
michael@0 1126
michael@0 1127
michael@0 1128 uint32_t nsDiskCacheDevice::getCacheCapacity()
michael@0 1129 {
michael@0 1130 return mCacheCapacity;
michael@0 1131 }
michael@0 1132
michael@0 1133
michael@0 1134 uint32_t nsDiskCacheDevice::getCacheSize()
michael@0 1135 {
michael@0 1136 return mCacheMap.TotalSize();
michael@0 1137 }
michael@0 1138
michael@0 1139
michael@0 1140 uint32_t nsDiskCacheDevice::getEntryCount()
michael@0 1141 {
michael@0 1142 return mCacheMap.EntryCount();
michael@0 1143 }
michael@0 1144
michael@0 1145 void
michael@0 1146 nsDiskCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
michael@0 1147 {
michael@0 1148 // Internal units are bytes. Changing this only takes effect *after* the
michael@0 1149 // change and has no consequences for existing cache-entries
michael@0 1150 if (maxSizeInKilobytes >= 0)
michael@0 1151 mMaxEntrySize = maxSizeInKilobytes * 1024;
michael@0 1152 else
michael@0 1153 mMaxEntrySize = -1;
michael@0 1154 }
michael@0 1155
michael@0 1156 size_t
michael@0 1157 nsDiskCacheDevice::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
michael@0 1158 {
michael@0 1159 size_t usage = aMallocSizeOf(this);
michael@0 1160
michael@0 1161 usage += mCacheMap.SizeOfExcludingThis(aMallocSizeOf);
michael@0 1162 usage += mBindery.SizeOfExcludingThis(aMallocSizeOf);
michael@0 1163
michael@0 1164 return usage;
michael@0 1165 }
michael@0 1166

mercurial