Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache / nsMemoryCacheDevice.cpp
blob8e839ff5045f01d4b94235e8f4a60f84e0b7d669
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=8 sts=4 et sw=4 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsCache.h"
8 #include "nsMemoryCacheDevice.h"
9 #include "nsCacheService.h"
10 #include "nsICacheService.h"
11 #include "nsICacheVisitor.h"
12 #include "nsIStorageStream.h"
13 #include "nsCRT.h"
14 #include "nsReadableUtils.h"
15 #include "mozilla/MathAlgorithms.h"
16 #include "mozilla/Telemetry.h"
17 #include <algorithm>
19 // The memory cache implements the "LRU-SP" caching algorithm
20 // described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement
21 // Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi.
23 // We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit))
24 // The queues hold exponentially increasing ranges of floor(log2((size/nref)))
25 // values for entries.
26 // Entries larger than 2^(kQueueCount-1) go in the last queue.
27 // Entries with no expiration go in the first queue.
29 const char *gMemoryDeviceID = "memory";
32 nsMemoryCacheDevice::nsMemoryCacheDevice()
33 : mInitialized(false),
34 mHardLimit(4 * 1024 * 1024), // default, if no pref
35 mSoftLimit((mHardLimit * 9) / 10), // default, if no pref
36 mTotalSize(0),
37 mInactiveSize(0),
38 mEntryCount(0),
39 mMaxEntryCount(0),
40 mMaxEntrySize(-1) // -1 means "no limit"
42 for (int i=0; i<kQueueCount; ++i)
43 PR_INIT_CLIST(&mEvictionList[i]);
47 nsMemoryCacheDevice::~nsMemoryCacheDevice()
49 Shutdown();
53 nsresult
54 nsMemoryCacheDevice::Init()
56 if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
58 nsresult rv = mMemCacheEntries.Init();
59 mInitialized = NS_SUCCEEDED(rv);
60 return rv;
64 nsresult
65 nsMemoryCacheDevice::Shutdown()
67 NS_ASSERTION(mInitialized, "### attempting shutdown while not initialized");
68 NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
70 mMemCacheEntries.Shutdown();
72 // evict all entries
73 nsCacheEntry * entry, * next;
75 for (int i = kQueueCount - 1; i >= 0; --i) {
76 entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
77 while (entry != &mEvictionList[i]) {
78 NS_ASSERTION(!entry->IsInUse(), "### shutting down with active entries");
79 next = (nsCacheEntry *)PR_NEXT_LINK(entry);
80 PR_REMOVE_AND_INIT_LINK(entry);
82 // update statistics
83 int32_t memoryRecovered = (int32_t)entry->DataSize();
84 mTotalSize -= memoryRecovered;
85 mInactiveSize -= memoryRecovered;
86 --mEntryCount;
88 delete entry;
89 entry = next;
94 * we're not factoring in changes to meta data yet...
95 * NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?");
97 NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?");
98 NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?");
100 mInitialized = false;
102 return NS_OK;
106 const char *
107 nsMemoryCacheDevice::GetDeviceID()
109 return gMemoryDeviceID;
113 nsCacheEntry *
114 nsMemoryCacheDevice::FindEntry(nsCString * key, bool *collision)
116 mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_MEMORY_SEARCH_2> timer;
117 nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
118 if (!entry) return nullptr;
120 // move entry to the tail of an eviction list
121 PR_REMOVE_AND_INIT_LINK(entry);
122 PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
124 mInactiveSize -= entry->DataSize();
126 return entry;
130 nsresult
131 nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry)
133 CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n",
134 entry));
135 if (entry->IsDoomed()) {
136 #ifdef DEBUG
137 // XXX verify we've removed it from mMemCacheEntries & eviction list
138 #endif
139 delete entry;
140 CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry));
141 return NS_OK;
144 #ifdef DEBUG
145 nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key());
146 NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!");
147 NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry");
148 if (ourEntry != entry)
149 return NS_ERROR_INVALID_POINTER;
150 #endif
152 mInactiveSize += entry->DataSize();
153 EvictEntriesIfNecessary();
155 return NS_OK;
159 nsresult
160 nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry)
162 if (!entry->IsDoomed()) {
163 NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!");
165 // append entry to the eviction list
166 PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
168 // add entry to hashtable of mem cache entries
169 nsresult rv = mMemCacheEntries.AddEntry(entry);
170 if (NS_FAILED(rv)) {
171 PR_REMOVE_AND_INIT_LINK(entry);
172 return rv;
175 // add size of entry to memory totals
176 ++mEntryCount;
177 if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount;
179 mTotalSize += entry->DataSize();
180 EvictEntriesIfNecessary();
183 return NS_OK;
187 void
188 nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry)
190 #ifdef DEBUG
191 // debug code to verify we have entry
192 nsCacheEntry * hashEntry = mMemCacheEntries.GetEntry(entry->Key());
193 if (!hashEntry) NS_WARNING("no entry for key");
194 else if (entry != hashEntry) NS_WARNING("entry != hashEntry");
195 #endif
196 CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry));
197 EvictEntry(entry, DO_NOT_DELETE_ENTRY);
201 nsresult
202 nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry * entry,
203 nsCacheAccessMode mode,
204 uint32_t offset,
205 nsIInputStream ** result)
207 NS_ENSURE_ARG_POINTER(entry);
208 NS_ENSURE_ARG_POINTER(result);
210 nsCOMPtr<nsIStorageStream> storage;
211 nsresult rv;
213 nsISupports *data = entry->Data();
214 if (data) {
215 storage = do_QueryInterface(data, &rv);
216 if (NS_FAILED(rv))
217 return rv;
219 else {
220 rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
221 if (NS_FAILED(rv))
222 return rv;
223 entry->SetData(storage);
226 return storage->NewInputStream(offset, result);
230 nsresult
231 nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry * entry,
232 nsCacheAccessMode mode,
233 uint32_t offset,
234 nsIOutputStream ** result)
236 NS_ENSURE_ARG_POINTER(entry);
237 NS_ENSURE_ARG_POINTER(result);
239 nsCOMPtr<nsIStorageStream> storage;
240 nsresult rv;
242 nsISupports *data = entry->Data();
243 if (data) {
244 storage = do_QueryInterface(data, &rv);
245 if (NS_FAILED(rv))
246 return rv;
248 else {
249 rv = NS_NewStorageStream(4096, uint32_t(-1), getter_AddRefs(storage));
250 if (NS_FAILED(rv))
251 return rv;
252 entry->SetData(storage);
255 return storage->GetOutputStream(offset, result);
259 nsresult
260 nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry,
261 nsIFile ** result )
263 return NS_ERROR_NOT_IMPLEMENTED;
266 bool
267 nsMemoryCacheDevice::EntryIsTooBig(int64_t entrySize)
269 CACHE_LOG_DEBUG(("nsMemoryCacheDevice::EntryIsTooBig "
270 "[size=%d max=%d soft=%d]\n",
271 entrySize, mMaxEntrySize, mSoftLimit));
272 if (mMaxEntrySize == -1)
273 return entrySize > mSoftLimit;
274 else
275 return (entrySize > mSoftLimit || entrySize > mMaxEntrySize);
278 size_t
279 nsMemoryCacheDevice::TotalSize()
281 return mTotalSize;
284 nsresult
285 nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, int32_t deltaSize)
287 if (entry->IsStreamData()) {
288 // we have the right to refuse or pre-evict
289 uint32_t newSize = entry->DataSize() + deltaSize;
290 if (EntryIsTooBig(newSize)) {
291 #ifdef DEBUG
292 nsresult rv =
293 #endif
294 nsCacheService::DoomEntry(entry);
295 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
296 return NS_ERROR_ABORT;
300 // adjust our totals
301 mTotalSize += deltaSize;
303 if (!entry->IsDoomed()) {
304 // move entry to the tail of the appropriate eviction list
305 PR_REMOVE_AND_INIT_LINK(entry);
306 PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]);
309 EvictEntriesIfNecessary();
310 return NS_OK;
314 void
315 nsMemoryCacheDevice::AdjustMemoryLimits(int32_t softLimit, int32_t hardLimit)
317 mSoftLimit = softLimit;
318 mHardLimit = hardLimit;
320 // First, evict entries that won't fit into the new cache size.
321 EvictEntriesIfNecessary();
325 void
326 nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry, bool deleteEntry)
328 CACHE_LOG_DEBUG(("Evicting entry 0x%p from memory cache, deleting: %d\n",
329 entry, deleteEntry));
330 // remove entry from our hashtable
331 mMemCacheEntries.RemoveEntry(entry);
333 // remove entry from the eviction list
334 PR_REMOVE_AND_INIT_LINK(entry);
336 // update statistics
337 int32_t memoryRecovered = (int32_t)entry->DataSize();
338 mTotalSize -= memoryRecovered;
339 if (!entry->IsDoomed())
340 mInactiveSize -= memoryRecovered;
341 --mEntryCount;
343 if (deleteEntry) delete entry;
347 void
348 nsMemoryCacheDevice::EvictEntriesIfNecessary(void)
350 nsCacheEntry * entry;
351 nsCacheEntry * maxEntry;
352 CACHE_LOG_DEBUG(("EvictEntriesIfNecessary. mTotalSize: %d, mHardLimit: %d,"
353 "mInactiveSize: %d, mSoftLimit: %d\n",
354 mTotalSize, mHardLimit, mInactiveSize, mSoftLimit));
356 if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
357 return;
359 uint32_t now = SecondsFromPRTime(PR_Now());
360 uint64_t entryCost = 0;
361 uint64_t maxCost = 0;
362 do {
363 // LRU-SP eviction selection: Check the head of each segment (each
364 // eviction list, kept in LRU order) and select the maximal-cost
365 // entry for eviction. Cost is time-since-accessed * size / nref.
366 maxEntry = 0;
367 for (int i = kQueueCount - 1; i >= 0; --i) {
368 entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
370 // If the head of a list is in use, check the next available entry
371 while ((entry != &mEvictionList[i]) &&
372 (entry->IsInUse())) {
373 entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
376 if (entry != &mEvictionList[i]) {
377 entryCost = (uint64_t)
378 (now - entry->LastFetched()) * entry->DataSize() /
379 std::max(1, entry->FetchCount());
380 if (!maxEntry || (entryCost > maxCost)) {
381 maxEntry = entry;
382 maxCost = entryCost;
386 if (maxEntry) {
387 EvictEntry(maxEntry, DELETE_ENTRY);
388 } else {
389 break;
392 while ((mTotalSize >= mHardLimit) || (mInactiveSize >= mSoftLimit));
397 nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, int32_t deltaSize)
399 // favor items which never expire by putting them in the lowest-index queue
400 if (entry->ExpirationTime() == nsICache::NO_EXPIRATION_TIME)
401 return 0;
403 // compute which eviction queue this entry should go into,
404 // based on floor(log2(size/nref))
405 int32_t size = deltaSize + (int32_t)entry->DataSize();
406 int32_t fetchCount = std::max(1, entry->FetchCount());
408 return std::min((int)mozilla::FloorLog2(size / fetchCount), kQueueCount - 1);
412 nsresult
413 nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor)
415 nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this);
416 nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo);
417 if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY;
419 bool keepGoing;
420 nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing);
421 if (NS_FAILED(rv)) return rv;
423 if (!keepGoing)
424 return NS_OK;
426 nsCacheEntry * entry;
427 nsCOMPtr<nsICacheEntryInfo> entryRef;
429 for (int i = kQueueCount - 1; i >= 0; --i) {
430 entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
431 while (entry != &mEvictionList[i]) {
432 nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry);
433 if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY;
434 entryRef = entryInfo;
436 rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing);
437 entryInfo->DetachEntry();
438 if (NS_FAILED(rv)) return rv;
439 if (!keepGoing) break;
441 entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
444 return NS_OK;
448 static bool
449 IsEntryPrivate(nsCacheEntry* entry, void* args)
451 return entry->IsPrivate();
454 struct ClientIDArgs {
455 const char* clientID;
456 uint32_t prefixLength;
459 static bool
460 EntryMatchesClientID(nsCacheEntry* entry, void* args)
462 const char * clientID = static_cast<ClientIDArgs*>(args)->clientID;
463 uint32_t prefixLength = static_cast<ClientIDArgs*>(args)->prefixLength;
464 const char * key = entry->Key()->get();
465 return !clientID || nsCRT::strncmp(clientID, key, prefixLength) == 0;
468 nsresult
469 nsMemoryCacheDevice::DoEvictEntries(bool (*matchFn)(nsCacheEntry* entry, void* args), void* args)
471 nsCacheEntry * entry;
473 for (int i = kQueueCount - 1; i >= 0; --i) {
474 PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
475 while (elem != &mEvictionList[i]) {
476 entry = (nsCacheEntry *)elem;
477 elem = PR_NEXT_LINK(elem);
479 if (!matchFn(entry, args))
480 continue;
482 if (entry->IsInUse()) {
483 nsresult rv = nsCacheService::DoomEntry(entry);
484 if (NS_FAILED(rv)) {
485 CACHE_LOG_WARNING(("memCache->DoEvictEntries() aborted: rv =%x", rv));
486 return rv;
488 } else {
489 EvictEntry(entry, DELETE_ENTRY);
494 return NS_OK;
497 nsresult
498 nsMemoryCacheDevice::EvictEntries(const char * clientID)
500 ClientIDArgs args = {clientID, clientID ? uint32_t(strlen(clientID)) : 0};
501 return DoEvictEntries(&EntryMatchesClientID, &args);
504 nsresult
505 nsMemoryCacheDevice::EvictPrivateEntries()
507 return DoEvictEntries(&IsEntryPrivate, nullptr);
511 // WARNING: SetCapacity can get called before Init()
512 void
513 nsMemoryCacheDevice::SetCapacity(int32_t capacity)
515 int32_t hardLimit = capacity * 1024; // convert k into bytes
516 int32_t softLimit = (hardLimit * 9) / 10;
517 AdjustMemoryLimits(softLimit, hardLimit);
520 void
521 nsMemoryCacheDevice::SetMaxEntrySize(int32_t maxSizeInKilobytes)
523 // Internal unit is bytes. Changing this only takes effect *after* the
524 // change and has no consequences for existing cache-entries
525 if (maxSizeInKilobytes >= 0)
526 mMaxEntrySize = maxSizeInKilobytes * 1024;
527 else
528 mMaxEntrySize = -1;
531 #ifdef DEBUG
532 static PLDHashOperator
533 CountEntry(PLDHashTable * table, PLDHashEntryHdr * hdr, uint32_t number, void * arg)
535 int32_t *entryCount = (int32_t *)arg;
536 ++(*entryCount);
537 return PL_DHASH_NEXT;
540 void
541 nsMemoryCacheDevice::CheckEntryCount()
543 if (!mInitialized) return;
545 int32_t evictionListCount = 0;
546 for (int i=0; i<kQueueCount; ++i) {
547 PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
548 while (elem != &mEvictionList[i]) {
549 elem = PR_NEXT_LINK(elem);
550 ++evictionListCount;
553 NS_ASSERTION(mEntryCount == evictionListCount, "### mem cache badness");
555 int32_t entryCount = 0;
556 mMemCacheEntries.VisitEntries(CountEntry, &entryCount);
557 NS_ASSERTION(mEntryCount == entryCount, "### mem cache badness");
559 #endif
561 /******************************************************************************
562 * nsMemoryCacheDeviceInfo - for implementing about:cache
563 *****************************************************************************/
566 NS_IMPL_ISUPPORTS(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo)
569 NS_IMETHODIMP
570 nsMemoryCacheDeviceInfo::GetDescription(char ** result)
572 NS_ENSURE_ARG_POINTER(result);
573 *result = NS_strdup("Memory cache device");
574 if (!*result) return NS_ERROR_OUT_OF_MEMORY;
575 return NS_OK;
579 NS_IMETHODIMP
580 nsMemoryCacheDeviceInfo::GetUsageReport(char ** result)
582 NS_ENSURE_ARG_POINTER(result);
583 nsCString buffer;
585 buffer.AssignLiteral(" <tr>\n"
586 " <th>Inactive storage:</th>\n"
587 " <td>");
588 buffer.AppendInt(mDevice->mInactiveSize / 1024);
589 buffer.AppendLiteral(" KiB</td>\n"
590 " </tr>\n");
592 *result = ToNewCString(buffer);
593 if (!*result) return NS_ERROR_OUT_OF_MEMORY;
594 return NS_OK;
598 NS_IMETHODIMP
599 nsMemoryCacheDeviceInfo::GetEntryCount(uint32_t * result)
601 NS_ENSURE_ARG_POINTER(result);
602 // XXX compare calculated count vs. mEntryCount
603 *result = (uint32_t)mDevice->mEntryCount;
604 return NS_OK;
608 NS_IMETHODIMP
609 nsMemoryCacheDeviceInfo::GetTotalSize(uint32_t * result)
611 NS_ENSURE_ARG_POINTER(result);
612 *result = (uint32_t)mDevice->mTotalSize;
613 return NS_OK;
617 NS_IMETHODIMP
618 nsMemoryCacheDeviceInfo::GetMaximumSize(uint32_t * result)
620 NS_ENSURE_ARG_POINTER(result);
621 *result = (uint32_t)mDevice->mHardLimit;
622 return NS_OK;