Follow-on fix for bug 457825. Use sheet principal for agent and user sheets. r=dbaron...
[wine-gecko.git] / netwerk / cache / src / nsMemoryCacheDevice.cpp
blob13b7b50275afda22cb1b28e5432c7da45cc4a97a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is nsMemoryCacheDevice.cpp, released
17 * February 22, 2001.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2001
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
25 * Gordon Sheridan, <gordon@netscape.com>
26 * Patrick C. Beard <beard@netscape.com>
27 * Brian Ryner <bryner@brianryner.com>
29 * Alternatively, the contents of this file may be used under the terms of
30 * either the GNU General Public License Version 2 or later (the "GPL"), or
31 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
43 #include "nsMemoryCacheDevice.h"
44 #include "nsCacheService.h"
45 #include "nsICacheService.h"
46 #include "nsIStorageStream.h"
47 #include "nsICacheVisitor.h"
48 #include "nsCRT.h"
49 #include "nsCache.h"
50 #include "nsReadableUtils.h"
52 // The memory cache implements a variation of the "LRU-SP" caching algorithm
53 // described in "LRU-SP: A Size-Adjusted and Popularity-Aware LRU Replacement
54 // Algorithm for Web Caching" by Kai Cheng and Yahiko Kambayashi.
56 // We keep kQueueCount LRU queues, which should be about ceil(log2(mHardLimit))
57 // The queues hold exponentially increasing ranges of floor(log2((size/nref)))
58 // values for entries.
59 // Entries larger than 2^(kQueueCount-1) go in the last queue.
60 // Entries with no expiration go in the first queue.
62 const char *gMemoryDeviceID = "memory";
64 nsMemoryCacheDevice::nsMemoryCacheDevice()
65 : mInitialized(PR_FALSE),
66 mEvictionThreshold(PR_INT32_MAX),
67 mHardLimit(4 * 1024 * 1024), // default, if no pref
68 mSoftLimit((mHardLimit * 9) / 10), // default, if no pref
69 mTotalSize(0),
70 mInactiveSize(0),
71 mEntryCount(0),
72 mMaxEntryCount(0)
74 for (int i=0; i<kQueueCount; ++i)
75 PR_INIT_CLIST(&mEvictionList[i]);
79 nsMemoryCacheDevice::~nsMemoryCacheDevice()
81 Shutdown();
85 nsresult
86 nsMemoryCacheDevice::Init()
88 if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
90 nsresult rv = mMemCacheEntries.Init();
91 mInitialized = NS_SUCCEEDED(rv);
92 return rv;
96 nsresult
97 nsMemoryCacheDevice::Shutdown()
99 NS_ASSERTION(mInitialized, "### attempting shutdown while not initialized");
100 NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
102 mMemCacheEntries.Shutdown();
104 // evict all entries
105 nsCacheEntry * entry, * next;
107 for (int i = kQueueCount - 1; i >= 0; --i) {
108 entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
109 while (entry != &mEvictionList[i]) {
110 NS_ASSERTION(entry->IsInUse() == PR_FALSE, "### shutting down with active entries");
111 next = (nsCacheEntry *)PR_NEXT_LINK(entry);
112 PR_REMOVE_AND_INIT_LINK(entry);
114 // update statistics
115 PRInt32 memoryRecovered = (PRInt32)entry->Size();
116 mTotalSize -= memoryRecovered;
117 mInactiveSize -= memoryRecovered;
118 --mEntryCount;
120 delete entry;
121 entry = next;
126 * we're not factoring in changes to meta data yet...
127 * NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?");
129 NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?");
130 NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?");
132 mInitialized = PR_FALSE;
134 return NS_OK;
138 const char *
139 nsMemoryCacheDevice::GetDeviceID()
141 return gMemoryDeviceID;
145 nsCacheEntry *
146 nsMemoryCacheDevice::FindEntry(nsCString * key, PRBool *collision)
148 nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
149 if (!entry) return nsnull;
151 // move entry to the tail of an eviction list
152 PR_REMOVE_AND_INIT_LINK(entry);
153 PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
155 mInactiveSize -= entry->Size();
157 return entry;
161 nsresult
162 nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry)
164 CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n",
165 entry));
166 if (entry->IsDoomed()) {
167 #ifdef DEBUG
168 // XXX verify we've removed it from mMemCacheEntries & eviction list
169 #endif
170 delete entry;
171 CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry));
172 return NS_OK;
175 #ifdef DEBUG
176 nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key());
177 NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!");
178 NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry");
179 if (ourEntry != entry)
180 return NS_ERROR_INVALID_POINTER;
181 #endif
183 mInactiveSize += entry->Size();
184 EvictEntriesIfNecessary();
186 return NS_OK;
190 nsresult
191 nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry)
193 if (!entry->IsDoomed()) {
194 NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!");
196 // append entry to the eviction list
197 PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
199 // add entry to hashtable of mem cache entries
200 nsresult rv = mMemCacheEntries.AddEntry(entry);
201 if (NS_FAILED(rv)) {
202 PR_REMOVE_AND_INIT_LINK(entry);
203 return rv;
207 // add size of entry to memory totals
208 ++mEntryCount;
209 if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount;
211 mTotalSize += entry->Size();
212 EvictEntriesIfNecessary();
214 return NS_OK;
218 void
219 nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry)
221 #ifdef DEBUG
222 // debug code to verify we have entry
223 nsCacheEntry * hashEntry = mMemCacheEntries.GetEntry(entry->Key());
224 if (!hashEntry) NS_WARNING("no entry for key");
225 else if (entry != hashEntry) NS_WARNING("entry != hashEntry");
226 #endif
227 CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry));
228 EvictEntry(entry, DO_NOT_DELETE_ENTRY);
232 nsresult
233 nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry * entry,
234 nsCacheAccessMode mode,
235 PRUint32 offset,
236 nsIInputStream ** result)
238 NS_ENSURE_ARG_POINTER(entry);
239 NS_ENSURE_ARG_POINTER(result);
241 nsCOMPtr<nsIStorageStream> storage;
242 nsresult rv;
244 nsISupports *data = entry->Data();
245 if (data) {
246 storage = do_QueryInterface(data, &rv);
247 if (NS_FAILED(rv))
248 return rv;
250 else {
251 rv = NS_NewStorageStream(4096, PRUint32(-1), getter_AddRefs(storage));
252 if (NS_FAILED(rv))
253 return rv;
254 entry->SetData(storage);
257 return storage->NewInputStream(offset, result);
261 nsresult
262 nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry * entry,
263 nsCacheAccessMode mode,
264 PRUint32 offset,
265 nsIOutputStream ** result)
267 NS_ENSURE_ARG_POINTER(entry);
268 NS_ENSURE_ARG_POINTER(result);
270 nsCOMPtr<nsIStorageStream> storage;
271 nsresult rv;
273 nsISupports *data = entry->Data();
274 if (data) {
275 storage = do_QueryInterface(data, &rv);
276 if (NS_FAILED(rv))
277 return rv;
279 else {
280 rv = NS_NewStorageStream(4096, PRUint32(-1), getter_AddRefs(storage));
281 if (NS_FAILED(rv))
282 return rv;
283 entry->SetData(storage);
286 return storage->GetOutputStream(offset, result);
290 nsresult
291 nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry,
292 nsIFile ** result )
294 return NS_ERROR_NOT_IMPLEMENTED;
298 nsresult
299 nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize)
301 if (entry->IsStreamData()) {
302 // we have the right to refuse or pre-evict
303 PRUint32 newSize = entry->DataSize() + deltaSize;
304 if ((PRInt32) newSize > mSoftLimit) {
305 #ifdef DEBUG
306 nsresult rv =
307 #endif
308 nsCacheService::DoomEntry(entry);
309 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
310 return NS_ERROR_ABORT;
314 // adjust our totals
315 mTotalSize += deltaSize;
317 if (!entry->IsDoomed()) {
318 // move entry to the tail of the appropriate eviction list
319 PR_REMOVE_AND_INIT_LINK(entry);
320 PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]);
323 EvictEntriesIfNecessary();
324 return NS_OK;
328 void
329 nsMemoryCacheDevice::AdjustMemoryLimits(PRInt32 softLimit, PRInt32 hardLimit)
331 mSoftLimit = softLimit;
332 mHardLimit = hardLimit;
334 // First, evict entries that won't fit into the new cache size.
335 EvictEntriesIfNecessary();
339 void
340 nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry, PRBool deleteEntry)
342 CACHE_LOG_DEBUG(("Evicting entry 0x%p from memory cache, deleting: %d\n",
343 entry, deleteEntry));
344 // remove entry from our hashtable
345 mMemCacheEntries.RemoveEntry(entry);
347 // remove entry from the eviction list
348 PR_REMOVE_AND_INIT_LINK(entry);
350 // update statistics
351 PRInt32 memoryRecovered = (PRInt32)entry->Size();
352 mTotalSize -= memoryRecovered;
353 if (!entry->IsDoomed())
354 mInactiveSize -= memoryRecovered;
355 --mEntryCount;
357 if (deleteEntry) delete entry;
361 void
362 nsMemoryCacheDevice::EvictEntriesIfNecessary(void)
364 nsCacheEntry * entry, * next;
366 CACHE_LOG_DEBUG(("EvictEntriesIfNecessary. mTotalSize: %d, mHardLimit: %d,"
367 "mInactiveSize: %d, mSoftLimit: %d\n",
368 mTotalSize, mHardLimit, mInactiveSize, mSoftLimit));
370 if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
371 return;
373 for (int i = kQueueCount - 1; i >= 0; --i) {
374 entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
375 while (entry != &mEvictionList[i]) {
376 if (entry->IsInUse()) {
377 entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
378 continue;
381 next = (nsCacheEntry *)PR_NEXT_LINK(entry);
382 EvictEntry(entry, DELETE_ENTRY);
383 entry = next;
385 if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
386 return;
393 nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, PRInt32 deltaSize)
395 // favor items which never expire by putting them in the lowest-index queue
396 if (entry->ExpirationTime() == NO_EXPIRATION_TIME)
397 return 0;
399 // compute which eviction queue this entry should go into,
400 // based on floor(log2(size/nref))
401 PRInt32 size = deltaSize + (PRInt32)entry->Size();
402 PRInt32 fetchCount = PR_MAX(1, entry->FetchCount());
404 return PR_MIN(PR_FloorLog2(size / fetchCount), kQueueCount - 1);
408 nsresult
409 nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor)
411 nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this);
412 nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo);
413 if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY;
415 PRBool keepGoing;
416 nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing);
417 if (NS_FAILED(rv)) return rv;
419 if (!keepGoing)
420 return NS_OK;
422 nsCacheEntry * entry;
423 nsCOMPtr<nsICacheEntryInfo> entryRef;
425 for (int i = kQueueCount - 1; i >= 0; --i) {
426 entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
427 while (entry != &mEvictionList[i]) {
428 nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry);
429 if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY;
430 entryRef = entryInfo;
432 rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing);
433 entryInfo->DetachEntry();
434 if (NS_FAILED(rv)) return rv;
435 if (!keepGoing) break;
437 entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
440 return NS_OK;
444 nsresult
445 nsMemoryCacheDevice::EvictEntries(const char * clientID)
447 nsCacheEntry * entry;
448 PRUint32 prefixLength = (clientID ? strlen(clientID) : 0);
450 for (int i = kQueueCount - 1; i >= 0; --i) {
451 PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
452 while (elem != &mEvictionList[i]) {
453 entry = (nsCacheEntry *)elem;
454 elem = PR_NEXT_LINK(elem);
456 const char * key = entry->Key()->get();
457 if (clientID && nsCRT::strncmp(clientID, key, prefixLength) != 0)
458 continue;
460 if (entry->IsInUse()) {
461 nsresult rv = nsCacheService::DoomEntry(entry);
462 if (NS_FAILED(rv)) {
463 CACHE_LOG_WARNING(("memCache->EvictEntries() aborted: rv =%x", rv));
464 return rv;
466 } else {
467 EvictEntry(entry, DELETE_ENTRY);
472 return NS_OK;
476 // WARNING: SetCapacity can get called before Init()
477 void
478 nsMemoryCacheDevice::SetCapacity(PRInt32 capacity)
480 PRInt32 hardLimit = capacity * 1024; // convert k into bytes
481 PRInt32 softLimit = (hardLimit * 9) / 10;
482 AdjustMemoryLimits(softLimit, hardLimit);
486 #ifdef DEBUG
487 static PLDHashOperator
488 CountEntry(PLDHashTable * table, PLDHashEntryHdr * hdr, PRUint32 number, void * arg)
490 PRInt32 *entryCount = (PRInt32 *)arg;
491 ++(*entryCount);
492 return PL_DHASH_NEXT;
495 void
496 nsMemoryCacheDevice::CheckEntryCount()
498 if (!mInitialized) return;
500 PRInt32 evictionListCount = 0;
501 for (int i=0; i<kQueueCount; ++i) {
502 PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
503 while (elem != &mEvictionList[i]) {
504 elem = PR_NEXT_LINK(elem);
505 ++evictionListCount;
508 NS_ASSERTION(mEntryCount == evictionListCount, "### mem cache badness");
510 PRInt32 entryCount = 0;
511 mMemCacheEntries.VisitEntries(CountEntry, &entryCount);
512 NS_ASSERTION(mEntryCount == entryCount, "### mem cache badness");
514 #endif
516 /******************************************************************************
517 * nsMemoryCacheDeviceInfo - for implementing about:cache
518 *****************************************************************************/
521 NS_IMPL_ISUPPORTS1(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo)
524 NS_IMETHODIMP
525 nsMemoryCacheDeviceInfo::GetDescription(char ** result)
527 NS_ENSURE_ARG_POINTER(result);
528 *result = NS_strdup("Memory cache device");
529 if (!*result) return NS_ERROR_OUT_OF_MEMORY;
530 return NS_OK;
534 NS_IMETHODIMP
535 nsMemoryCacheDeviceInfo::GetUsageReport(char ** result)
537 NS_ENSURE_ARG_POINTER(result);
538 nsCString buffer;
540 buffer.AssignLiteral("\n<tr>\n<td><b>Inactive storage:</b></td>\n<td><tt> ");
541 buffer.AppendInt(mDevice->mInactiveSize / 1024);
542 buffer.AppendLiteral(" KiB</tt></td>\n</tr>\n");
544 *result = ToNewCString(buffer);
545 if (!*result) return NS_ERROR_OUT_OF_MEMORY;
546 return NS_OK;
550 NS_IMETHODIMP
551 nsMemoryCacheDeviceInfo::GetEntryCount(PRUint32 * result)
553 NS_ENSURE_ARG_POINTER(result);
554 // XXX compare calculated count vs. mEntryCount
555 *result = (PRUint32)mDevice->mEntryCount;
556 return NS_OK;
560 NS_IMETHODIMP
561 nsMemoryCacheDeviceInfo::GetTotalSize(PRUint32 * result)
563 NS_ENSURE_ARG_POINTER(result);
564 *result = (PRUint32)mDevice->mTotalSize;
565 return NS_OK;
569 NS_IMETHODIMP
570 nsMemoryCacheDeviceInfo::GetMaximumSize(PRUint32 * result)
572 NS_ENSURE_ARG_POINTER(result);
573 *result = (PRUint32)mDevice->mHardLimit;
574 return NS_OK;