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
16 * The Original Code is nsMemoryCacheDevice.cpp, released
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.
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"
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
74 for (int i
=0; i
<kQueueCount
; ++i
)
75 PR_INIT_CLIST(&mEvictionList
[i
]);
79 nsMemoryCacheDevice::~nsMemoryCacheDevice()
86 nsMemoryCacheDevice::Init()
88 if (mInitialized
) return NS_ERROR_ALREADY_INITIALIZED
;
90 nsresult rv
= mMemCacheEntries
.Init();
91 mInitialized
= NS_SUCCEEDED(rv
);
97 nsMemoryCacheDevice::Shutdown()
99 NS_ASSERTION(mInitialized
, "### attempting shutdown while not initialized");
100 NS_ENSURE_TRUE(mInitialized
, NS_ERROR_NOT_INITIALIZED
);
102 mMemCacheEntries
.Shutdown();
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
);
115 PRInt32 memoryRecovered
= (PRInt32
)entry
->Size();
116 mTotalSize
-= memoryRecovered
;
117 mInactiveSize
-= memoryRecovered
;
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
;
139 nsMemoryCacheDevice::GetDeviceID()
141 return gMemoryDeviceID
;
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();
162 nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry
* entry
)
164 CACHE_LOG_DEBUG(("nsMemoryCacheDevice::DeactivateEntry for entry 0x%p\n",
166 if (entry
->IsDoomed()) {
168 // XXX verify we've removed it from mMemCacheEntries & eviction list
171 CACHE_LOG_DEBUG(("deleted doomed entry 0x%p\n", entry
));
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
;
183 mInactiveSize
+= entry
->Size();
184 EvictEntriesIfNecessary();
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
);
202 PR_REMOVE_AND_INIT_LINK(entry
);
207 // add size of entry to memory totals
209 if (mMaxEntryCount
< mEntryCount
) mMaxEntryCount
= mEntryCount
;
211 mTotalSize
+= entry
->Size();
212 EvictEntriesIfNecessary();
219 nsMemoryCacheDevice::DoomEntry(nsCacheEntry
* entry
)
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");
227 CACHE_LOG_DEBUG(("Dooming entry 0x%p in memory cache\n", entry
));
228 EvictEntry(entry
, DO_NOT_DELETE_ENTRY
);
233 nsMemoryCacheDevice::OpenInputStreamForEntry( nsCacheEntry
* entry
,
234 nsCacheAccessMode mode
,
236 nsIInputStream
** result
)
238 NS_ENSURE_ARG_POINTER(entry
);
239 NS_ENSURE_ARG_POINTER(result
);
241 nsCOMPtr
<nsIStorageStream
> storage
;
244 nsISupports
*data
= entry
->Data();
246 storage
= do_QueryInterface(data
, &rv
);
251 rv
= NS_NewStorageStream(4096, PRUint32(-1), getter_AddRefs(storage
));
254 entry
->SetData(storage
);
257 return storage
->NewInputStream(offset
, result
);
262 nsMemoryCacheDevice::OpenOutputStreamForEntry( nsCacheEntry
* entry
,
263 nsCacheAccessMode mode
,
265 nsIOutputStream
** result
)
267 NS_ENSURE_ARG_POINTER(entry
);
268 NS_ENSURE_ARG_POINTER(result
);
270 nsCOMPtr
<nsIStorageStream
> storage
;
273 nsISupports
*data
= entry
->Data();
275 storage
= do_QueryInterface(data
, &rv
);
280 rv
= NS_NewStorageStream(4096, PRUint32(-1), getter_AddRefs(storage
));
283 entry
->SetData(storage
);
286 return storage
->GetOutputStream(offset
, result
);
291 nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry
* entry
,
294 return NS_ERROR_NOT_IMPLEMENTED
;
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
) {
308 nsCacheService::DoomEntry(entry
);
309 NS_ASSERTION(NS_SUCCEEDED(rv
),"DoomEntry() failed.");
310 return NS_ERROR_ABORT
;
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();
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();
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
);
351 PRInt32 memoryRecovered
= (PRInt32
)entry
->Size();
352 mTotalSize
-= memoryRecovered
;
353 if (!entry
->IsDoomed())
354 mInactiveSize
-= memoryRecovered
;
357 if (deleteEntry
) delete entry
;
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
))
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
);
381 next
= (nsCacheEntry
*)PR_NEXT_LINK(entry
);
382 EvictEntry(entry
, DELETE_ENTRY
);
385 if ((mTotalSize
< mHardLimit
) && (mInactiveSize
< mSoftLimit
))
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
)
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);
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
;
416 nsresult rv
= visitor
->VisitDevice(gMemoryDeviceID
, deviceInfo
, &keepGoing
);
417 if (NS_FAILED(rv
)) return rv
;
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
);
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)
460 if (entry
->IsInUse()) {
461 nsresult rv
= nsCacheService::DoomEntry(entry
);
463 CACHE_LOG_WARNING(("memCache->EvictEntries() aborted: rv =%x", rv
));
467 EvictEntry(entry
, DELETE_ENTRY
);
476 // WARNING: SetCapacity can get called before Init()
478 nsMemoryCacheDevice::SetCapacity(PRInt32 capacity
)
480 PRInt32 hardLimit
= capacity
* 1024; // convert k into bytes
481 PRInt32 softLimit
= (hardLimit
* 9) / 10;
482 AdjustMemoryLimits(softLimit
, hardLimit
);
487 static PLDHashOperator
488 CountEntry(PLDHashTable
* table
, PLDHashEntryHdr
* hdr
, PRUint32 number
, void * arg
)
490 PRInt32
*entryCount
= (PRInt32
*)arg
;
492 return PL_DHASH_NEXT
;
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
);
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");
516 /******************************************************************************
517 * nsMemoryCacheDeviceInfo - for implementing about:cache
518 *****************************************************************************/
521 NS_IMPL_ISUPPORTS1(nsMemoryCacheDeviceInfo
, nsICacheDeviceInfo
)
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
;
535 nsMemoryCacheDeviceInfo::GetUsageReport(char ** result
)
537 NS_ENSURE_ARG_POINTER(result
);
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
;
551 nsMemoryCacheDeviceInfo::GetEntryCount(PRUint32
* result
)
553 NS_ENSURE_ARG_POINTER(result
);
554 // XXX compare calculated count vs. mEntryCount
555 *result
= (PRUint32
)mDevice
->mEntryCount
;
561 nsMemoryCacheDeviceInfo::GetTotalSize(PRUint32
* result
)
563 NS_ENSURE_ARG_POINTER(result
);
564 *result
= (PRUint32
)mDevice
->mTotalSize
;
570 nsMemoryCacheDeviceInfo::GetMaximumSize(PRUint32
* result
)
572 NS_ENSURE_ARG_POINTER(result
);
573 *result
= (PRUint32
)mDevice
->mHardLimit
;