Thu, 15 Jan 2015 15:55:04 +0100
Back out 97036ab72558 which inappropriately compared turds to third parties.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* vim: set ts=8 sts=4 et sw=4 tw=80: */ |
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 "nsCache.h" |
michael@0 | 8 | #include "nsMemoryCacheDevice.h" |
michael@0 | 9 | #include "nsCacheService.h" |
michael@0 | 10 | #include "nsICacheService.h" |
michael@0 | 11 | #include "nsICacheVisitor.h" |
michael@0 | 12 | #include "nsIStorageStream.h" |
michael@0 | 13 | #include "nsCRT.h" |
michael@0 | 14 | #include "nsReadableUtils.h" |
michael@0 | 15 | #include "mozilla/MathAlgorithms.h" |
michael@0 | 16 | #include "mozilla/Telemetry.h" |
michael@0 | 17 | #include <algorithm> |
michael@0 | 18 | |
michael@0 | 19 | // The memory cache implements the "LRU-SP" caching algorithm |
michael@0 | 20 | // described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement |
michael@0 | 21 | // Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi. |
michael@0 | 22 | |
michael@0 | 23 | // We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit)) |
michael@0 | 24 | // The queues hold exponentially increasing ranges of floor(log2((size/nref))) |
michael@0 | 25 | // values for entries. |
michael@0 | 26 | // Entries larger than 2^(kQueueCount-1) go in the last queue. |
michael@0 | 27 | // Entries with no expiration go in the first queue. |
michael@0 | 28 | |
michael@0 | 29 | const char *gMemoryDeviceID = "memory"; |
michael@0 | 30 | |
michael@0 | 31 | |
michael@0 | 32 | nsMemoryCacheDevice::nsMemoryCacheDevice() |
michael@0 | 33 | : mInitialized(false), |
michael@0 | 34 | mHardLimit(4 * 1024 * 1024), // default, if no pref |
michael@0 | 35 | mSoftLimit((mHardLimit * 9) / 10), // default, if no pref |
michael@0 | 36 | mTotalSize(0), |
michael@0 | 37 | mInactiveSize(0), |
michael@0 | 38 | mEntryCount(0), |
michael@0 | 39 | mMaxEntryCount(0), |
michael@0 | 40 | mMaxEntrySize(-1) // -1 means "no limit" |
michael@0 | 41 | { |
michael@0 | 42 | for (int i=0; i<kQueueCount; ++i) |
michael@0 | 43 | PR_INIT_CLIST(&mEvictionList[i]); |
michael@0 | 44 | } |
michael@0 | 45 | |
michael@0 | 46 | |
michael@0 | 47 | nsMemoryCacheDevice::~nsMemoryCacheDevice() |
michael@0 | 48 | { |
michael@0 | 49 | Shutdown(); |
michael@0 | 50 | } |
michael@0 | 51 | |
michael@0 | 52 | |
michael@0 | 53 | nsresult |
michael@0 | 54 | nsMemoryCacheDevice::Init() |
michael@0 | 55 | { |
michael@0 | 56 | if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED; |
michael@0 | 57 | |
michael@0 | 58 | nsresult rv = mMemCacheEntries.Init(); |
michael@0 | 59 | mInitialized = NS_SUCCEEDED(rv); |
michael@0 | 60 | return rv; |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | |
michael@0 | 64 | nsresult |
michael@0 | 65 | nsMemoryCacheDevice::Shutdown() |
michael@0 | 66 | { |
michael@0 | 67 | NS_ASSERTION(mInitialized, "### attempting shutdown while not initialized"); |
michael@0 | 68 | NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); |
michael@0 | 69 | |
michael@0 | 70 | mMemCacheEntries.Shutdown(); |
michael@0 | 71 | |
michael@0 | 72 | // evict all entries |
michael@0 | 73 | nsCacheEntry * entry, * next; |
michael@0 | 74 | |
michael@0 | 75 | for (int i = kQueueCount - 1; i >= 0; --i) { |
michael@0 | 76 | entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]); |
michael@0 | 77 | while (entry != &mEvictionList[i]) { |
michael@0 | 78 | NS_ASSERTION(!entry->IsInUse(), "### shutting down with active entries"); |
michael@0 | 79 | next = (nsCacheEntry *)PR_NEXT_LINK(entry); |
michael@0 | 80 | PR_REMOVE_AND_INIT_LINK(entry); |
michael@0 | 81 | |
michael@0 | 82 | // update statistics |
michael@0 | 83 | int32_t memoryRecovered = (int32_t)entry->DataSize(); |
michael@0 | 84 | mTotalSize -= memoryRecovered; |
michael@0 | 85 | mInactiveSize -= memoryRecovered; |
michael@0 | 86 | --mEntryCount; |
michael@0 | 87 | |
michael@0 | 88 | delete entry; |
michael@0 | 89 | entry = next; |
michael@0 | 90 | } |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | /* |
michael@0 | 94 | * we're not factoring in changes to meta data yet... |
michael@0 | 95 | * NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?"); |
michael@0 | 96 | */ |
michael@0 | 97 | NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?"); |
michael@0 | 98 | NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?"); |
michael@0 | 99 | |
michael@0 | 100 | mInitialized = false; |
michael@0 | 101 | |
michael@0 | 102 | return NS_OK; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | |
michael@0 | 106 | const char * |
michael@0 | 107 | nsMemoryCacheDevice::GetDeviceID() |
michael@0 | 108 | { |
michael@0 | 109 | return gMemoryDeviceID; |
michael@0 | 110 | } |
michael@0 | 111 | |
michael@0 | 112 | |
michael@0 | 113 | nsCacheEntry * |
michael@0 | 114 | nsMemoryCacheDevice::FindEntry(nsCString * key, bool *collision) |
michael@0 | 115 | { |
michael@0 | 116 | mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_MEMORY_SEARCH_2> timer; |
michael@0 | 117 | nsCacheEntry * entry = mMemCacheEntries.GetEntry(key); |
michael@0 | 118 | if (!entry) return nullptr; |
michael@0 | 119 | |
michael@0 | 120 | // move entry to the tail of an eviction list |
michael@0 | 121 | PR_REMOVE_AND_INIT_LINK(entry); |
michael@0 | 122 | PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]); |
michael@0 | 123 | |
michael@0 | 124 | mInactiveSize -= entry->DataSize(); |
michael@0 | 125 | |
michael@0 | 126 | return entry; |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | |
michael@0 | 130 | nsresult |
michael@0 | 131 | nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry) |
michael@0 | 132 | { |
michael@0 | 133 | CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n", |
michael@0 | 134 | entry)); |
michael@0 | 135 | if (entry->IsDoomed()) { |
michael@0 | 136 | #ifdef DEBUG |
michael@0 | 137 | // XXX verify we've removed it from mMemCacheEntries & eviction list |
michael@0 | 138 | #endif |
michael@0 | 139 | delete entry; |
michael@0 | 140 | CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry)); |
michael@0 | 141 | return NS_OK; |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | #ifdef DEBUG |
michael@0 | 145 | nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key()); |
michael@0 | 146 | NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!"); |
michael@0 | 147 | NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry"); |
michael@0 | 148 | if (ourEntry != entry) |
michael@0 | 149 | return NS_ERROR_INVALID_POINTER; |
michael@0 | 150 | #endif |
michael@0 | 151 | |
michael@0 | 152 | mInactiveSize += entry->DataSize(); |
michael@0 | 153 | EvictEntriesIfNecessary(); |
michael@0 | 154 | |
michael@0 | 155 | return NS_OK; |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | |
michael@0 | 159 | nsresult |
michael@0 | 160 | nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry) |
michael@0 | 161 | { |
michael@0 | 162 | if (!entry->IsDoomed()) { |
michael@0 | 163 | NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!"); |
michael@0 | 164 | |
michael@0 | 165 | // append entry to the eviction list |
michael@0 | 166 | PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]); |
michael@0 | 167 | |
michael@0 | 168 | // add entry to hashtable of mem cache entries |
michael@0 | 169 | nsresult rv = mMemCacheEntries.AddEntry(entry); |
michael@0 | 170 | if (NS_FAILED(rv)) { |
michael@0 | 171 | PR_REMOVE_AND_INIT_LINK(entry); |
michael@0 | 172 | return rv; |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | // add size of entry to memory totals |
michael@0 | 176 | ++mEntryCount; |
michael@0 | 177 | if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount; |
michael@0 | 178 | |
michael@0 | 179 | mTotalSize += entry->DataSize(); |
michael@0 | 180 | EvictEntriesIfNecessary(); |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | return NS_OK; |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | |
michael@0 | 187 | void |
michael@0 | 188 | nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry) |
michael@0 | 189 | { |
michael@0 | 190 | #ifdef DEBUG |
michael@0 | 191 | // debug code to verify we have entry |
michael@0 | 192 | nsCacheEntry * hashEntry = mMemCacheEntries.GetEntry(entry->Key()); |
michael@0 | 193 | if (!hashEntry) NS_WARNING("no entry for key"); |
michael@0 | 194 | else if (entry != hashEntry) NS_WARNING("entry != hashEntry"); |
michael@0 | 195 | #endif |
michael@0 | 196 | CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry)); |
michael@0 | 197 | EvictEntry(entry, DO_NOT_DELETE_ENTRY); |
michael@0 | 198 | } |
michael@0 | 199 | |
michael@0 | 200 | |
michael@0 | 201 | nsresult |
michael@0 | 202 | nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry * entry, |
michael@0 | 203 | nsCacheAccessMode mode, |
michael@0 | 204 | uint32_t offset, |
michael@0 | 205 | nsIInputStream ** result) |
michael@0 | 206 | { |
michael@0 | 207 | NS_ENSURE_ARG_POINTER(entry); |
michael@0 | 208 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 209 | |
michael@0 | 210 | nsCOMPtr<nsIStorageStream> storage; |
michael@0 | 211 | nsresult rv; |
michael@0 | 212 | |
michael@0 | 213 | nsISupports *data = entry->Data(); |
michael@0 | 214 | if (data) { |
michael@0 | 215 | storage = do_QueryInterface(data, &rv); |
michael@0 | 216 | if (NS_FAILED(rv)) |
michael@0 | 217 | return rv; |
michael@0 | 218 | } |
michael@0 | 219 | else { |
michael@0 | 220 | rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage)); |
michael@0 | 221 | if (NS_FAILED(rv)) |
michael@0 | 222 | return rv; |
michael@0 | 223 | entry->SetData(storage); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | return storage->NewInputStream(offset, result); |
michael@0 | 227 | } |
michael@0 | 228 | |
michael@0 | 229 | |
michael@0 | 230 | nsresult |
michael@0 | 231 | nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry * entry, |
michael@0 | 232 | nsCacheAccessMode mode, |
michael@0 | 233 | uint32_t offset, |
michael@0 | 234 | nsIOutputStream ** result) |
michael@0 | 235 | { |
michael@0 | 236 | NS_ENSURE_ARG_POINTER(entry); |
michael@0 | 237 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 238 | |
michael@0 | 239 | nsCOMPtr<nsIStorageStream> storage; |
michael@0 | 240 | nsresult rv; |
michael@0 | 241 | |
michael@0 | 242 | nsISupports *data = entry->Data(); |
michael@0 | 243 | if (data) { |
michael@0 | 244 | storage = do_QueryInterface(data, &rv); |
michael@0 | 245 | if (NS_FAILED(rv)) |
michael@0 | 246 | return rv; |
michael@0 | 247 | } |
michael@0 | 248 | else { |
michael@0 | 249 | rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage)); |
michael@0 | 250 | if (NS_FAILED(rv)) |
michael@0 | 251 | return rv; |
michael@0 | 252 | entry->SetData(storage); |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | return storage->GetOutputStream(offset, result); |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | |
michael@0 | 259 | nsresult |
michael@0 | 260 | nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry, |
michael@0 | 261 | nsIFile ** result ) |
michael@0 | 262 | { |
michael@0 | 263 | return NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | bool |
michael@0 | 267 | nsMemoryCacheDevice::EntryIsTooBig(int64_t entrySize) |
michael@0 | 268 | { |
michael@0 | 269 | CACHE_LOG_DEBUG(("nsMemoryCacheDevice::EntryIsTooBig " |
michael@0 | 270 | "[size=%d max=%d soft=%d]\n", |
michael@0 | 271 | entrySize, mMaxEntrySize, mSoftLimit)); |
michael@0 | 272 | if (mMaxEntrySize == -1) |
michael@0 | 273 | return entrySize > mSoftLimit; |
michael@0 | 274 | else |
michael@0 | 275 | return (entrySize > mSoftLimit || entrySize > mMaxEntrySize); |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | size_t |
michael@0 | 279 | nsMemoryCacheDevice::TotalSize() |
michael@0 | 280 | { |
michael@0 | 281 | return mTotalSize; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | nsresult |
michael@0 | 285 | nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize) |
michael@0 | 286 | { |
michael@0 | 287 | if (entry->IsStreamData()) { |
michael@0 | 288 | // we have the right to refuse or pre-evict |
michael@0 | 289 | uint32_t newSize = entry->DataSize() + deltaSize; |
michael@0 | 290 | if (EntryIsTooBig(newSize)) { |
michael@0 | 291 | #ifdef DEBUG |
michael@0 | 292 | nsresult rv = |
michael@0 | 293 | #endif |
michael@0 | 294 | nsCacheService::DoomEntry(entry); |
michael@0 | 295 | NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed."); |
michael@0 | 296 | return NS_ERROR_ABORT; |
michael@0 | 297 | } |
michael@0 | 298 | } |
michael@0 | 299 | |
michael@0 | 300 | // adjust our totals |
michael@0 | 301 | mTotalSize += deltaSize; |
michael@0 | 302 | |
michael@0 | 303 | if (!entry->IsDoomed()) { |
michael@0 | 304 | // move entry to the tail of the appropriate eviction list |
michael@0 | 305 | PR_REMOVE_AND_INIT_LINK(entry); |
michael@0 | 306 | PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]); |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | EvictEntriesIfNecessary(); |
michael@0 | 310 | return NS_OK; |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | |
michael@0 | 314 | void |
michael@0 | 315 | nsMemoryCacheDevice::AdjustMemoryLimits(int32_t softLimit, int32_t hardLimit) |
michael@0 | 316 | { |
michael@0 | 317 | mSoftLimit = softLimit; |
michael@0 | 318 | mHardLimit = hardLimit; |
michael@0 | 319 | |
michael@0 | 320 | // First, evict entries that won't fit into the new cache size. |
michael@0 | 321 | EvictEntriesIfNecessary(); |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | |
michael@0 | 325 | void |
michael@0 | 326 | nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry, bool deleteEntry) |
michael@0 | 327 | { |
michael@0 | 328 | CACHE_LOG_DEBUG(("Evicting entry 0x%p from memory cache, deleting: %d\n", |
michael@0 | 329 | entry, deleteEntry)); |
michael@0 | 330 | // remove entry from our hashtable |
michael@0 | 331 | mMemCacheEntries.RemoveEntry(entry); |
michael@0 | 332 | |
michael@0 | 333 | // remove entry from the eviction list |
michael@0 | 334 | PR_REMOVE_AND_INIT_LINK(entry); |
michael@0 | 335 | |
michael@0 | 336 | // update statistics |
michael@0 | 337 | int32_t memoryRecovered = (int32_t)entry->DataSize(); |
michael@0 | 338 | mTotalSize -= memoryRecovered; |
michael@0 | 339 | if (!entry->IsDoomed()) |
michael@0 | 340 | mInactiveSize -= memoryRecovered; |
michael@0 | 341 | --mEntryCount; |
michael@0 | 342 | |
michael@0 | 343 | if (deleteEntry) delete entry; |
michael@0 | 344 | } |
michael@0 | 345 | |
michael@0 | 346 | |
michael@0 | 347 | void |
michael@0 | 348 | nsMemoryCacheDevice::EvictEntriesIfNecessary(void) |
michael@0 | 349 | { |
michael@0 | 350 | nsCacheEntry * entry; |
michael@0 | 351 | nsCacheEntry * maxEntry; |
michael@0 | 352 | CACHE_LOG_DEBUG(("EvictEntriesIfNecessary. mTotalSize: %d, mHardLimit: %d," |
michael@0 | 353 | "mInactiveSize: %d, mSoftLimit: %d\n", |
michael@0 | 354 | mTotalSize, mHardLimit, mInactiveSize, mSoftLimit)); |
michael@0 | 355 | |
michael@0 | 356 | if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit)) |
michael@0 | 357 | return; |
michael@0 | 358 | |
michael@0 | 359 | uint32_t now = SecondsFromPRTime(PR_Now()); |
michael@0 | 360 | uint64_t entryCost = 0; |
michael@0 | 361 | uint64_t maxCost = 0; |
michael@0 | 362 | do { |
michael@0 | 363 | // LRU-SP eviction selection: Check the head of each segment (each |
michael@0 | 364 | // eviction list, kept in LRU order) and select the maximal-cost |
michael@0 | 365 | // entry for eviction. Cost is time-since-accessed * size / nref. |
michael@0 | 366 | maxEntry = 0; |
michael@0 | 367 | for (int i = kQueueCount - 1; i >= 0; --i) { |
michael@0 | 368 | entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]); |
michael@0 | 369 | |
michael@0 | 370 | // If the head of a list is in use, check the next available entry |
michael@0 | 371 | while ((entry != &mEvictionList[i]) && |
michael@0 | 372 | (entry->IsInUse())) { |
michael@0 | 373 | entry = (nsCacheEntry *)PR_NEXT_LINK(entry); |
michael@0 | 374 | } |
michael@0 | 375 | |
michael@0 | 376 | if (entry != &mEvictionList[i]) { |
michael@0 | 377 | entryCost = (uint64_t) |
michael@0 | 378 | (now - entry->LastFetched()) * entry->DataSize() / |
michael@0 | 379 | std::max(1, entry->FetchCount()); |
michael@0 | 380 | if (!maxEntry || (entryCost > maxCost)) { |
michael@0 | 381 | maxEntry = entry; |
michael@0 | 382 | maxCost = entryCost; |
michael@0 | 383 | } |
michael@0 | 384 | } |
michael@0 | 385 | } |
michael@0 | 386 | if (maxEntry) { |
michael@0 | 387 | EvictEntry(maxEntry, DELETE_ENTRY); |
michael@0 | 388 | } else { |
michael@0 | 389 | break; |
michael@0 | 390 | } |
michael@0 | 391 | } |
michael@0 | 392 | while ((mTotalSize >= mHardLimit) || (mInactiveSize >= mSoftLimit)); |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | |
michael@0 | 396 | int |
michael@0 | 397 | nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, int32_t deltaSize) |
michael@0 | 398 | { |
michael@0 | 399 | // favor items which never expire by putting them in the lowest-index queue |
michael@0 | 400 | if (entry->ExpirationTime() == nsICache::NO_EXPIRATION_TIME) |
michael@0 | 401 | return 0; |
michael@0 | 402 | |
michael@0 | 403 | // compute which eviction queue this entry should go into, |
michael@0 | 404 | // based on floor(log2(size/nref)) |
michael@0 | 405 | int32_t size = deltaSize + (int32_t)entry->DataSize(); |
michael@0 | 406 | int32_t fetchCount = std::max(1, entry->FetchCount()); |
michael@0 | 407 | |
michael@0 | 408 | return std::min((int)mozilla::FloorLog2(size / fetchCount), kQueueCount - 1); |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | |
michael@0 | 412 | nsresult |
michael@0 | 413 | nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor) |
michael@0 | 414 | { |
michael@0 | 415 | nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this); |
michael@0 | 416 | nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo); |
michael@0 | 417 | if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 418 | |
michael@0 | 419 | bool keepGoing; |
michael@0 | 420 | nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing); |
michael@0 | 421 | if (NS_FAILED(rv)) return rv; |
michael@0 | 422 | |
michael@0 | 423 | if (!keepGoing) |
michael@0 | 424 | return NS_OK; |
michael@0 | 425 | |
michael@0 | 426 | nsCacheEntry * entry; |
michael@0 | 427 | nsCOMPtr<nsICacheEntryInfo> entryRef; |
michael@0 | 428 | |
michael@0 | 429 | for (int i = kQueueCount - 1; i >= 0; --i) { |
michael@0 | 430 | entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]); |
michael@0 | 431 | while (entry != &mEvictionList[i]) { |
michael@0 | 432 | nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry); |
michael@0 | 433 | if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 434 | entryRef = entryInfo; |
michael@0 | 435 | |
michael@0 | 436 | rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing); |
michael@0 | 437 | entryInfo->DetachEntry(); |
michael@0 | 438 | if (NS_FAILED(rv)) return rv; |
michael@0 | 439 | if (!keepGoing) break; |
michael@0 | 440 | |
michael@0 | 441 | entry = (nsCacheEntry *)PR_NEXT_LINK(entry); |
michael@0 | 442 | } |
michael@0 | 443 | } |
michael@0 | 444 | return NS_OK; |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | |
michael@0 | 448 | static bool |
michael@0 | 449 | IsEntryPrivate(nsCacheEntry* entry, void* args) |
michael@0 | 450 | { |
michael@0 | 451 | return entry->IsPrivate(); |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | struct ClientIDArgs { |
michael@0 | 455 | const char* clientID; |
michael@0 | 456 | uint32_t prefixLength; |
michael@0 | 457 | }; |
michael@0 | 458 | |
michael@0 | 459 | static bool |
michael@0 | 460 | EntryMatchesClientID(nsCacheEntry* entry, void* args) |
michael@0 | 461 | { |
michael@0 | 462 | const char * clientID = static_cast<ClientIDArgs*>(args)->clientID; |
michael@0 | 463 | uint32_t prefixLength = static_cast<ClientIDArgs*>(args)->prefixLength; |
michael@0 | 464 | const char * key = entry->Key()->get(); |
michael@0 | 465 | return !clientID || nsCRT::strncmp(clientID, key, prefixLength) == 0; |
michael@0 | 466 | } |
michael@0 | 467 | |
michael@0 | 468 | nsresult |
michael@0 | 469 | nsMemoryCacheDevice::DoEvictEntries(bool (*matchFn)(nsCacheEntry* entry, void* args), void* args) |
michael@0 | 470 | { |
michael@0 | 471 | nsCacheEntry * entry; |
michael@0 | 472 | |
michael@0 | 473 | for (int i = kQueueCount - 1; i >= 0; --i) { |
michael@0 | 474 | PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]); |
michael@0 | 475 | while (elem != &mEvictionList[i]) { |
michael@0 | 476 | entry = (nsCacheEntry *)elem; |
michael@0 | 477 | elem = PR_NEXT_LINK(elem); |
michael@0 | 478 | |
michael@0 | 479 | if (!matchFn(entry, args)) |
michael@0 | 480 | continue; |
michael@0 | 481 | |
michael@0 | 482 | if (entry->IsInUse()) { |
michael@0 | 483 | nsresult rv = nsCacheService::DoomEntry(entry); |
michael@0 | 484 | if (NS_FAILED(rv)) { |
michael@0 | 485 | CACHE_LOG_WARNING(("memCache->DoEvictEntries() aborted: rv =%x", rv)); |
michael@0 | 486 | return rv; |
michael@0 | 487 | } |
michael@0 | 488 | } else { |
michael@0 | 489 | EvictEntry(entry, DELETE_ENTRY); |
michael@0 | 490 | } |
michael@0 | 491 | } |
michael@0 | 492 | } |
michael@0 | 493 | |
michael@0 | 494 | return NS_OK; |
michael@0 | 495 | } |
michael@0 | 496 | |
michael@0 | 497 | nsresult |
michael@0 | 498 | nsMemoryCacheDevice::EvictEntries(const char * clientID) |
michael@0 | 499 | { |
michael@0 | 500 | ClientIDArgs args = {clientID, clientID ? uint32_t(strlen(clientID)) : 0}; |
michael@0 | 501 | return DoEvictEntries(&EntryMatchesClientID, &args); |
michael@0 | 502 | } |
michael@0 | 503 | |
michael@0 | 504 | nsresult |
michael@0 | 505 | nsMemoryCacheDevice::EvictPrivateEntries() |
michael@0 | 506 | { |
michael@0 | 507 | return DoEvictEntries(&IsEntryPrivate, nullptr); |
michael@0 | 508 | } |
michael@0 | 509 | |
michael@0 | 510 | |
michael@0 | 511 | // WARNING: SetCapacity can get called before Init() |
michael@0 | 512 | void |
michael@0 | 513 | nsMemoryCacheDevice::SetCapacity(int32_t capacity) |
michael@0 | 514 | { |
michael@0 | 515 | int32_t hardLimit = capacity * 1024; // convert k into bytes |
michael@0 | 516 | int32_t softLimit = (hardLimit * 9) / 10; |
michael@0 | 517 | AdjustMemoryLimits(softLimit, hardLimit); |
michael@0 | 518 | } |
michael@0 | 519 | |
michael@0 | 520 | void |
michael@0 | 521 | nsMemoryCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes) |
michael@0 | 522 | { |
michael@0 | 523 | // Internal unit is bytes. Changing this only takes effect *after* the |
michael@0 | 524 | // change and has no consequences for existing cache-entries |
michael@0 | 525 | if (maxSizeInKilobytes >= 0) |
michael@0 | 526 | mMaxEntrySize = maxSizeInKilobytes * 1024; |
michael@0 | 527 | else |
michael@0 | 528 | mMaxEntrySize = -1; |
michael@0 | 529 | } |
michael@0 | 530 | |
michael@0 | 531 | #ifdef DEBUG |
michael@0 | 532 | static PLDHashOperator |
michael@0 | 533 | CountEntry(PLDHashTable * table, PLDHashEntryHdr * hdr, uint32_t number, void * arg) |
michael@0 | 534 | { |
michael@0 | 535 | int32_t *entryCount = (int32_t *)arg; |
michael@0 | 536 | ++(*entryCount); |
michael@0 | 537 | return PL_DHASH_NEXT; |
michael@0 | 538 | } |
michael@0 | 539 | |
michael@0 | 540 | void |
michael@0 | 541 | nsMemoryCacheDevice::CheckEntryCount() |
michael@0 | 542 | { |
michael@0 | 543 | if (!mInitialized) return; |
michael@0 | 544 | |
michael@0 | 545 | int32_t evictionListCount = 0; |
michael@0 | 546 | for (int i=0; i<kQueueCount; ++i) { |
michael@0 | 547 | PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]); |
michael@0 | 548 | while (elem != &mEvictionList[i]) { |
michael@0 | 549 | elem = PR_NEXT_LINK(elem); |
michael@0 | 550 | ++evictionListCount; |
michael@0 | 551 | } |
michael@0 | 552 | } |
michael@0 | 553 | NS_ASSERTION(mEntryCount == evictionListCount, "### mem cache badness"); |
michael@0 | 554 | |
michael@0 | 555 | int32_t entryCount = 0; |
michael@0 | 556 | mMemCacheEntries.VisitEntries(CountEntry, &entryCount); |
michael@0 | 557 | NS_ASSERTION(mEntryCount == entryCount, "### mem cache badness"); |
michael@0 | 558 | } |
michael@0 | 559 | #endif |
michael@0 | 560 | |
michael@0 | 561 | /****************************************************************************** |
michael@0 | 562 | * nsMemoryCacheDeviceInfo - for implementing about:cache |
michael@0 | 563 | *****************************************************************************/ |
michael@0 | 564 | |
michael@0 | 565 | |
michael@0 | 566 | NS_IMPL_ISUPPORTS(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo) |
michael@0 | 567 | |
michael@0 | 568 | |
michael@0 | 569 | NS_IMETHODIMP |
michael@0 | 570 | nsMemoryCacheDeviceInfo::GetDescription(char ** result) |
michael@0 | 571 | { |
michael@0 | 572 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 573 | *result = NS_strdup("Memory cache device"); |
michael@0 | 574 | if (!*result) return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 575 | return NS_OK; |
michael@0 | 576 | } |
michael@0 | 577 | |
michael@0 | 578 | |
michael@0 | 579 | NS_IMETHODIMP |
michael@0 | 580 | nsMemoryCacheDeviceInfo::GetUsageReport(char ** result) |
michael@0 | 581 | { |
michael@0 | 582 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 583 | nsCString buffer; |
michael@0 | 584 | |
michael@0 | 585 | buffer.AssignLiteral(" <tr>\n" |
michael@0 | 586 | " <th>Inactive storage:</th>\n" |
michael@0 | 587 | " <td>"); |
michael@0 | 588 | buffer.AppendInt(mDevice->mInactiveSize / 1024); |
michael@0 | 589 | buffer.AppendLiteral(" KiB</td>\n" |
michael@0 | 590 | " </tr>\n"); |
michael@0 | 591 | |
michael@0 | 592 | *result = ToNewCString(buffer); |
michael@0 | 593 | if (!*result) return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 594 | return NS_OK; |
michael@0 | 595 | } |
michael@0 | 596 | |
michael@0 | 597 | |
michael@0 | 598 | NS_IMETHODIMP |
michael@0 | 599 | nsMemoryCacheDeviceInfo::GetEntryCount(uint32_t * result) |
michael@0 | 600 | { |
michael@0 | 601 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 602 | // XXX compare calculated count vs. mEntryCount |
michael@0 | 603 | *result = (uint32_t)mDevice->mEntryCount; |
michael@0 | 604 | return NS_OK; |
michael@0 | 605 | } |
michael@0 | 606 | |
michael@0 | 607 | |
michael@0 | 608 | NS_IMETHODIMP |
michael@0 | 609 | nsMemoryCacheDeviceInfo::GetTotalSize(uint32_t * result) |
michael@0 | 610 | { |
michael@0 | 611 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 612 | *result = (uint32_t)mDevice->mTotalSize; |
michael@0 | 613 | return NS_OK; |
michael@0 | 614 | } |
michael@0 | 615 | |
michael@0 | 616 | |
michael@0 | 617 | NS_IMETHODIMP |
michael@0 | 618 | nsMemoryCacheDeviceInfo::GetMaximumSize(uint32_t * result) |
michael@0 | 619 | { |
michael@0 | 620 | NS_ENSURE_ARG_POINTER(result); |
michael@0 | 621 | *result = (uint32_t)mDevice->mHardLimit; |
michael@0 | 622 | return NS_OK; |
michael@0 | 623 | } |