Bug 627938: Fix nsGlobalChromeWindow cleanup. (r=smaug, a=jst)
[mozilla-central.git] / netwerk / cache / nsCacheService.cpp
blobed14309e8c372e39ad8e03e0abb02dca94cc7ebf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set sw=4 ts=8 et tw=80 : */
3 /*
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is nsCacheService.cpp, released
18 * February 10, 2001.
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 2001
23 * the Initial Developer. All Rights Reserved.
25 * Contributor(s):
26 * Gordon Sheridan, 10-February-2001
27 * Michael Ventnor <m.ventnor@gmail.com>
28 * Ehsan Akhgari <ehsan.akhgari@gmail.com>
30 * Alternatively, the contents of this file may be used under the terms of
31 * either the GNU General Public License Version 2 or later (the "GPL"), or
32 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
33 * in which case the provisions of the GPL or the LGPL are applicable instead
34 * of those above. If you wish to allow use of your version of this file only
35 * under the terms of either the GPL or the LGPL, and not to allow others to
36 * use your version of this file under the terms of the MPL, indicate your
37 * decision by deleting the provisions above and replace them with the notice
38 * and other provisions required by the GPL or the LGPL. If you do not delete
39 * the provisions above, a recipient may use your version of this file under
40 * the terms of any one of the MPL, the GPL or the LGPL.
42 * ***** END LICENSE BLOCK ***** */
44 #include "necko-config.h"
46 #include "nsCache.h"
47 #include "nsCacheService.h"
48 #include "nsCacheRequest.h"
49 #include "nsCacheEntry.h"
50 #include "nsCacheEntryDescriptor.h"
51 #include "nsCacheDevice.h"
52 #include "nsMemoryCacheDevice.h"
53 #include "nsICacheVisitor.h"
54 #include "nsDiskCacheDevice.h"
56 #ifdef NECKO_OFFLINE_CACHE
57 #include "nsDiskCacheDeviceSQL.h"
58 #endif
60 #include "nsIObserverService.h"
61 #include "nsIPrefService.h"
62 #include "nsIPrefBranch.h"
63 #include "nsIPrefBranch2.h"
64 #include "nsILocalFile.h"
65 #include "nsIOService.h"
66 #include "nsDirectoryServiceDefs.h"
67 #include "nsAppDirectoryServiceDefs.h"
68 #include "nsThreadUtils.h"
69 #include "nsProxyRelease.h"
70 #include "nsVoidArray.h"
71 #include "nsDeleteDir.h"
72 #include "nsIPrivateBrowsingService.h"
73 #include "nsNetCID.h"
74 #include <math.h> // for log()
75 #include "mozilla/Services.h"
77 #include "mozilla/FunctionTimer.h"
79 #ifdef MOZ_IPC
80 #include "mozilla/net/NeckoCommon.h"
81 #endif
83 /******************************************************************************
84 * nsCacheProfilePrefObserver
85 *****************************************************************************/
86 #ifdef XP_MAC
87 #pragma mark nsCacheProfilePrefObserver
88 #endif
90 #define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
91 #define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
92 #define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
93 "browser.cache.disk.smart_size.first_run"
94 #define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
95 "browser.cache.disk.smart_size.enabled"
96 #define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
97 #define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
98 #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
99 #define DISK_CACHE_CAPACITY 256000
101 #define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
102 #define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
103 #define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
104 #define OFFLINE_CACHE_CAPACITY 512000
106 #define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
107 #define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
108 #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
110 static const char * observerList[] = {
111 "profile-before-change",
112 "profile-do-change",
113 NS_XPCOM_SHUTDOWN_OBSERVER_ID,
114 NS_PRIVATE_BROWSING_SWITCH_TOPIC
116 static const char * prefList[] = {
117 #ifdef NECKO_DISK_CACHE
118 DISK_CACHE_ENABLE_PREF,
119 DISK_CACHE_SMART_SIZE_ENABLED_PREF,
120 DISK_CACHE_CAPACITY_PREF,
121 DISK_CACHE_DIR_PREF,
122 #endif
123 #ifdef NECKO_OFFLINE_CACHE
124 OFFLINE_CACHE_ENABLE_PREF,
125 OFFLINE_CACHE_CAPACITY_PREF,
126 OFFLINE_CACHE_DIR_PREF,
127 #endif
128 MEMORY_CACHE_ENABLE_PREF,
129 MEMORY_CACHE_CAPACITY_PREF
132 // Cache sizes, in KB
133 const PRInt32 DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
134 const PRInt32 MIN_CACHE_SIZE = 50 * 1024; // 50 MB
135 const PRInt32 MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
136 // Default cache size was 50 MB for many years until FF 4:
137 const PRInt32 PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
139 class nsCacheProfilePrefObserver : public nsIObserver
141 public:
142 NS_DECL_ISUPPORTS
143 NS_DECL_NSIOBSERVER
145 nsCacheProfilePrefObserver()
146 : mHaveProfile(PR_FALSE)
147 , mDiskCacheEnabled(PR_FALSE)
148 , mDiskCacheCapacity(0)
149 , mOfflineCacheEnabled(PR_FALSE)
150 , mOfflineCacheCapacity(0)
151 , mMemoryCacheEnabled(PR_TRUE)
152 , mMemoryCacheCapacity(-1)
153 , mInPrivateBrowsing(PR_FALSE)
157 virtual ~nsCacheProfilePrefObserver() {}
159 nsresult Install();
160 void Remove();
161 nsresult ReadPrefs(nsIPrefBranch* branch);
163 PRBool DiskCacheEnabled();
164 PRInt32 DiskCacheCapacity() { return mDiskCacheCapacity; }
165 void SetDiskCacheCapacity(PRInt32);
166 nsILocalFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
168 PRBool OfflineCacheEnabled();
169 PRInt32 OfflineCacheCapacity() { return mOfflineCacheCapacity; }
170 nsILocalFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
172 PRBool MemoryCacheEnabled();
173 PRInt32 MemoryCacheCapacity();
175 static PRUint32 GetSmartCacheSize(const nsAString& cachePath);
177 private:
178 bool PermittedToSmartSize(nsIPrefBranch*, PRBool firstRun);
179 PRBool mHaveProfile;
181 PRBool mDiskCacheEnabled;
182 PRInt32 mDiskCacheCapacity; // in kilobytes
183 nsCOMPtr<nsILocalFile> mDiskCacheParentDirectory;
185 PRBool mOfflineCacheEnabled;
186 PRInt32 mOfflineCacheCapacity; // in kilobytes
187 nsCOMPtr<nsILocalFile> mOfflineCacheParentDirectory;
189 PRBool mMemoryCacheEnabled;
190 PRInt32 mMemoryCacheCapacity; // in kilobytes
192 PRBool mInPrivateBrowsing;
195 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver)
197 // Runnable sent to main thread after the cache IO thread calculates available
198 // disk space, so that there is no race in setting mDiskCacheCapacity.
199 class nsSetSmartSizeEvent: public nsRunnable
201 public:
202 nsSetSmartSizeEvent(bool firstRun, PRInt32 smartSize)
203 : mFirstRun(firstRun) , mSmartSize(smartSize) {}
205 NS_IMETHOD Run()
207 nsresult rv;
208 NS_ASSERTION(NS_IsMainThread(),
209 "Setting smart size data off the main thread");
211 // Main thread may have already called nsCacheService::Shutdown
212 if (!nsCacheService::gService || !nsCacheService::gService->mObserver)
213 return NS_ERROR_NOT_AVAILABLE;
215 PRBool smartSizeEnabled;
216 nsCOMPtr<nsIPrefBranch2> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
217 if (!branch) {
218 NS_WARNING("Failed to get pref service!");
219 return NS_ERROR_NOT_AVAILABLE;
221 // ensure smart sizing wasn't switched off while event was pending
222 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
223 &smartSizeEnabled);
224 if (NS_FAILED(rv))
225 smartSizeEnabled = PR_FALSE;
226 if (smartSizeEnabled) {
227 nsCacheService::SetDiskCacheCapacity(mSmartSize);
228 // also set on observer, in case mDiskDevice not init'd yet.
229 nsCacheService::gService->mObserver->SetDiskCacheCapacity(mSmartSize);
230 rv = branch->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize);
231 if (NS_FAILED(rv))
232 NS_WARNING("Failed to set smart size pref");
234 return rv;
237 private:
238 bool mFirstRun;
239 PRInt32 mSmartSize;
243 // Runnable sent from main thread to cacheIO thread
244 class nsGetSmartSizeEvent: public nsRunnable
246 public:
247 nsGetSmartSizeEvent(bool firstRun, const nsAString& cachePath)
248 : mFirstRun(firstRun)
249 , mCachePath(cachePath)
250 , mSmartSize(0)
253 // Calculates user's disk space available on a background thread and
254 // dispatches this value back to the main thread.
255 NS_IMETHOD Run()
257 mSmartSize =
258 nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath);
259 nsCOMPtr<nsIRunnable> event = new nsSetSmartSizeEvent(mFirstRun,
260 mSmartSize);
261 NS_DispatchToMainThread(event);
262 return NS_OK;
265 private:
266 bool mFirstRun;
267 nsString mCachePath;
268 PRInt32 mSmartSize;
271 class nsBlockOnCacheThreadEvent : public nsRunnable {
272 public:
273 nsBlockOnCacheThreadEvent()
276 NS_IMETHOD Run()
278 mozilla::MonitorAutoEnter
279 autoMonitor(nsCacheService::gService->mMonitor);
280 #ifdef PR_LOGGING
281 CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
282 #endif
283 autoMonitor.Notify();
284 return NS_OK;
289 nsresult
290 nsCacheProfilePrefObserver::Install()
292 // install profile-change observer
293 nsCOMPtr<nsIObserverService> observerService =
294 mozilla::services::GetObserverService();
295 if (!observerService)
296 return NS_ERROR_FAILURE;
298 nsresult rv, rv2 = NS_OK;
299 for (unsigned int i=0; i<NS_ARRAY_LENGTH(observerList); i++) {
300 rv = observerService->AddObserver(this, observerList[i], PR_FALSE);
301 if (NS_FAILED(rv))
302 rv2 = rv;
305 // install preferences observer
306 nsCOMPtr<nsIPrefBranch2> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
307 if (!branch) return NS_ERROR_FAILURE;
309 for (unsigned int i=0; i<NS_ARRAY_LENGTH(prefList); i++) {
310 rv = branch->AddObserver(prefList[i], this, PR_FALSE);
311 if (NS_FAILED(rv))
312 rv2 = rv;
315 // determine the initial status of the private browsing mode
316 nsCOMPtr<nsIPrivateBrowsingService> pbs =
317 do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
318 if (pbs)
319 pbs->GetPrivateBrowsingEnabled(&mInPrivateBrowsing);
321 // Determine if we have a profile already
322 // Install() is called *after* the profile-after-change notification
323 // when there is only a single profile, or it is specified on the
324 // commandline at startup.
325 // In that case, we detect the presence of a profile by the existence
326 // of the NS_APP_USER_PROFILE_50_DIR directory.
328 nsCOMPtr<nsIFile> directory;
329 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
330 getter_AddRefs(directory));
331 if (NS_SUCCEEDED(rv))
332 mHaveProfile = PR_TRUE;
334 rv = ReadPrefs(branch);
335 NS_ENSURE_SUCCESS(rv, rv);
337 return rv2;
341 void
342 nsCacheProfilePrefObserver::Remove()
344 // remove Observer Service observers
345 nsCOMPtr<nsIObserverService> obs =
346 mozilla::services::GetObserverService();
347 if (obs) {
348 for (unsigned int i=0; i<NS_ARRAY_LENGTH(observerList); i++) {
349 obs->RemoveObserver(this, observerList[i]);
353 // remove Pref Service observers
354 nsCOMPtr<nsIPrefBranch2> prefs =
355 do_GetService(NS_PREFSERVICE_CONTRACTID);
356 if (!prefs)
357 return;
358 for (unsigned int i=0; i<NS_ARRAY_LENGTH(prefList); i++)
359 prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
362 void
363 nsCacheProfilePrefObserver::SetDiskCacheCapacity(PRInt32 capacity)
365 mDiskCacheCapacity = PR_MAX(0, capacity);
369 NS_IMETHODIMP
370 nsCacheProfilePrefObserver::Observe(nsISupports * subject,
371 const char * topic,
372 const PRUnichar * data_unicode)
374 nsresult rv;
375 NS_ConvertUTF16toUTF8 data(data_unicode);
376 CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get()));
378 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
379 // xpcom going away, shutdown cache service
380 if (nsCacheService::GlobalInstance())
381 nsCacheService::GlobalInstance()->Shutdown();
383 } else if (!strcmp("profile-before-change", topic)) {
384 // profile before change
385 mHaveProfile = PR_FALSE;
387 // XXX shutdown devices
388 nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse",
389 data.get()));
391 } else if (!strcmp("profile-do-change", topic)) {
392 // profile after change
393 mHaveProfile = PR_TRUE;
394 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
395 ReadPrefs(branch);
396 nsCacheService::OnProfileChanged();
398 } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
400 // ignore pref changes until we're done switch profiles
401 if (!mHaveProfile)
402 return NS_OK;
404 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
405 if (NS_FAILED(rv))
406 return rv;
408 #ifdef NECKO_DISK_CACHE
409 // which preference changed?
410 if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
412 if (!mInPrivateBrowsing) {
413 rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
414 &mDiskCacheEnabled);
415 if (NS_FAILED(rv))
416 return rv;
417 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
420 } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
422 PRInt32 capacity = 0;
423 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
424 if (NS_FAILED(rv))
425 return rv;
426 mDiskCacheCapacity = PR_MAX(0, capacity);
427 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
429 // Update the cache capacity when smart sizing is turned on/off
430 } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
431 // Is the update because smartsizing was turned on, or off?
432 PRBool smartSizeEnabled;
433 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
434 &smartSizeEnabled);
435 if (NS_FAILED(rv))
436 return rv;
437 PRInt32 newCapacity = 0;
438 if (smartSizeEnabled) {
439 // Dispatch event to update smart size: just keep using old
440 // value if this fails at any point
441 if (!mDiskCacheParentDirectory)
442 return NS_ERROR_NOT_AVAILABLE; // disk cache disabled anyway
443 nsAutoString cachePath;
444 rv = mDiskCacheParentDirectory->GetPath(cachePath);
445 if (NS_FAILED(rv))
446 return rv;
447 // Smart sizing switched on: recalculate the capacity.
448 nsCOMPtr<nsIRunnable> event =
449 new nsGetSmartSizeEvent(false, cachePath);
450 rv = nsCacheService::DispatchToCacheIOThread(event);
451 } else {
452 // Smart sizing switched off: use user specified size
453 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
454 if (NS_FAILED(rv))
455 return rv;
456 mDiskCacheCapacity = PR_MAX(0, newCapacity);
457 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
459 #if 0
460 } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
461 // XXX We probaby don't want to respond to this pref except after
462 // XXX profile changes. Ideally, there should be somekind of user
463 // XXX notification that the pref change won't take effect until
464 // XXX the next time the profile changes (browser launch)
465 #endif
466 } else
467 #endif // !NECKO_DISK_CACHE
469 #ifdef NECKO_OFFLINE_CACHE
470 // which preference changed?
471 if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
473 if (!mInPrivateBrowsing) {
474 rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
475 &mOfflineCacheEnabled);
476 if (NS_FAILED(rv)) return rv;
477 nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
480 } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
482 PRInt32 capacity = 0;
483 rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
484 if (NS_FAILED(rv)) return rv;
485 mOfflineCacheCapacity = PR_MAX(0, capacity);
486 nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
487 #if 0
488 } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
489 // XXX We probaby don't want to respond to this pref except after
490 // XXX profile changes. Ideally, there should be some kind of user
491 // XXX notification that the pref change won't take effect until
492 // XXX the next time the profile changes (browser launch)
493 #endif
494 } else
495 #endif // !NECKO_OFFLINE_CACHE
497 if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
499 rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
500 &mMemoryCacheEnabled);
501 if (NS_FAILED(rv))
502 return rv;
503 nsCacheService::SetMemoryCache();
505 } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
507 mMemoryCacheCapacity = -1;
508 (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
509 &mMemoryCacheCapacity);
510 nsCacheService::SetMemoryCache();
512 } else if (!strcmp(NS_PRIVATE_BROWSING_SWITCH_TOPIC, topic)) {
513 if (!strcmp(NS_PRIVATE_BROWSING_ENTER, data.get())) {
514 mInPrivateBrowsing = PR_TRUE;
516 nsCacheService::OnEnterExitPrivateBrowsing();
518 #ifdef NECKO_DISK_CACHE
519 mDiskCacheEnabled = PR_FALSE;
520 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
521 #endif // !NECKO_DISK_CACHE
523 #ifdef NECKO_OFFLINE_CACHE
524 mOfflineCacheEnabled = PR_FALSE;
525 nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
526 #endif // !NECKO_OFFLINE_CACHE
527 } else if (!strcmp(NS_PRIVATE_BROWSING_LEAVE, data.get())) {
528 mInPrivateBrowsing = PR_FALSE;
530 nsCacheService::OnEnterExitPrivateBrowsing();
532 #if defined(NECKO_DISK_CACHE) || defined(NECKO_OFFLINE_CACHE)
533 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
534 if (NS_FAILED(rv))
535 return rv;
536 #endif // !NECKO_DISK_CACHE && !NECKO_OFFLINE_CACHE
538 #ifdef NECKO_DISK_CACHE
539 mDiskCacheEnabled = PR_TRUE; // by default enabled
540 (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
541 &mDiskCacheEnabled);
542 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
543 #endif // !NECKO_DISK_CACHE
545 #ifdef NECKO_OFFLINE_CACHE
546 mOfflineCacheEnabled = PR_TRUE; // by default enabled
547 (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
548 &mOfflineCacheEnabled);
549 nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
550 #endif // !NECKO_OFFLINE_CACHE
554 return NS_OK;
557 /* Computes our best guess for the default size of the user's disk cache,
558 * based on the amount of space they have free on their hard drive.
559 * We use a tiered scheme: the more space available,
560 * the larger the disk cache will be. However, we do not want
561 * to enable the disk cache to grow to an unbounded size, so the larger the
562 * user's available space is, the smaller of a percentage we take. We set a
563 * lower bound of 50MB and an upper bound of 1GB.
565 *@param: None.
566 *@return: The size that the user's disk cache should default to, in kBytes.
568 PRUint32
569 nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath)
571 // Check for free space on device where cache directory lives
572 nsresult rv;
573 nsCOMPtr<nsILocalFile>
574 cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
575 if (NS_FAILED(rv) || !cacheDirectory)
576 return DEFAULT_CACHE_SIZE;
577 rv = cacheDirectory->InitWithPath(cachePath);
578 if (NS_FAILED(rv))
579 return DEFAULT_CACHE_SIZE;
580 PRInt64 bytesAvailable;
581 rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
582 if (NS_FAILED(rv))
583 return DEFAULT_CACHE_SIZE;
584 PRInt64 kBytesAvail = bytesAvailable / 1024;
586 // 0 MB <= Available < 500 MB: Use between 50MB and 200MB
587 if (kBytesAvail < DEFAULT_CACHE_SIZE * 2)
588 return PR_MAX(MIN_CACHE_SIZE, kBytesAvail * 4 / 10);
590 // 500MB <= Available < 2.5 GB: Use 250MB
591 if (kBytesAvail < static_cast<PRInt64>(DEFAULT_CACHE_SIZE) * 10)
592 return DEFAULT_CACHE_SIZE;
594 // 2.5 GB <= Available < 5 GB: Use between 250MB and 500MB
595 if (kBytesAvail < static_cast<PRInt64>(DEFAULT_CACHE_SIZE) * 20)
596 return kBytesAvail / 10;
598 // 5 GB <= Available < 50 GB: Use 625MB
599 if (kBytesAvail < static_cast<PRInt64>(DEFAULT_CACHE_SIZE) * 200 )
600 return DEFAULT_CACHE_SIZE * 5 / 2;
602 // 50 GB <= Available < 75 GB: Use 800MB
603 if (kBytesAvail < static_cast<PRInt64>(DEFAULT_CACHE_SIZE) * 300)
604 return DEFAULT_CACHE_SIZE / 5 * 16;
606 // Use 1 GB
607 return MAX_CACHE_SIZE;
610 /* Determine if we are permitted to dynamically size the user's disk cache based
611 * on their disk space available. We may do this so long as the pref
612 * smart_size.enabled is true.
614 bool
615 nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, PRBool
616 firstRun)
618 nsresult rv;
619 if (firstRun) {
620 // check if user has set cache size in the past
621 PRBool userSet;
622 rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
623 if (NS_FAILED(rv)) userSet = PR_TRUE;
624 if (userSet) {
625 PRInt32 oldCapacity;
626 // If user explicitly set cache size to be smaller than old default
627 // of 50 MB, then keep user's value. Otherwise use smart sizing.
628 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
629 if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
630 branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
631 PR_FALSE);
632 return false;
635 // Set manual setting to MAX cache size as starting val for any
636 // adjustment by user: (bug 559942 comment 65)
637 branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
639 PRBool smartSizeEnabled;
640 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
641 &smartSizeEnabled);
642 if (NS_FAILED(rv))
643 return false;
644 return !!smartSizeEnabled;
648 nsresult
649 nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
651 nsresult rv = NS_OK;
653 #ifdef NECKO_DISK_CACHE
654 // read disk cache device prefs
655 if (!mInPrivateBrowsing) {
656 mDiskCacheEnabled = PR_TRUE; // presume disk cache is enabled
657 (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
660 mDiskCacheCapacity = DISK_CACHE_CAPACITY;
661 (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
662 mDiskCacheCapacity = PR_MAX(0, mDiskCacheCapacity);
664 (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
665 NS_GET_IID(nsILocalFile),
666 getter_AddRefs(mDiskCacheParentDirectory));
668 if (!mDiskCacheParentDirectory) {
669 nsCOMPtr<nsIFile> directory;
671 // try to get the disk cache parent directory
672 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
673 getter_AddRefs(directory));
674 if (NS_FAILED(rv)) {
675 // try to get the profile directory (there may not be a profile yet)
676 nsCOMPtr<nsIFile> profDir;
677 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
678 getter_AddRefs(profDir));
679 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
680 getter_AddRefs(directory));
681 if (!directory)
682 directory = profDir;
683 else if (profDir) {
684 PRBool same;
685 if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) {
686 // We no longer store the cache directory in the main
687 // profile directory, so we should cleanup the old one.
688 rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
689 if (NS_SUCCEEDED(rv)) {
690 PRBool exists;
691 if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists)
692 DeleteDir(profDir, PR_FALSE, PR_FALSE);
697 // use file cache in build tree only if asked, to avoid cache dir litter
698 if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
699 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
700 getter_AddRefs(directory));
702 if (directory)
703 mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
705 if (mDiskCacheParentDirectory) {
706 PRBool firstSmartSizeRun;
707 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
708 &firstSmartSizeRun);
709 if (NS_FAILED(rv))
710 firstSmartSizeRun = PR_FALSE;
711 if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
712 // Avoid evictions: use previous cache size until smart size event
713 // updates mDiskCacheCapacity
714 if (!firstSmartSizeRun) {
715 PRInt32 oldSmartSize;
716 rv = branch->GetIntPref(DISK_CACHE_SMART_SIZE_PREF,
717 &oldSmartSize);
718 mDiskCacheCapacity = oldSmartSize;
719 } else {
720 PRInt32 oldCapacity;
721 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
722 if (NS_SUCCEEDED(rv)) {
723 mDiskCacheCapacity = oldCapacity;
724 } else {
725 mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
728 nsAutoString cachePath;
729 rv = mDiskCacheParentDirectory->GetPath(cachePath);
730 if (NS_SUCCEEDED(rv)) {
731 nsCOMPtr<nsIRunnable> event =
732 new nsGetSmartSizeEvent(!!firstSmartSizeRun, cachePath);
733 nsCacheService::DispatchToCacheIOThread(event);
737 if (firstSmartSizeRun) {
738 // It is no longer our first run
739 rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
740 PR_FALSE);
741 if (NS_FAILED(rv))
742 NS_WARNING("Failed setting first_run pref in ReadPrefs.");
745 #endif // !NECKO_DISK_CACHE
747 #ifdef NECKO_OFFLINE_CACHE
748 // read offline cache device prefs
749 if (!mInPrivateBrowsing) {
750 mOfflineCacheEnabled = PR_TRUE; // presume offline cache is enabled
751 (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
752 &mOfflineCacheEnabled);
755 mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
756 (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
757 &mOfflineCacheCapacity);
758 mOfflineCacheCapacity = PR_MAX(0, mOfflineCacheCapacity);
760 (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
761 NS_GET_IID(nsILocalFile),
762 getter_AddRefs(mOfflineCacheParentDirectory));
764 if (!mOfflineCacheParentDirectory) {
765 nsCOMPtr<nsIFile> directory;
767 // try to get the offline cache parent directory
768 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
769 getter_AddRefs(directory));
770 if (NS_FAILED(rv)) {
771 // try to get the profile directory (there may not be a profile yet)
772 nsCOMPtr<nsIFile> profDir;
773 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
774 getter_AddRefs(profDir));
775 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
776 getter_AddRefs(directory));
777 if (!directory)
778 directory = profDir;
780 #if DEBUG
781 if (!directory) {
782 // use current process directory during development
783 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
784 getter_AddRefs(directory));
786 #endif
787 if (directory)
788 mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
790 #endif // !NECKO_OFFLINE_CACHE
792 // read memory cache device prefs
793 (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
795 mMemoryCacheCapacity = -1;
796 (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
797 &mMemoryCacheCapacity);
799 return rv;
802 nsresult
803 nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
805 if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
806 return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
809 nsresult
810 nsCacheService::SyncWithCacheIOThread()
812 NS_ASSERTION(gService->mLockedThread == PR_GetCurrentThread(),
813 "not holding cache-lock");
814 if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
816 mozilla::MonitorAutoEnter autoMonitor(gService->mMonitor);
818 nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
820 // dispatch event - it will notify the monitor when it's done
821 nsresult rv =
822 gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
823 if (NS_FAILED(rv)) {
824 NS_WARNING("Failed dispatching block-event");
825 return NS_ERROR_UNEXPECTED;
828 Unlock();
829 // wait until notified, then return
830 rv = autoMonitor.Wait();
831 Lock();
833 return rv;
837 PRBool
838 nsCacheProfilePrefObserver::DiskCacheEnabled()
840 if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return PR_FALSE;
841 return mDiskCacheEnabled;
845 PRBool
846 nsCacheProfilePrefObserver::OfflineCacheEnabled()
848 if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
849 return PR_FALSE;
851 return mOfflineCacheEnabled;
855 PRBool
856 nsCacheProfilePrefObserver::MemoryCacheEnabled()
858 if (mMemoryCacheCapacity == 0) return PR_FALSE;
859 return mMemoryCacheEnabled;
864 * MemoryCacheCapacity
866 * If the browser.cache.memory.capacity preference is positive, we use that
867 * value for the amount of memory available for the cache.
869 * If browser.cache.memory.capacity is zero, the memory cache is disabled.
871 * If browser.cache.memory.capacity is negative or not present, we use a
872 * formula that grows less than linearly with the amount of system memory,
873 * with an upper limit on the cache size. No matter how much physical RAM is
874 * present, the default cache size would not exceed 32 MB. This maximum would
875 * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
877 * RAM Cache
878 * --- -----
879 * 32 Mb 2 Mb
880 * 64 Mb 4 Mb
881 * 128 Mb 6 Mb
882 * 256 Mb 10 Mb
883 * 512 Mb 14 Mb
884 * 1024 Mb 18 Mb
885 * 2048 Mb 24 Mb
886 * 4096 Mb 30 Mb
888 * The equation for this is (for cache size C and memory size K (kbytes)):
889 * x = log2(K) - 14
890 * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
891 * if (C > 32) C = 32
894 PRInt32
895 nsCacheProfilePrefObserver::MemoryCacheCapacity()
897 PRInt32 capacity = mMemoryCacheCapacity;
898 if (capacity >= 0) {
899 CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
900 return capacity;
903 static PRUint64 bytes = PR_GetPhysicalMemorySize();
904 CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
906 // If getting the physical memory failed, arbitrarily assume
907 // 32 MB of RAM. We use a low default to have a reasonable
908 // size on all the devices we support.
909 if (bytes == 0)
910 bytes = 32 * 1024 * 1024;
912 // Conversion from unsigned int64 to double doesn't work on all platforms.
913 // We need to truncate the value at LL_MAXINT to make sure we don't
914 // overflow.
915 if (LL_CMP(bytes, >, LL_MAXINT))
916 bytes = LL_MAXINT;
918 PRUint64 kbytes;
919 LL_SHR(kbytes, bytes, 10);
921 double kBytesD;
922 LL_L2D(kBytesD, (PRInt64) kbytes);
924 double x = log(kBytesD)/log(2.0) - 14;
925 if (x > 0) {
926 capacity = (PRInt32)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
927 if (capacity > 32)
928 capacity = 32;
929 capacity *= 1024;
930 } else {
931 capacity = 0;
934 return capacity;
938 /******************************************************************************
939 * nsProcessRequestEvent
940 *****************************************************************************/
942 class nsProcessRequestEvent : public nsRunnable {
943 public:
944 nsProcessRequestEvent(nsCacheRequest *aRequest)
946 mRequest = aRequest;
949 NS_IMETHOD Run()
951 nsresult rv;
953 NS_ASSERTION(mRequest->mListener,
954 "Sync OpenCacheEntry() posted to background thread!");
956 nsCacheServiceAutoLock lock;
957 rv = nsCacheService::gService->ProcessRequest(mRequest,
958 PR_FALSE,
959 nsnull);
961 // Don't delete the request if it was queued
962 if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
963 delete mRequest;
965 return NS_OK;
968 protected:
969 virtual ~nsProcessRequestEvent() {}
971 private:
972 nsCacheRequest *mRequest;
975 /******************************************************************************
976 * nsCacheService
977 *****************************************************************************/
978 #ifdef XP_MAC
979 #pragma mark -
980 #pragma mark nsCacheService
981 #endif
983 nsCacheService * nsCacheService::gService = nsnull;
985 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService)
987 nsCacheService::nsCacheService()
988 : mLock(nsnull),
989 mMonitor("block-on-cache-monitor"),
990 mInitialized(PR_FALSE),
991 mEnableMemoryDevice(PR_TRUE),
992 mEnableDiskDevice(PR_TRUE),
993 mMemoryDevice(nsnull),
994 mDiskDevice(nsnull),
995 mOfflineDevice(nsnull),
996 mTotalEntries(0),
997 mCacheHits(0),
998 mCacheMisses(0),
999 mMaxKeyLength(0),
1000 mMaxDataSize(0),
1001 mMaxMetaSize(0),
1002 mDeactivateFailures(0),
1003 mDeactivatedUnboundEntries(0)
1005 NS_ASSERTION(gService==nsnull, "multiple nsCacheService instances!");
1006 gService = this;
1008 // create list of cache devices
1009 PR_INIT_CLIST(&mDoomedEntries);
1011 // allocate service lock
1012 mLock = PR_NewLock();
1014 #if defined(DEBUG)
1015 mLockedThread = nsnull;
1016 #endif
1019 nsCacheService::~nsCacheService()
1021 if (mInitialized) // Shutdown hasn't been called yet.
1022 (void) Shutdown();
1024 PR_DestroyLock(mLock);
1025 gService = nsnull;
1029 nsresult
1030 nsCacheService::Init()
1032 NS_TIME_FUNCTION;
1034 NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
1035 if (mInitialized)
1036 return NS_ERROR_ALREADY_INITIALIZED;
1038 #ifdef MOZ_IPC
1039 if (mozilla::net::IsNeckoChild()) {
1040 return NS_ERROR_UNEXPECTED;
1042 #endif
1044 if (mLock == nsnull)
1045 return NS_ERROR_OUT_OF_MEMORY;
1047 CACHE_LOG_INIT();
1049 nsresult rv = NS_NewThread(getter_AddRefs(mCacheIOThread));
1050 if (NS_FAILED(rv)) {
1051 NS_WARNING("Can't create cache IO thread");
1054 // initialize hashtable for active cache entries
1055 rv = mActiveEntries.Init();
1056 if (NS_FAILED(rv)) return rv;
1058 // create profile/preference observer
1059 mObserver = new nsCacheProfilePrefObserver();
1060 if (!mObserver) return NS_ERROR_OUT_OF_MEMORY;
1061 NS_ADDREF(mObserver);
1063 mObserver->Install();
1064 mEnableDiskDevice = mObserver->DiskCacheEnabled();
1065 mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
1066 mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
1068 mInitialized = PR_TRUE;
1069 return NS_OK;
1073 void
1074 nsCacheService::Shutdown()
1076 nsCOMPtr<nsIThread> cacheIOThread;
1079 nsCacheServiceAutoLock lock;
1080 NS_ASSERTION(mInitialized,
1081 "can't shutdown nsCacheService unless it has been initialized.");
1083 if (mInitialized) {
1085 mInitialized = PR_FALSE;
1087 mObserver->Remove();
1088 NS_RELEASE(mObserver);
1090 // Clear entries
1091 ClearDoomList();
1092 ClearActiveEntries();
1094 // Make sure to wait for any pending cache-operations before
1095 // proceeding with destructive actions (bug #620660)
1096 (void) SyncWithCacheIOThread();
1098 // deallocate memory and disk caches
1099 delete mMemoryDevice;
1100 mMemoryDevice = nsnull;
1102 #ifdef NECKO_DISK_CACHE
1103 delete mDiskDevice;
1104 mDiskDevice = nsnull;
1105 #endif // !NECKO_DISK_CACHE
1107 #ifdef NECKO_OFFLINE_CACHE
1108 NS_IF_RELEASE(mOfflineDevice);
1109 #endif // !NECKO_OFFLINE_CACHE
1111 #if defined(NECKO_DISK_CACHE) && defined(PR_LOGGING)
1112 LogCacheStatistics();
1113 #endif
1115 mCacheIOThread.swap(cacheIOThread);
1117 } // lock
1119 if (cacheIOThread)
1120 cacheIOThread->Shutdown();
1124 nsresult
1125 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
1127 nsresult rv;
1129 if (aOuter != nsnull)
1130 return NS_ERROR_NO_AGGREGATION;
1132 nsCacheService * cacheService = new nsCacheService();
1133 if (cacheService == nsnull)
1134 return NS_ERROR_OUT_OF_MEMORY;
1136 NS_ADDREF(cacheService);
1137 rv = cacheService->Init();
1138 if (NS_SUCCEEDED(rv)) {
1139 rv = cacheService->QueryInterface(aIID, aResult);
1141 NS_RELEASE(cacheService);
1142 return rv;
1146 NS_IMETHODIMP
1147 nsCacheService::CreateSession(const char * clientID,
1148 nsCacheStoragePolicy storagePolicy,
1149 PRBool streamBased,
1150 nsICacheSession **result)
1152 *result = nsnull;
1154 if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
1156 nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
1157 if (!session) return NS_ERROR_OUT_OF_MEMORY;
1159 NS_ADDREF(*result = session);
1161 return NS_OK;
1165 nsresult
1166 nsCacheService::EvictEntriesForSession(nsCacheSession * session)
1168 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1169 return gService->EvictEntriesForClient(session->ClientID()->get(),
1170 session->StoragePolicy());
1174 nsresult
1175 nsCacheService::EvictEntriesForClient(const char * clientID,
1176 nsCacheStoragePolicy storagePolicy)
1178 if (this == nsnull) return NS_ERROR_NOT_AVAILABLE; // XXX eh?
1180 nsCOMPtr<nsIObserverService> obsSvc =
1181 mozilla::services::GetObserverService();
1182 if (obsSvc) {
1183 // Proxy to the UI thread since the observer service isn't thredsafe.
1184 // We use an async proxy, since this it's not important whether this
1185 // notification happens before or after the actual eviction.
1187 nsCOMPtr<nsIObserverService> obsProxy;
1188 NS_GetProxyForObject(NS_PROXY_TO_MAIN_THREAD,
1189 NS_GET_IID(nsIObserverService), obsSvc,
1190 NS_PROXY_ASYNC, getter_AddRefs(obsProxy));
1192 if (obsProxy) {
1193 obsProxy->NotifyObservers(this,
1194 NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
1195 nsnull);
1199 nsCacheServiceAutoLock lock;
1200 nsresult res = NS_OK;
1202 #ifdef NECKO_DISK_CACHE
1203 if (storagePolicy == nsICache::STORE_ANYWHERE ||
1204 storagePolicy == nsICache::STORE_ON_DISK) {
1206 if (mEnableDiskDevice) {
1207 nsresult rv;
1208 if (!mDiskDevice)
1209 rv = CreateDiskDevice();
1210 if (mDiskDevice)
1211 rv = mDiskDevice->EvictEntries(clientID);
1212 if (NS_FAILED(rv)) res = rv;
1215 #endif // ! NECKO_DISK_CACHE
1217 #ifdef NECKO_OFFLINE_CACHE
1218 // Only clear the offline cache if it has been specifically asked for.
1219 if (storagePolicy == nsICache::STORE_OFFLINE) {
1220 if (mEnableOfflineDevice) {
1221 nsresult rv;
1222 if (!mOfflineDevice)
1223 rv = CreateOfflineDevice();
1224 if (mOfflineDevice)
1225 rv = mOfflineDevice->EvictEntries(clientID);
1226 if (NS_FAILED(rv)) res = rv;
1229 #endif // ! NECKO_OFFLINE_CACHE
1231 if (storagePolicy == nsICache::STORE_ANYWHERE ||
1232 storagePolicy == nsICache::STORE_IN_MEMORY) {
1234 // If there is no memory device, there is no need to evict it...
1235 if (mMemoryDevice) {
1236 nsresult rv;
1237 rv = mMemoryDevice->EvictEntries(clientID);
1238 if (NS_FAILED(rv)) res = rv;
1242 return res;
1246 nsresult
1247 nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
1248 PRBool * result)
1250 if (gService == nsnull) return NS_ERROR_NOT_AVAILABLE;
1251 nsCacheServiceAutoLock lock;
1253 *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
1254 return NS_OK;
1258 PRBool
1259 nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
1261 if (gService->mEnableMemoryDevice &&
1262 (storagePolicy == nsICache::STORE_ANYWHERE ||
1263 storagePolicy == nsICache::STORE_IN_MEMORY)) {
1264 return PR_TRUE;
1266 if (gService->mEnableDiskDevice &&
1267 (storagePolicy == nsICache::STORE_ANYWHERE ||
1268 storagePolicy == nsICache::STORE_ON_DISK ||
1269 storagePolicy == nsICache::STORE_ON_DISK_AS_FILE)) {
1270 return PR_TRUE;
1272 if (gService->mEnableOfflineDevice &&
1273 storagePolicy == nsICache::STORE_OFFLINE) {
1274 return PR_TRUE;
1277 return PR_FALSE;
1280 NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
1282 NS_ENSURE_ARG_POINTER(visitor);
1284 nsCacheServiceAutoLock lock;
1286 if (!(mEnableDiskDevice || mEnableMemoryDevice))
1287 return NS_ERROR_NOT_AVAILABLE;
1289 // XXX record the fact that a visitation is in progress,
1290 // XXX i.e. keep list of visitors in progress.
1292 nsresult rv = NS_OK;
1293 // If there is no memory device, there are then also no entries to visit...
1294 if (mMemoryDevice) {
1295 rv = mMemoryDevice->Visit(visitor);
1296 if (NS_FAILED(rv)) return rv;
1299 #ifdef NECKO_DISK_CACHE
1300 if (mEnableDiskDevice) {
1301 if (!mDiskDevice) {
1302 rv = CreateDiskDevice();
1303 if (NS_FAILED(rv)) return rv;
1305 rv = mDiskDevice->Visit(visitor);
1306 if (NS_FAILED(rv)) return rv;
1308 #endif // !NECKO_DISK_CACHE
1310 #ifdef NECKO_OFFLINE_CACHE
1311 if (mEnableOfflineDevice) {
1312 if (!mOfflineDevice) {
1313 rv = CreateOfflineDevice();
1314 if (NS_FAILED(rv)) return rv;
1316 rv = mOfflineDevice->Visit(visitor);
1317 if (NS_FAILED(rv)) return rv;
1319 #endif // !NECKO_OFFLINE_CACHE
1321 // XXX notify any shutdown process that visitation is complete for THIS visitor.
1322 // XXX keep queue of visitors
1324 return NS_OK;
1328 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
1330 return EvictEntriesForClient(nsnull, storagePolicy);
1333 NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
1335 nsCacheServiceAutoLock lock;
1337 if (!mCacheIOThread)
1338 return NS_ERROR_NOT_AVAILABLE;
1340 NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
1341 return NS_OK;
1345 * Internal Methods
1347 nsresult
1348 nsCacheService::CreateDiskDevice()
1350 #ifdef NECKO_DISK_CACHE
1351 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1352 if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
1353 if (mDiskDevice) return NS_OK;
1355 mDiskDevice = new nsDiskCacheDevice;
1356 if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
1358 // set the preferences
1359 mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
1360 mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
1362 nsresult rv = mDiskDevice->Init();
1363 if (NS_FAILED(rv)) {
1364 #if DEBUG
1365 printf("###\n");
1366 printf("### mDiskDevice->Init() failed (0x%.8x)\n", rv);
1367 printf("### - disabling disk cache for this session.\n");
1368 printf("###\n");
1369 #endif
1370 mEnableDiskDevice = PR_FALSE;
1371 delete mDiskDevice;
1372 mDiskDevice = nsnull;
1374 return rv;
1375 #else // !NECKO_DISK_CACHE
1376 NS_NOTREACHED("nsCacheService::CreateDiskDevice");
1377 return NS_ERROR_NOT_IMPLEMENTED;
1378 #endif
1381 nsresult
1382 nsCacheService::CreateOfflineDevice()
1384 #ifdef NECKO_OFFLINE_CACHE
1385 CACHE_LOG_ALWAYS(("Creating offline device"));
1387 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1388 if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
1389 if (mOfflineDevice) return NS_OK;
1391 mOfflineDevice = new nsOfflineCacheDevice;
1392 if (!mOfflineDevice) return NS_ERROR_OUT_OF_MEMORY;
1394 NS_ADDREF(mOfflineDevice);
1396 // set the preferences
1397 mOfflineDevice->SetCacheParentDirectory(
1398 mObserver->OfflineCacheParentDirectory());
1399 mOfflineDevice->SetCapacity(mObserver->OfflineCacheCapacity());
1401 nsresult rv = mOfflineDevice->Init();
1402 if (NS_FAILED(rv)) {
1403 CACHE_LOG_DEBUG(("mOfflineDevice->Init() failed (0x%.8x)\n", rv));
1404 CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
1406 mEnableOfflineDevice = PR_FALSE;
1407 NS_RELEASE(mOfflineDevice);
1409 return rv;
1410 #else // !NECKO_DISK_CACHE
1411 NS_NOTREACHED("nsCacheService::CreateOfflineDevice");
1412 return NS_ERROR_NOT_IMPLEMENTED;
1413 #endif
1416 nsresult
1417 nsCacheService::CreateMemoryDevice()
1419 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1420 if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
1421 if (mMemoryDevice) return NS_OK;
1423 mMemoryDevice = new nsMemoryCacheDevice;
1424 if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
1426 // set preference
1427 PRInt32 capacity = mObserver->MemoryCacheCapacity();
1428 CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
1429 mMemoryDevice->SetCapacity(capacity);
1431 nsresult rv = mMemoryDevice->Init();
1432 if (NS_FAILED(rv)) {
1433 NS_WARNING("Initialization of Memory Cache failed.");
1434 delete mMemoryDevice;
1435 mMemoryDevice = nsnull;
1437 return rv;
1441 nsresult
1442 nsCacheService::CreateRequest(nsCacheSession * session,
1443 const nsACString & clientKey,
1444 nsCacheAccessMode accessRequested,
1445 PRBool blockingMode,
1446 nsICacheListener * listener,
1447 nsCacheRequest ** request)
1449 NS_ASSERTION(request, "CreateRequest: request is null");
1451 nsCString * key = new nsCString(*session->ClientID());
1452 if (!key)
1453 return NS_ERROR_OUT_OF_MEMORY;
1454 key->Append(':');
1455 key->Append(clientKey);
1457 if (mMaxKeyLength < key->Length()) mMaxKeyLength = key->Length();
1459 // create request
1460 *request = new nsCacheRequest(key, listener, accessRequested, blockingMode, session);
1461 if (!*request) {
1462 delete key;
1463 return NS_ERROR_OUT_OF_MEMORY;
1466 if (!listener) return NS_OK; // we're sync, we're done.
1468 // get the request's thread
1469 (*request)->mThread = do_GetCurrentThread();
1471 return NS_OK;
1475 class nsCacheListenerEvent : public nsRunnable
1477 public:
1478 nsCacheListenerEvent(nsICacheListener *listener,
1479 nsICacheEntryDescriptor *descriptor,
1480 nsCacheAccessMode accessGranted,
1481 nsresult status)
1482 : mListener(listener) // transfers reference
1483 , mDescriptor(descriptor) // transfers reference (may be null)
1484 , mAccessGranted(accessGranted)
1485 , mStatus(status)
1488 NS_IMETHOD Run()
1490 mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
1492 NS_RELEASE(mListener);
1493 NS_IF_RELEASE(mDescriptor);
1494 return NS_OK;
1497 private:
1498 // We explicitly leak mListener or mDescriptor if Run is not called
1499 // because otherwise we cannot guarantee that they are destroyed on
1500 // the right thread.
1502 nsICacheListener *mListener;
1503 nsICacheEntryDescriptor *mDescriptor;
1504 nsCacheAccessMode mAccessGranted;
1505 nsresult mStatus;
1509 nsresult
1510 nsCacheService::NotifyListener(nsCacheRequest * request,
1511 nsICacheEntryDescriptor * descriptor,
1512 nsCacheAccessMode accessGranted,
1513 nsresult status)
1515 NS_ASSERTION(request->mThread, "no thread set in async request!");
1517 // Swap ownership, and release listener on target thread...
1518 nsICacheListener *listener = request->mListener;
1519 request->mListener = nsnull;
1521 nsCOMPtr<nsIRunnable> ev =
1522 new nsCacheListenerEvent(listener, descriptor,
1523 accessGranted, status);
1524 if (!ev) {
1525 // Better to leak listener and descriptor if we fail because we don't
1526 // want to destroy them inside the cache service lock or on potentially
1527 // the wrong thread.
1528 return NS_ERROR_OUT_OF_MEMORY;
1531 return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1535 nsresult
1536 nsCacheService::ProcessRequest(nsCacheRequest * request,
1537 PRBool calledFromOpenCacheEntry,
1538 nsICacheEntryDescriptor ** result)
1540 // !!! must be called with mLock held !!!
1541 nsresult rv;
1542 nsCacheEntry * entry = nsnull;
1543 nsCacheEntry * doomedEntry = nsnull;
1544 nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1545 if (result) *result = nsnull;
1547 while(1) { // Activate entry loop
1548 rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
1549 if (NS_FAILED(rv)) break;
1551 while(1) { // Request Access loop
1552 NS_ASSERTION(entry, "no entry in Request Access loop!");
1553 // entry->RequestAccess queues request on entry
1554 rv = entry->RequestAccess(request, &accessGranted);
1555 if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
1557 if (request->mListener) // async exits - validate, doom, or close will resume
1558 return rv;
1560 if (request->IsBlocking()) {
1561 // XXX this is probably wrong...
1562 Unlock();
1563 rv = request->WaitForValidation();
1564 Lock();
1567 PR_REMOVE_AND_INIT_LINK(request);
1568 if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
1569 // okay, we're ready to process this request, request access again
1571 if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
1573 if (entry->IsNotInUse()) {
1574 // this request was the last one keeping it around, so get rid of it
1575 DeactivateEntry(entry);
1577 // loop back around to look for another entry
1580 nsICacheEntryDescriptor *descriptor = nsnull;
1582 if (NS_SUCCEEDED(rv))
1583 rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
1585 // If doomedEntry is set, ActivatEntry() doomed an existing entry and
1586 // created a new one for that cache-key. However, any pending requests
1587 // on the doomed entry were not processed and we need to do that here.
1588 // This must be done after adding the created entry to list of active
1589 // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
1590 // (see bug ##561313). It is also important to do this after creating a
1591 // descriptor for this request, or some other request may end up being
1592 // executed first for the newly created entry.
1593 // Finally, it is worth to emphasize that if doomedEntry is set,
1594 // ActivateEntry() created a new entry for the request, which will be
1595 // initialized by RequestAccess() and they both should have returned NS_OK.
1596 if (doomedEntry) {
1597 (void) ProcessPendingRequests(doomedEntry);
1598 if (doomedEntry->IsNotInUse())
1599 DeactivateEntry(doomedEntry);
1600 doomedEntry = nsnull;
1603 if (request->mListener) { // Asynchronous
1605 if (NS_FAILED(rv) && calledFromOpenCacheEntry)
1606 return rv; // skip notifying listener, just return rv to caller
1608 // call listener to report error or descriptor
1609 nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
1610 if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
1611 rv = rv2; // trigger delete request
1613 } else { // Synchronous
1614 *result = descriptor;
1616 return rv;
1620 nsresult
1621 nsCacheService::OpenCacheEntry(nsCacheSession * session,
1622 const nsACString & key,
1623 nsCacheAccessMode accessRequested,
1624 PRBool blockingMode,
1625 nsICacheListener * listener,
1626 nsICacheEntryDescriptor ** result)
1628 CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
1629 session, PromiseFlatCString(key).get(), accessRequested,
1630 blockingMode));
1631 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1632 if (result)
1633 *result = nsnull;
1635 if (!gService->mInitialized)
1636 return NS_ERROR_NOT_INITIALIZED;
1638 nsCacheRequest * request = nsnull;
1640 nsCacheServiceAutoLock lock;
1641 nsresult rv = gService->CreateRequest(session,
1642 key,
1643 accessRequested,
1644 blockingMode,
1645 listener,
1646 &request);
1647 if (NS_FAILED(rv)) return rv;
1649 CACHE_LOG_DEBUG(("Created request %p\n", request));
1651 // Process the request on the background thread if we are on the main thread
1652 // and the the request is asynchronous
1653 if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
1654 nsCOMPtr<nsIRunnable> ev =
1655 new nsProcessRequestEvent(request);
1656 if (ev) {
1657 rv = gService->mCacheIOThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1658 } else {
1659 rv = NS_ERROR_OUT_OF_MEMORY;
1662 // delete request if we didn't post the event
1663 if (NS_FAILED(rv))
1664 delete request;
1666 else {
1667 rv = gService->ProcessRequest(request, PR_TRUE, result);
1669 // delete requests that have completed
1670 if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
1671 delete request;
1674 return rv;
1678 nsresult
1679 nsCacheService::ActivateEntry(nsCacheRequest * request,
1680 nsCacheEntry ** result,
1681 nsCacheEntry ** doomedEntry)
1683 CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
1685 nsresult rv = NS_OK;
1687 NS_ASSERTION(request != nsnull, "ActivateEntry called with no request");
1688 if (result) *result = nsnull;
1689 if (doomedEntry) *doomedEntry = nsnull;
1690 if ((!request) || (!result) || (!doomedEntry))
1691 return NS_ERROR_NULL_POINTER;
1693 // check if the request can be satisfied
1694 if (!mEnableMemoryDevice && !request->IsStreamBased())
1695 return NS_ERROR_FAILURE;
1696 if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
1697 return NS_ERROR_FAILURE;
1699 // search active entries (including those not bound to device)
1700 nsCacheEntry *entry = mActiveEntries.GetEntry(request->mKey);
1701 CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
1703 if (!entry) {
1704 // search cache devices for entry
1705 PRBool collision = PR_FALSE;
1706 entry = SearchCacheDevices(request->mKey, request->StoragePolicy(), &collision);
1707 CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
1708 request, entry));
1709 // When there is a hashkey collision just refuse to cache it...
1710 if (collision) return NS_ERROR_CACHE_IN_USE;
1712 if (entry) entry->MarkInitialized();
1713 } else {
1714 NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
1717 if (entry) {
1718 ++mCacheHits;
1719 entry->Fetched();
1720 } else {
1721 ++mCacheMisses;
1724 if (entry &&
1725 ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
1726 ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
1727 (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
1728 request->WillDoomEntriesIfExpired()))))
1731 // this is FORCE-WRITE request or the entry has expired
1732 // we doom entry without processing pending requests, but store it in
1733 // doomedEntry which causes pending requests to be processed below
1734 rv = DoomEntry_Internal(entry, false);
1735 *doomedEntry = entry;
1736 if (NS_FAILED(rv)) {
1737 // XXX what to do? Increment FailedDooms counter?
1739 entry = nsnull;
1742 if (!entry) {
1743 if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
1744 // this is a READ-ONLY request
1745 rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
1746 goto error;
1749 entry = new nsCacheEntry(request->mKey,
1750 request->IsStreamBased(),
1751 request->StoragePolicy());
1752 if (!entry)
1753 return NS_ERROR_OUT_OF_MEMORY;
1755 entry->Fetched();
1756 ++mTotalEntries;
1758 // XXX we could perform an early bind in some cases based on storage policy
1761 if (!entry->IsActive()) {
1762 rv = mActiveEntries.AddEntry(entry);
1763 if (NS_FAILED(rv)) goto error;
1764 CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
1765 entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
1767 *result = entry;
1768 return NS_OK;
1770 error:
1771 *result = nsnull;
1772 if (entry) {
1773 delete entry;
1775 return rv;
1779 nsCacheEntry *
1780 nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, PRBool *collision)
1782 nsCacheEntry * entry = nsnull;
1784 CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
1786 *collision = PR_FALSE;
1787 if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
1788 // If there is no memory device, then there is nothing to search...
1789 if (mMemoryDevice) {
1790 entry = mMemoryDevice->FindEntry(key, collision);
1791 CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
1792 "collision: %d\n", key->get(), entry, collision));
1796 if (!entry &&
1797 ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
1799 #ifdef NECKO_DISK_CACHE
1800 if (mEnableDiskDevice) {
1801 if (!mDiskDevice) {
1802 nsresult rv = CreateDiskDevice();
1803 if (NS_FAILED(rv))
1804 return nsnull;
1807 entry = mDiskDevice->FindEntry(key, collision);
1809 #endif // !NECKO_DISK_CACHE
1812 if (!entry && (policy == nsICache::STORE_OFFLINE ||
1813 (policy == nsICache::STORE_ANYWHERE &&
1814 gIOService->IsOffline()))) {
1816 #ifdef NECKO_OFFLINE_CACHE
1817 if (mEnableOfflineDevice) {
1818 if (!mOfflineDevice) {
1819 nsresult rv = CreateOfflineDevice();
1820 if (NS_FAILED(rv))
1821 return nsnull;
1824 entry = mOfflineDevice->FindEntry(key, collision);
1826 #endif // !NECKO_OFFLINE_CACHE
1829 return entry;
1833 nsCacheDevice *
1834 nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
1836 nsCacheDevice * device = entry->CacheDevice();
1837 // return device if found, possibly null if the entry is doomed i.e prevent
1838 // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
1839 if (device || entry->IsDoomed()) return device;
1841 PRInt64 predictedDataSize = entry->PredictedDataSize();
1842 #ifdef NECKO_DISK_CACHE
1843 if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
1844 // this is the default
1845 if (!mDiskDevice) {
1846 (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
1849 if (mDiskDevice) {
1850 // Bypass the cache if Content-Length says the entry will be too big
1851 if (predictedDataSize != -1 &&
1852 mDiskDevice->EntryIsTooBig(predictedDataSize)) {
1853 nsresult rv = nsCacheService::DoomEntry(entry);
1854 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
1855 return nsnull;
1858 entry->MarkBinding(); // enter state of binding
1859 nsresult rv = mDiskDevice->BindEntry(entry);
1860 entry->ClearBinding(); // exit state of binding
1861 if (NS_SUCCEEDED(rv))
1862 device = mDiskDevice;
1865 #endif // !NECKO_DISK_CACHE
1867 // if we can't use mDiskDevice, try mMemoryDevice
1868 if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
1869 if (!mMemoryDevice) {
1870 (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
1872 if (mMemoryDevice) {
1873 // Bypass the cache if Content-Length says entry will be too big
1874 if (predictedDataSize != -1 &&
1875 mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
1876 nsresult rv = nsCacheService::DoomEntry(entry);
1877 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
1878 return nsnull;
1881 entry->MarkBinding(); // enter state of binding
1882 nsresult rv = mMemoryDevice->BindEntry(entry);
1883 entry->ClearBinding(); // exit state of binding
1884 if (NS_SUCCEEDED(rv))
1885 device = mMemoryDevice;
1889 #ifdef NECKO_OFFLINE_CACHE
1890 if (!device && entry->IsStreamData() &&
1891 entry->IsAllowedOffline() && mEnableOfflineDevice) {
1892 if (!mOfflineDevice) {
1893 (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
1896 if (mOfflineDevice) {
1897 entry->MarkBinding();
1898 nsresult rv = mOfflineDevice->BindEntry(entry);
1899 entry->ClearBinding();
1900 if (NS_SUCCEEDED(rv))
1901 device = mOfflineDevice;
1904 #endif // ! NECKO_OFFLINE_CACHE
1906 if (device)
1907 entry->SetCacheDevice(device);
1908 return device;
1912 nsresult
1913 nsCacheService::DoomEntry(nsCacheEntry * entry)
1915 return gService->DoomEntry_Internal(entry, true);
1919 nsresult
1920 nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
1921 PRBool doProcessPendingRequests)
1923 if (entry->IsDoomed()) return NS_OK;
1925 CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
1926 nsresult rv = NS_OK;
1927 entry->MarkDoomed();
1929 NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
1930 nsCacheDevice * device = entry->CacheDevice();
1931 if (device) device->DoomEntry(entry);
1933 if (entry->IsActive()) {
1934 // remove from active entries
1935 mActiveEntries.RemoveEntry(entry);
1936 CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
1937 entry->MarkInactive();
1940 // put on doom list to wait for descriptors to close
1941 NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
1942 PR_APPEND_LINK(entry, &mDoomedEntries);
1944 // handle pending requests only if we're supposed to
1945 if (doProcessPendingRequests) {
1946 // tell pending requests to get on with their lives...
1947 rv = ProcessPendingRequests(entry);
1949 // All requests have been removed, but there may still be open descriptors
1950 if (entry->IsNotInUse()) {
1951 DeactivateEntry(entry); // tell device to get rid of it
1954 return rv;
1958 void
1959 nsCacheService::OnProfileShutdown(PRBool cleanse)
1961 if (!gService) return;
1962 if (!gService->mInitialized) {
1963 // The cache service has been shut down, but someone is still holding
1964 // a reference to it. Ignore this call.
1965 return;
1967 nsCacheServiceAutoLock lock;
1969 gService->DoomActiveEntries();
1970 gService->ClearDoomList();
1972 // Make sure to wait for any pending cache-operations before
1973 // proceeding with destructive actions (bug #620660)
1974 (void) SyncWithCacheIOThread();
1976 #ifdef NECKO_DISK_CACHE
1977 if (gService->mDiskDevice && gService->mEnableDiskDevice) {
1978 if (cleanse)
1979 gService->mDiskDevice->EvictEntries(nsnull);
1981 gService->mDiskDevice->Shutdown();
1983 gService->mEnableDiskDevice = PR_FALSE;
1984 #endif // !NECKO_DISK_CACHE
1986 #ifdef NECKO_OFFLINE_CACHE
1987 if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
1988 if (cleanse)
1989 gService->mOfflineDevice->EvictEntries(nsnull);
1991 gService->mOfflineDevice->Shutdown();
1993 gService->mEnableOfflineDevice = PR_FALSE;
1994 #endif // !NECKO_OFFLINE_CACHE
1996 if (gService->mMemoryDevice) {
1997 // clear memory cache
1998 gService->mMemoryDevice->EvictEntries(nsnull);
2004 void
2005 nsCacheService::OnProfileChanged()
2007 if (!gService) return;
2009 CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
2011 nsCacheServiceAutoLock lock;
2013 gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2014 gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2015 gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2017 #ifdef NECKO_DISK_CACHE
2018 if (gService->mDiskDevice) {
2019 gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
2020 gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
2022 // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
2023 nsresult rv = gService->mDiskDevice->Init();
2024 if (NS_FAILED(rv)) {
2025 NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
2026 gService->mEnableDiskDevice = PR_FALSE;
2027 // XXX delete mDiskDevice?
2030 #endif // !NECKO_DISK_CACHE
2032 #ifdef NECKO_OFFLINE_CACHE
2033 if (gService->mOfflineDevice) {
2034 gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
2035 gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
2037 // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
2038 nsresult rv = gService->mOfflineDevice->Init();
2039 if (NS_FAILED(rv)) {
2040 NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
2041 gService->mEnableOfflineDevice = PR_FALSE;
2042 // XXX delete mOfflineDevice?
2045 #endif // !NECKO_OFFLINE_CACHE
2047 // If memoryDevice exists, reset its size to the new profile
2048 if (gService->mMemoryDevice) {
2049 if (gService->mEnableMemoryDevice) {
2050 // make sure that capacity is reset to the right value
2051 PRInt32 capacity = gService->mObserver->MemoryCacheCapacity();
2052 CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2053 capacity));
2054 gService->mMemoryDevice->SetCapacity(capacity);
2055 } else {
2056 // tell memory device to evict everything
2057 CACHE_LOG_DEBUG(("memory device disabled\n"));
2058 gService->mMemoryDevice->SetCapacity(0);
2059 // Don't delete memory device, because some entries may be active still...
2065 void
2066 nsCacheService::SetDiskCacheEnabled(PRBool enabled)
2068 if (!gService) return;
2069 nsCacheServiceAutoLock lock;
2070 gService->mEnableDiskDevice = enabled;
2074 void
2075 nsCacheService::SetDiskCacheCapacity(PRInt32 capacity)
2077 if (!gService) return;
2078 nsCacheServiceAutoLock lock;
2080 #ifdef NECKO_DISK_CACHE
2081 if (gService->mDiskDevice) {
2082 gService->mDiskDevice->SetCapacity(capacity);
2084 #endif // !NECKO_DISK_CACHE
2086 if (gService->mObserver)
2087 gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2090 void
2091 nsCacheService::SetOfflineCacheEnabled(PRBool enabled)
2093 if (!gService) return;
2094 nsCacheServiceAutoLock lock;
2095 gService->mEnableOfflineDevice = enabled;
2098 void
2099 nsCacheService::SetOfflineCacheCapacity(PRInt32 capacity)
2101 if (!gService) return;
2102 nsCacheServiceAutoLock lock;
2104 #ifdef NECKO_OFFLINE_CACHE
2105 if (gService->mOfflineDevice) {
2106 gService->mOfflineDevice->SetCapacity(capacity);
2108 #endif // !NECKO_OFFLINE_CACHE
2110 gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2114 void
2115 nsCacheService::SetMemoryCache()
2117 if (!gService) return;
2119 CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
2121 nsCacheServiceAutoLock lock;
2123 gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2125 if (gService->mEnableMemoryDevice) {
2126 if (gService->mMemoryDevice) {
2127 PRInt32 capacity = gService->mObserver->MemoryCacheCapacity();
2128 // make sure that capacity is reset to the right value
2129 CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2130 capacity));
2131 gService->mMemoryDevice->SetCapacity(capacity);
2133 } else {
2134 if (gService->mMemoryDevice) {
2135 // tell memory device to evict everything
2136 CACHE_LOG_DEBUG(("memory device disabled\n"));
2137 gService->mMemoryDevice->SetCapacity(0);
2138 // Don't delete memory device, because some entries may be active still...
2144 /******************************************************************************
2145 * static methods for nsCacheEntryDescriptor
2146 *****************************************************************************/
2147 #ifdef XP_MAC
2148 #pragma mark -
2149 #endif
2151 void
2152 nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
2154 // ask entry to remove descriptor
2155 nsCacheEntry * entry = descriptor->CacheEntry();
2156 PRBool stillActive = entry->RemoveDescriptor(descriptor);
2157 nsresult rv = NS_OK;
2159 if (!entry->IsValid()) {
2160 rv = gService->ProcessPendingRequests(entry);
2163 if (!stillActive) {
2164 gService->DeactivateEntry(entry);
2169 nsresult
2170 nsCacheService::GetFileForEntry(nsCacheEntry * entry,
2171 nsIFile ** result)
2173 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2174 if (!device) return NS_ERROR_UNEXPECTED;
2176 return device->GetFileForEntry(entry, result);
2180 nsresult
2181 nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
2182 nsCacheAccessMode mode,
2183 PRUint32 offset,
2184 nsIInputStream ** result)
2186 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2187 if (!device) return NS_ERROR_UNEXPECTED;
2189 return device->OpenInputStreamForEntry(entry, mode, offset, result);
2192 nsresult
2193 nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
2194 nsCacheAccessMode mode,
2195 PRUint32 offset,
2196 nsIOutputStream ** result)
2198 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2199 if (!device) return NS_ERROR_UNEXPECTED;
2201 return device->OpenOutputStreamForEntry(entry, mode, offset, result);
2205 nsresult
2206 nsCacheService::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
2208 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2209 if (!device) return NS_ERROR_UNEXPECTED;
2211 return device->OnDataSizeChange(entry, deltaSize);
2214 void
2215 nsCacheService::Lock()
2217 PR_Lock(gService->mLock);
2219 #if defined(DEBUG)
2220 gService->mLockedThread = PR_GetCurrentThread();
2221 #endif
2224 void
2225 nsCacheService::Unlock()
2227 NS_ASSERTION(gService->mLockedThread == PR_GetCurrentThread(), "oops");
2229 nsTArray<nsISupports*> doomed;
2230 doomed.SwapElements(gService->mDoomedObjects);
2232 #if defined(DEBUG)
2233 gService->mLockedThread = nsnull;
2234 #endif
2235 PR_Unlock(gService->mLock);
2237 for (PRUint32 i = 0; i < doomed.Length(); ++i)
2238 doomed[i]->Release();
2241 void
2242 nsCacheService::ReleaseObject_Locked(nsISupports * obj,
2243 nsIEventTarget * target)
2245 NS_ASSERTION(gService->mLockedThread == PR_GetCurrentThread(), "oops");
2247 PRBool isCur;
2248 if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
2249 gService->mDoomedObjects.AppendElement(obj);
2250 } else {
2251 NS_ProxyRelease(target, obj);
2256 nsresult
2257 nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
2259 entry->SetData(element);
2260 entry->TouchData();
2261 return NS_OK;
2265 nsresult
2266 nsCacheService::ValidateEntry(nsCacheEntry * entry)
2268 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2269 if (!device) return NS_ERROR_UNEXPECTED;
2271 entry->MarkValid();
2272 nsresult rv = gService->ProcessPendingRequests(entry);
2273 NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
2274 // XXX what else should be done?
2276 return rv;
2279 #ifdef XP_MAC
2280 #pragma mark -
2281 #endif
2284 void
2285 nsCacheService::DeactivateEntry(nsCacheEntry * entry)
2287 CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
2288 nsresult rv = NS_OK;
2289 NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
2290 nsCacheDevice * device = nsnull;
2292 if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
2293 if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
2295 if (entry->IsDoomed()) {
2296 // remove from Doomed list
2297 PR_REMOVE_AND_INIT_LINK(entry);
2298 } else if (entry->IsActive()) {
2299 // remove from active entries
2300 mActiveEntries.RemoveEntry(entry);
2301 CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
2302 entry));
2303 entry->MarkInactive();
2305 // bind entry if necessary to store meta-data
2306 device = EnsureEntryHasDevice(entry);
2307 if (!device) {
2308 CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
2309 "entry %p\n",
2310 entry));
2311 NS_WARNING("DeactivateEntry: unable to bind active entry\n");
2312 return;
2314 } else {
2315 // if mInitialized == PR_FALSE,
2316 // then we're shutting down and this state is okay.
2317 NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
2320 device = entry->CacheDevice();
2321 if (device) {
2322 rv = device->DeactivateEntry(entry);
2323 if (NS_FAILED(rv)) {
2324 // increment deactivate failure count
2325 ++mDeactivateFailures;
2327 } else {
2328 // increment deactivating unbound entry statistic
2329 ++mDeactivatedUnboundEntries;
2330 delete entry; // because no one else will
2335 nsresult
2336 nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
2338 nsresult rv = NS_OK;
2339 nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2340 nsCacheRequest * nextRequest;
2341 PRBool newWriter = PR_FALSE;
2343 CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
2344 (entry->IsInitialized()?"" : "Un"),
2345 (entry->IsDoomed()?"DOOMED" : ""),
2346 (entry->IsValid()? "V":"Inv"), entry));
2348 if (request == &entry->mRequestQ) return NS_OK; // no queued requests
2350 if (!entry->IsDoomed() && entry->IsInvalid()) {
2351 // 1st descriptor closed w/o MarkValid()
2352 NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
2354 #if DEBUG
2355 // verify no ACCESS_WRITE requests(shouldn't have any of these)
2356 while (request != &entry->mRequestQ) {
2357 NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
2358 "ACCESS_WRITE request should have been given a new entry");
2359 request = (nsCacheRequest *)PR_NEXT_LINK(request);
2361 request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2362 #endif
2363 // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
2364 while (request != &entry->mRequestQ) {
2365 if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
2366 newWriter = PR_TRUE;
2367 CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
2368 break;
2371 request = (nsCacheRequest *)PR_NEXT_LINK(request);
2374 if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
2375 request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2377 // XXX what should we do if there are only READ requests in queue?
2378 // XXX serialize their accesses, give them only read access, but force them to check validate flag?
2379 // XXX or do readers simply presume the entry is valid
2380 // See fix for bug #467392 below
2383 nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
2385 while (request != &entry->mRequestQ) {
2386 nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
2387 CACHE_LOG_DEBUG((" %sync request %p for %p\n",
2388 (request->mListener?"As":"S"), request, entry));
2390 if (request->mListener) {
2392 // Async request
2393 PR_REMOVE_AND_INIT_LINK(request);
2395 if (entry->IsDoomed()) {
2396 rv = ProcessRequest(request, PR_FALSE, nsnull);
2397 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
2398 rv = NS_OK;
2399 else
2400 delete request;
2402 if (NS_FAILED(rv)) {
2403 // XXX what to do?
2405 } else if (entry->IsValid() || newWriter) {
2406 rv = entry->RequestAccess(request, &accessGranted);
2407 NS_ASSERTION(NS_SUCCEEDED(rv),
2408 "if entry is valid, RequestAccess must succeed.");
2409 // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
2411 // entry->CreateDescriptor dequeues request, and queues descriptor
2412 nsICacheEntryDescriptor *descriptor = nsnull;
2413 rv = entry->CreateDescriptor(request,
2414 accessGranted,
2415 &descriptor);
2417 // post call to listener to report error or descriptor
2418 rv = NotifyListener(request, descriptor, accessGranted, rv);
2419 delete request;
2420 if (NS_FAILED(rv)) {
2421 // XXX what to do?
2424 } else {
2425 // read-only request to an invalid entry - need to wait for
2426 // the entry to become valid so we post an event to process
2427 // the request again later (bug #467392)
2428 nsCOMPtr<nsIRunnable> ev =
2429 new nsProcessRequestEvent(request);
2430 rv = DispatchToCacheIOThread(ev);
2431 if (NS_FAILED(rv)) {
2432 delete request; // avoid leak
2435 } else {
2437 // Synchronous request
2438 request->WakeUp();
2440 if (newWriter) break; // process remaining requests after validation
2441 request = nextRequest;
2444 return NS_OK;
2448 void
2449 nsCacheService::ClearPendingRequests(nsCacheEntry * entry)
2451 nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2453 while (request != &entry->mRequestQ) {
2454 nsCacheRequest * next = (nsCacheRequest *)PR_NEXT_LINK(request);
2456 // XXX we're just dropping these on the floor for now...definitely wrong.
2457 PR_REMOVE_AND_INIT_LINK(request);
2458 delete request;
2459 request = next;
2464 void
2465 nsCacheService::ClearDoomList()
2467 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2469 while (entry != &mDoomedEntries) {
2470 nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2472 entry->DetachDescriptors();
2473 DeactivateEntry(entry);
2474 entry = next;
2479 void
2480 nsCacheService::ClearActiveEntries()
2482 mActiveEntries.VisitEntries(DeactivateAndClearEntry, nsnull);
2483 mActiveEntries.Shutdown();
2487 PLDHashOperator
2488 nsCacheService::DeactivateAndClearEntry(PLDHashTable * table,
2489 PLDHashEntryHdr * hdr,
2490 PRUint32 number,
2491 void * arg)
2493 nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
2494 NS_ASSERTION(entry, "### active entry = nsnull!");
2495 // only called from Shutdown() so we don't worry about pending requests
2496 gService->ClearPendingRequests(entry);
2497 entry->DetachDescriptors();
2499 entry->MarkInactive(); // so we don't call Remove() while we're enumerating
2500 gService->DeactivateEntry(entry);
2502 return PL_DHASH_REMOVE; // and continue enumerating
2506 void
2507 nsCacheService::DoomActiveEntries()
2509 nsAutoTArray<nsCacheEntry*, 8> array;
2511 mActiveEntries.VisitEntries(RemoveActiveEntry, &array);
2513 PRUint32 count = array.Length();
2514 for (PRUint32 i=0; i < count; ++i)
2515 DoomEntry_Internal(array[i], true);
2519 PLDHashOperator
2520 nsCacheService::RemoveActiveEntry(PLDHashTable * table,
2521 PLDHashEntryHdr * hdr,
2522 PRUint32 number,
2523 void * arg)
2525 nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
2526 NS_ASSERTION(entry, "### active entry = nsnull!");
2528 nsTArray<nsCacheEntry*> * array = (nsTArray<nsCacheEntry*> *) arg;
2529 NS_ASSERTION(array, "### array = nsnull!");
2530 array->AppendElement(entry);
2532 // entry is being removed from the active entry list
2533 entry->MarkInactive();
2534 return PL_DHASH_REMOVE; // and continue enumerating
2538 #if defined(PR_LOGGING)
2539 void
2540 nsCacheService::LogCacheStatistics()
2542 PRUint32 hitPercentage = (PRUint32)((((double)mCacheHits) /
2543 ((double)(mCacheHits + mCacheMisses))) * 100);
2544 CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
2545 CACHE_LOG_ALWAYS((" TotalEntries = %d\n", mTotalEntries));
2546 CACHE_LOG_ALWAYS((" Cache Hits = %d\n", mCacheHits));
2547 CACHE_LOG_ALWAYS((" Cache Misses = %d\n", mCacheMisses));
2548 CACHE_LOG_ALWAYS((" Cache Hit %% = %d%%\n", hitPercentage));
2549 CACHE_LOG_ALWAYS((" Max Key Length = %d\n", mMaxKeyLength));
2550 CACHE_LOG_ALWAYS((" Max Meta Size = %d\n", mMaxMetaSize));
2551 CACHE_LOG_ALWAYS((" Max Data Size = %d\n", mMaxDataSize));
2552 CACHE_LOG_ALWAYS(("\n"));
2553 CACHE_LOG_ALWAYS((" Deactivate Failures = %d\n",
2554 mDeactivateFailures));
2555 CACHE_LOG_ALWAYS((" Deactivated Unbound Entries = %d\n",
2556 mDeactivatedUnboundEntries));
2558 #endif
2561 void
2562 nsCacheService::OnEnterExitPrivateBrowsing()
2564 if (!gService) return;
2565 nsCacheServiceAutoLock lock;
2567 gService->DoomActiveEntries();
2569 if (gService->mMemoryDevice) {
2570 // clear memory cache
2571 gService->mMemoryDevice->EvictEntries(nsnull);