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
6 : *
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/
11 : *
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.
16 : *
17 : * The Original Code is nsCacheService.cpp, released
18 : * February 10, 2001.
19 : *
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.
24 : *
25 : * Contributor(s):
26 : * Gordon Sheridan, 10-February-2001
27 : * Michael Ventnor <m.ventnor@gmail.com>
28 : * Ehsan Akhgari <ehsan.akhgari@gmail.com>
29 : *
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.
41 : *
42 : * ***** END LICENSE BLOCK ***** */
43 :
44 : #include "mozilla/Util.h"
45 :
46 : #include "necko-config.h"
47 :
48 : #include "nsCache.h"
49 : #include "nsCacheService.h"
50 : #include "nsCacheRequest.h"
51 : #include "nsCacheEntry.h"
52 : #include "nsCacheEntryDescriptor.h"
53 : #include "nsCacheDevice.h"
54 : #include "nsMemoryCacheDevice.h"
55 : #include "nsICacheVisitor.h"
56 : #include "nsDiskCacheDevice.h"
57 : #include "nsDiskCacheDeviceSQL.h"
58 :
59 : #include "nsIMemoryReporter.h"
60 : #include "nsIObserverService.h"
61 : #include "nsIPrefService.h"
62 : #include "nsIPrefBranch.h"
63 : #include "nsILocalFile.h"
64 : #include "nsIOService.h"
65 : #include "nsDirectoryServiceDefs.h"
66 : #include "nsAppDirectoryServiceDefs.h"
67 : #include "nsThreadUtils.h"
68 : #include "nsProxyRelease.h"
69 : #include "nsVoidArray.h"
70 : #include "nsDeleteDir.h"
71 : #include "nsIPrivateBrowsingService.h"
72 : #include "nsNetCID.h"
73 : #include <math.h> // for log()
74 : #include "mozilla/Util.h" // for DebugOnly
75 : #include "mozilla/Services.h"
76 : #include "mozilla/Telemetry.h"
77 : #include "nsITimer.h"
78 :
79 : #include "mozilla/FunctionTimer.h"
80 :
81 : #include "mozilla/net/NeckoCommon.h"
82 :
83 : using namespace mozilla;
84 :
85 : /******************************************************************************
86 : * nsCacheProfilePrefObserver
87 : *****************************************************************************/
88 : #define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
89 : #define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
90 : #define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
91 : "browser.cache.disk.smart_size.first_run"
92 : #define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
93 : "browser.cache.disk.smart_size.enabled"
94 : #define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
95 : #define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
96 : #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
97 : #define DISK_CACHE_CAPACITY 256000
98 :
99 : #define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
100 : #define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
101 : #define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
102 : #define OFFLINE_CACHE_CAPACITY 512000
103 :
104 : #define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
105 : #define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
106 : #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
107 :
108 : #define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
109 : #define CACHE_COMPRESSION_LEVEL 1
110 :
111 : #define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
112 : #define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
113 :
114 : static const char * observerList[] = {
115 : "profile-before-change",
116 : "profile-do-change",
117 : NS_XPCOM_SHUTDOWN_OBSERVER_ID,
118 : NS_PRIVATE_BROWSING_SWITCH_TOPIC
119 : };
120 : static const char * prefList[] = {
121 : DISK_CACHE_ENABLE_PREF,
122 : DISK_CACHE_SMART_SIZE_ENABLED_PREF,
123 : DISK_CACHE_CAPACITY_PREF,
124 : DISK_CACHE_DIR_PREF,
125 : DISK_CACHE_MAX_ENTRY_SIZE_PREF,
126 : OFFLINE_CACHE_ENABLE_PREF,
127 : OFFLINE_CACHE_CAPACITY_PREF,
128 : OFFLINE_CACHE_DIR_PREF,
129 : MEMORY_CACHE_ENABLE_PREF,
130 : MEMORY_CACHE_CAPACITY_PREF,
131 : MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
132 : CACHE_COMPRESSION_LEVEL_PREF,
133 : SANITIZE_ON_SHUTDOWN_PREF,
134 : CLEAR_ON_SHUTDOWN_PREF
135 : };
136 :
137 : // Cache sizes, in KB
138 : const PRInt32 DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
139 : const PRInt32 MIN_CACHE_SIZE = 50 * 1024; // 50 MB
140 : const PRInt32 MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
141 : // Default cache size was 50 MB for many years until FF 4:
142 : const PRInt32 PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
143 :
144 : class nsCacheProfilePrefObserver : public nsIObserver
145 : {
146 : public:
147 : NS_DECL_ISUPPORTS
148 : NS_DECL_NSIOBSERVER
149 :
150 269 : nsCacheProfilePrefObserver()
151 : : mHaveProfile(false)
152 : , mDiskCacheEnabled(false)
153 : , mDiskCacheCapacity(0)
154 : , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
155 : , mSmartSizeEnabled(false)
156 : , mOfflineCacheEnabled(false)
157 : , mOfflineCacheCapacity(0)
158 : , mMemoryCacheEnabled(true)
159 : , mMemoryCacheCapacity(-1)
160 : , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
161 : , mInPrivateBrowsing(false)
162 : , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
163 : , mSanitizeOnShutdown(false)
164 269 : , mClearCacheOnShutdown(false)
165 : {
166 269 : }
167 :
168 1076 : virtual ~nsCacheProfilePrefObserver() {}
169 :
170 : nsresult Install();
171 : void Remove();
172 : nsresult ReadPrefs(nsIPrefBranch* branch);
173 :
174 : bool DiskCacheEnabled();
175 175 : PRInt32 DiskCacheCapacity() { return mDiskCacheCapacity; }
176 : void SetDiskCacheCapacity(PRInt32);
177 175 : PRInt32 DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
178 444 : nsILocalFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
179 0 : bool SmartSizeEnabled() { return mSmartSizeEnabled; }
180 :
181 : bool OfflineCacheEnabled();
182 15 : PRInt32 OfflineCacheCapacity() { return mOfflineCacheCapacity; }
183 15 : nsILocalFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
184 :
185 : bool MemoryCacheEnabled();
186 : PRInt32 MemoryCacheCapacity();
187 90 : PRInt32 MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
188 :
189 : PRInt32 CacheCompressionLevel();
190 :
191 269 : bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
192 :
193 : static PRUint32 GetSmartCacheSize(const nsAString& cachePath,
194 : PRUint32 currentSize);
195 :
196 : private:
197 : bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
198 : bool mHaveProfile;
199 :
200 : bool mDiskCacheEnabled;
201 : PRInt32 mDiskCacheCapacity; // in kilobytes
202 : PRInt32 mDiskCacheMaxEntrySize; // in kilobytes
203 : nsCOMPtr<nsILocalFile> mDiskCacheParentDirectory;
204 : bool mSmartSizeEnabled;
205 :
206 : bool mOfflineCacheEnabled;
207 : PRInt32 mOfflineCacheCapacity; // in kilobytes
208 : nsCOMPtr<nsILocalFile> mOfflineCacheParentDirectory;
209 :
210 : bool mMemoryCacheEnabled;
211 : PRInt32 mMemoryCacheCapacity; // in kilobytes
212 : PRInt32 mMemoryCacheMaxEntrySize; // in kilobytes
213 :
214 : bool mInPrivateBrowsing;
215 :
216 : PRInt32 mCacheCompressionLevel;
217 :
218 : bool mSanitizeOnShutdown;
219 : bool mClearCacheOnShutdown;
220 : };
221 :
222 78595 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver)
223 :
224 : class nsSetDiskSmartSizeCallback : public nsITimerCallback
225 175 : {
226 : public:
227 : NS_DECL_ISUPPORTS
228 :
229 0 : NS_IMETHOD Notify(nsITimer* aTimer) {
230 0 : if (nsCacheService::gService) {
231 0 : nsCacheServiceAutoLock autoLock;
232 0 : nsCacheService::gService->SetDiskSmartSize_Locked();
233 0 : nsCacheService::gService->mSmartSizeTimer = nsnull;
234 : }
235 0 : return NS_OK;
236 : }
237 : };
238 :
239 525 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsSetDiskSmartSizeCallback, nsITimerCallback)
240 :
241 : // Runnable sent to main thread after the cache IO thread calculates available
242 : // disk space, so that there is no race in setting mDiskCacheCapacity.
243 : class nsSetSmartSizeEvent: public nsRunnable
244 0 : {
245 : public:
246 0 : nsSetSmartSizeEvent(PRInt32 smartSize)
247 0 : : mSmartSize(smartSize) {}
248 :
249 0 : NS_IMETHOD Run()
250 : {
251 0 : NS_ASSERTION(NS_IsMainThread(),
252 : "Setting smart size data off the main thread");
253 :
254 : // Main thread may have already called nsCacheService::Shutdown
255 0 : if (!nsCacheService::gService || !nsCacheService::gService->mObserver)
256 0 : return NS_ERROR_NOT_AVAILABLE;
257 :
258 : // Ensure smart sizing wasn't switched off while event was pending.
259 : // It is safe to access the observer without the lock since we are
260 : // on the main thread and the value changes only on the main thread.
261 0 : if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
262 0 : return NS_OK;
263 :
264 0 : nsCacheService::SetDiskCacheCapacity(mSmartSize);
265 :
266 0 : nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
267 0 : if (!ps ||
268 0 : NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
269 0 : NS_WARNING("Failed to set smart size pref");
270 :
271 0 : return NS_OK;
272 : }
273 :
274 : private:
275 : PRInt32 mSmartSize;
276 : };
277 :
278 :
279 : // Runnable sent from main thread to cacheIO thread
280 : class nsGetSmartSizeEvent: public nsRunnable
281 0 : {
282 : public:
283 0 : nsGetSmartSizeEvent(const nsAString& cachePath, PRUint32 currentSize)
284 : : mCachePath(cachePath)
285 0 : , mCurrentSize(currentSize)
286 0 : {}
287 :
288 : // Calculates user's disk space available on a background thread and
289 : // dispatches this value back to the main thread.
290 0 : NS_IMETHOD Run()
291 : {
292 : PRUint32 size;
293 : size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
294 0 : mCurrentSize);
295 0 : NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
296 0 : return NS_OK;
297 : }
298 :
299 : private:
300 : nsString mCachePath;
301 : PRUint32 mCurrentSize;
302 : };
303 :
304 2732 : class nsBlockOnCacheThreadEvent : public nsRunnable {
305 : public:
306 683 : nsBlockOnCacheThreadEvent()
307 683 : {
308 683 : }
309 683 : NS_IMETHOD Run()
310 : {
311 1366 : nsCacheServiceAutoLock autoLock;
312 : #ifdef PR_LOGGING
313 683 : CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
314 : #endif
315 683 : nsCacheService::gService->mCondVar.Notify();
316 683 : return NS_OK;
317 : }
318 : };
319 :
320 :
321 : nsresult
322 269 : nsCacheProfilePrefObserver::Install()
323 : {
324 : // install profile-change observer
325 : nsCOMPtr<nsIObserverService> observerService =
326 538 : mozilla::services::GetObserverService();
327 269 : if (!observerService)
328 0 : return NS_ERROR_FAILURE;
329 :
330 269 : nsresult rv, rv2 = NS_OK;
331 1345 : for (unsigned int i=0; i<ArrayLength(observerList); i++) {
332 1076 : rv = observerService->AddObserver(this, observerList[i], false);
333 1076 : if (NS_FAILED(rv))
334 0 : rv2 = rv;
335 : }
336 :
337 : // install preferences observer
338 538 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
339 269 : if (!branch) return NS_ERROR_FAILURE;
340 :
341 4035 : for (unsigned int i=0; i<ArrayLength(prefList); i++) {
342 3766 : rv = branch->AddObserver(prefList[i], this, false);
343 3766 : if (NS_FAILED(rv))
344 0 : rv2 = rv;
345 : }
346 :
347 : // determine the initial status of the private browsing mode
348 : nsCOMPtr<nsIPrivateBrowsingService> pbs =
349 538 : do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
350 269 : if (pbs)
351 269 : pbs->GetPrivateBrowsingEnabled(&mInPrivateBrowsing);
352 :
353 : // Determine if we have a profile already
354 : // Install() is called *after* the profile-after-change notification
355 : // when there is only a single profile, or it is specified on the
356 : // commandline at startup.
357 : // In that case, we detect the presence of a profile by the existence
358 : // of the NS_APP_USER_PROFILE_50_DIR directory.
359 :
360 538 : nsCOMPtr<nsIFile> directory;
361 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
362 269 : getter_AddRefs(directory));
363 269 : if (NS_SUCCEEDED(rv))
364 176 : mHaveProfile = true;
365 :
366 269 : rv = ReadPrefs(branch);
367 269 : NS_ENSURE_SUCCESS(rv, rv);
368 :
369 269 : return rv2;
370 : }
371 :
372 :
373 : void
374 269 : nsCacheProfilePrefObserver::Remove()
375 : {
376 : // remove Observer Service observers
377 : nsCOMPtr<nsIObserverService> obs =
378 538 : mozilla::services::GetObserverService();
379 269 : if (obs) {
380 1345 : for (unsigned int i=0; i<ArrayLength(observerList); i++) {
381 1076 : obs->RemoveObserver(this, observerList[i]);
382 : }
383 : }
384 :
385 : // remove Pref Service observers
386 : nsCOMPtr<nsIPrefBranch> prefs =
387 538 : do_GetService(NS_PREFSERVICE_CONTRACTID);
388 269 : if (!prefs)
389 : return;
390 4035 : for (unsigned int i=0; i<ArrayLength(prefList); i++)
391 3766 : prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
392 : }
393 :
394 : void
395 0 : nsCacheProfilePrefObserver::SetDiskCacheCapacity(PRInt32 capacity)
396 : {
397 0 : mDiskCacheCapacity = NS_MAX(0, capacity);
398 0 : }
399 :
400 :
401 : NS_IMETHODIMP
402 638 : nsCacheProfilePrefObserver::Observe(nsISupports * subject,
403 : const char * topic,
404 : const PRUnichar * data_unicode)
405 : {
406 : nsresult rv;
407 1276 : NS_ConvertUTF16toUTF8 data(data_unicode);
408 638 : CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get()));
409 :
410 638 : if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
411 : // xpcom going away, shutdown cache service
412 269 : if (nsCacheService::GlobalInstance())
413 269 : nsCacheService::GlobalInstance()->Shutdown();
414 :
415 369 : } else if (!strcmp("profile-before-change", topic)) {
416 : // profile before change
417 178 : mHaveProfile = false;
418 :
419 : // XXX shutdown devices
420 : nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse",
421 178 : data.get()));
422 :
423 191 : } else if (!strcmp("profile-do-change", topic)) {
424 : // profile after change
425 0 : mHaveProfile = true;
426 0 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
427 0 : ReadPrefs(branch);
428 0 : nsCacheService::OnProfileChanged();
429 :
430 191 : } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
431 :
432 : // ignore pref changes until we're done switch profiles
433 185 : if (!mHaveProfile)
434 0 : return NS_OK;
435 :
436 370 : nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
437 185 : if (NS_FAILED(rv))
438 0 : return rv;
439 :
440 : // which preference changed?
441 185 : if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
442 :
443 2 : if (!mInPrivateBrowsing) {
444 2 : rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
445 2 : &mDiskCacheEnabled);
446 2 : if (NS_FAILED(rv))
447 0 : return rv;
448 2 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
449 : }
450 :
451 183 : } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
452 :
453 176 : PRInt32 capacity = 0;
454 176 : rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
455 176 : if (NS_FAILED(rv))
456 0 : return rv;
457 176 : mDiskCacheCapacity = NS_MAX(0, capacity);
458 176 : nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
459 :
460 : // Update the cache capacity when smart sizing is turned on/off
461 7 : } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
462 : // Is the update because smartsizing was turned on, or off?
463 1 : rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
464 1 : &mSmartSizeEnabled);
465 1 : if (NS_FAILED(rv))
466 0 : return rv;
467 1 : PRInt32 newCapacity = 0;
468 1 : if (mSmartSizeEnabled) {
469 0 : nsCacheService::SetDiskSmartSize();
470 : } else {
471 : // Smart sizing switched off: use user specified size
472 1 : rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
473 1 : if (NS_FAILED(rv))
474 0 : return rv;
475 1 : mDiskCacheCapacity = NS_MAX(0, newCapacity);
476 1 : nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
477 : }
478 6 : } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
479 : PRInt32 newMaxSize;
480 2 : rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
481 2 : &newMaxSize);
482 2 : if (NS_FAILED(rv))
483 0 : return rv;
484 :
485 2 : mDiskCacheMaxEntrySize = NS_MAX(-1, newMaxSize);
486 2 : nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
487 :
488 : #if 0
489 : } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
490 : // XXX We probaby don't want to respond to this pref except after
491 : // XXX profile changes. Ideally, there should be somekind of user
492 : // XXX notification that the pref change won't take effect until
493 : // XXX the next time the profile changes (browser launch)
494 : #endif
495 : } else
496 :
497 : // which preference changed?
498 4 : if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
499 :
500 0 : if (!mInPrivateBrowsing) {
501 0 : rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
502 0 : &mOfflineCacheEnabled);
503 0 : if (NS_FAILED(rv)) return rv;
504 0 : nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
505 : }
506 :
507 4 : } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
508 :
509 0 : PRInt32 capacity = 0;
510 0 : rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
511 0 : if (NS_FAILED(rv)) return rv;
512 0 : mOfflineCacheCapacity = NS_MAX(0, capacity);
513 0 : nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
514 : #if 0
515 : } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
516 : // XXX We probaby don't want to respond to this pref except after
517 : // XXX profile changes. Ideally, there should be some kind of user
518 : // XXX notification that the pref change won't take effect until
519 : // XXX the next time the profile changes (browser launch)
520 : #endif
521 : } else
522 :
523 4 : if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
524 :
525 1 : rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
526 1 : &mMemoryCacheEnabled);
527 1 : if (NS_FAILED(rv))
528 0 : return rv;
529 1 : nsCacheService::SetMemoryCache();
530 :
531 3 : } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
532 :
533 1 : mMemoryCacheCapacity = -1;
534 1 : (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
535 1 : &mMemoryCacheCapacity);
536 1 : nsCacheService::SetMemoryCache();
537 2 : } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
538 : PRInt32 newMaxSize;
539 2 : rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
540 2 : &newMaxSize);
541 2 : if (NS_FAILED(rv))
542 0 : return rv;
543 :
544 2 : mMemoryCacheMaxEntrySize = NS_MAX(-1, newMaxSize);
545 2 : nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
546 0 : } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
547 0 : mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
548 0 : (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
549 0 : &mCacheCompressionLevel);
550 0 : mCacheCompressionLevel = NS_MAX(0, mCacheCompressionLevel);
551 0 : mCacheCompressionLevel = NS_MIN(9, mCacheCompressionLevel);
552 0 : } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
553 0 : rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
554 0 : &mSanitizeOnShutdown);
555 0 : if (NS_FAILED(rv))
556 0 : return rv;
557 0 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
558 0 : } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
559 0 : rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
560 0 : &mClearCacheOnShutdown);
561 0 : if (NS_FAILED(rv))
562 0 : return rv;
563 0 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
564 : }
565 6 : } else if (!strcmp(NS_PRIVATE_BROWSING_SWITCH_TOPIC, topic)) {
566 6 : if (!strcmp(NS_PRIVATE_BROWSING_ENTER, data.get())) {
567 3 : mInPrivateBrowsing = true;
568 :
569 3 : nsCacheService::OnEnterExitPrivateBrowsing();
570 :
571 3 : mDiskCacheEnabled = false;
572 3 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
573 :
574 3 : mOfflineCacheEnabled = false;
575 3 : nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
576 3 : } else if (!strcmp(NS_PRIVATE_BROWSING_LEAVE, data.get())) {
577 3 : mInPrivateBrowsing = false;
578 :
579 3 : nsCacheService::OnEnterExitPrivateBrowsing();
580 :
581 6 : nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
582 3 : if (NS_FAILED(rv))
583 0 : return rv;
584 :
585 3 : mDiskCacheEnabled = true; // by default enabled
586 3 : (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
587 3 : &mDiskCacheEnabled);
588 3 : nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
589 :
590 3 : mOfflineCacheEnabled = true; // by default enabled
591 3 : (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
592 3 : &mOfflineCacheEnabled);
593 6 : nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
594 : }
595 : }
596 :
597 638 : return NS_OK;
598 : }
599 :
600 : // Returns default ("smart") size (in KB) of cache, given available disk space
601 : // (also in KB)
602 : static PRUint32
603 0 : SmartCacheSize(const PRUint32 availKB)
604 : {
605 0 : if (availKB > 100 * 1024 * 1024)
606 0 : return MAX_CACHE_SIZE; // skip computing if we're over 100 GB
607 :
608 : // Grow/shrink in 10 MB units, deliberately, so that in the common case we
609 : // don't shrink cache and evict items every time we startup (it's important
610 : // that we don't slow down startup benchmarks).
611 0 : PRUint32 sz10MBs = 0;
612 0 : PRUint32 avail10MBs = availKB / (1024*10);
613 :
614 : // .5% of space above 25 GB
615 0 : if (avail10MBs > 2500) {
616 0 : sz10MBs += (avail10MBs - 2500)*.005;
617 0 : avail10MBs = 2500;
618 : }
619 : // 1% of space between 7GB -> 25 GB
620 0 : if (avail10MBs > 700) {
621 0 : sz10MBs += (avail10MBs - 700)*.01;
622 0 : avail10MBs = 700;
623 : }
624 : // 5% of space between 500 MB -> 7 GB
625 0 : if (avail10MBs > 50) {
626 0 : sz10MBs += (avail10MBs - 50)*.05;
627 0 : avail10MBs = 50;
628 : }
629 :
630 : // 40% of space up to 500 MB (50 MB min)
631 0 : sz10MBs += NS_MAX<PRUint32>(5, avail10MBs * .4);
632 :
633 0 : return NS_MIN<PRUint32>(MAX_CACHE_SIZE, sz10MBs * 10 * 1024);
634 : }
635 :
636 : /* Computes our best guess for the default size of the user's disk cache,
637 : * based on the amount of space they have free on their hard drive.
638 : * We use a tiered scheme: the more space available,
639 : * the larger the disk cache will be. However, we do not want
640 : * to enable the disk cache to grow to an unbounded size, so the larger the
641 : * user's available space is, the smaller of a percentage we take. We set a
642 : * lower bound of 50MB and an upper bound of 1GB.
643 : *
644 : *@param: None.
645 : *@return: The size that the user's disk cache should default to, in kBytes.
646 : */
647 : PRUint32
648 0 : nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
649 : PRUint32 currentSize)
650 : {
651 : // Check for free space on device where cache directory lives
652 : nsresult rv;
653 : nsCOMPtr<nsILocalFile>
654 0 : cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
655 0 : if (NS_FAILED(rv) || !cacheDirectory)
656 0 : return DEFAULT_CACHE_SIZE;
657 0 : rv = cacheDirectory->InitWithPath(cachePath);
658 0 : if (NS_FAILED(rv))
659 0 : return DEFAULT_CACHE_SIZE;
660 : PRInt64 bytesAvailable;
661 0 : rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
662 0 : if (NS_FAILED(rv))
663 0 : return DEFAULT_CACHE_SIZE;
664 :
665 0 : return SmartCacheSize((bytesAvailable / 1024) + currentSize);
666 : }
667 :
668 : /* Determine if we are permitted to dynamically size the user's disk cache based
669 : * on their disk space available. We may do this so long as the pref
670 : * smart_size.enabled is true.
671 : */
672 : bool
673 176 : nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
674 : firstRun)
675 : {
676 : nsresult rv;
677 176 : if (firstRun) {
678 : // check if user has set cache size in the past
679 : bool userSet;
680 176 : rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
681 176 : if (NS_FAILED(rv)) userSet = true;
682 176 : if (userSet) {
683 : PRInt32 oldCapacity;
684 : // If user explicitly set cache size to be smaller than old default
685 : // of 50 MB, then keep user's value. Otherwise use smart sizing.
686 1 : rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
687 1 : if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
688 1 : mSmartSizeEnabled = false;
689 : branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
690 1 : mSmartSizeEnabled);
691 1 : return mSmartSizeEnabled;
692 : }
693 : }
694 : // Set manual setting to MAX cache size as starting val for any
695 : // adjustment by user: (bug 559942 comment 65)
696 175 : branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
697 : }
698 :
699 : rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
700 175 : &mSmartSizeEnabled);
701 175 : if (NS_FAILED(rv))
702 0 : mSmartSizeEnabled = false;
703 175 : return mSmartSizeEnabled;
704 : }
705 :
706 :
707 : nsresult
708 269 : nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
709 : {
710 269 : nsresult rv = NS_OK;
711 :
712 : // read disk cache device prefs
713 269 : if (!mInPrivateBrowsing) {
714 269 : mDiskCacheEnabled = true; // presume disk cache is enabled
715 269 : (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
716 : }
717 :
718 269 : mDiskCacheCapacity = DISK_CACHE_CAPACITY;
719 269 : (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
720 269 : mDiskCacheCapacity = NS_MAX(0, mDiskCacheCapacity);
721 :
722 : (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
723 269 : &mDiskCacheMaxEntrySize);
724 269 : mDiskCacheMaxEntrySize = NS_MAX(-1, mDiskCacheMaxEntrySize);
725 :
726 : (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
727 : NS_GET_IID(nsILocalFile),
728 269 : getter_AddRefs(mDiskCacheParentDirectory));
729 :
730 269 : if (!mDiskCacheParentDirectory) {
731 538 : nsCOMPtr<nsIFile> directory;
732 :
733 : // try to get the disk cache parent directory
734 : rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
735 269 : getter_AddRefs(directory));
736 269 : if (NS_FAILED(rv)) {
737 : // try to get the profile directory (there may not be a profile yet)
738 538 : nsCOMPtr<nsIFile> profDir;
739 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
740 269 : getter_AddRefs(profDir));
741 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
742 269 : getter_AddRefs(directory));
743 269 : if (!directory)
744 93 : directory = profDir;
745 176 : else if (profDir) {
746 : bool same;
747 176 : if (NS_SUCCEEDED(profDir->Equals(directory, &same)) && !same) {
748 : // We no longer store the cache directory in the main
749 : // profile directory, so we should cleanup the old one.
750 0 : rv = profDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
751 0 : if (NS_SUCCEEDED(rv)) {
752 : bool exists;
753 0 : if (NS_SUCCEEDED(profDir->Exists(&exists)) && exists)
754 0 : nsDeleteDir::DeleteDir(profDir, false);
755 : }
756 : }
757 : }
758 : }
759 : // use file cache in build tree only if asked, to avoid cache dir litter
760 269 : if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
761 : rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
762 0 : getter_AddRefs(directory));
763 : }
764 269 : if (directory)
765 176 : mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
766 : }
767 269 : if (mDiskCacheParentDirectory) {
768 : bool firstSmartSizeRun;
769 : rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
770 176 : &firstSmartSizeRun);
771 176 : if (NS_FAILED(rv))
772 0 : firstSmartSizeRun = false;
773 176 : if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
774 : // Avoid evictions: use previous cache size until smart size event
775 : // updates mDiskCacheCapacity
776 : rv = branch->GetIntPref(firstSmartSizeRun ?
777 : DISK_CACHE_CAPACITY_PREF :
778 : DISK_CACHE_SMART_SIZE_PREF,
779 175 : &mDiskCacheCapacity);
780 175 : if (NS_FAILED(rv))
781 0 : mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
782 : }
783 :
784 176 : if (firstSmartSizeRun) {
785 : // It is no longer our first run
786 : rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
787 176 : false);
788 176 : if (NS_FAILED(rv))
789 0 : NS_WARNING("Failed setting first_run pref in ReadPrefs.");
790 : }
791 : }
792 :
793 : // read offline cache device prefs
794 269 : if (!mInPrivateBrowsing) {
795 269 : mOfflineCacheEnabled = true; // presume offline cache is enabled
796 : (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
797 269 : &mOfflineCacheEnabled);
798 : }
799 :
800 269 : mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
801 : (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
802 269 : &mOfflineCacheCapacity);
803 269 : mOfflineCacheCapacity = NS_MAX(0, mOfflineCacheCapacity);
804 :
805 : (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
806 : NS_GET_IID(nsILocalFile),
807 269 : getter_AddRefs(mOfflineCacheParentDirectory));
808 :
809 269 : if (!mOfflineCacheParentDirectory) {
810 522 : nsCOMPtr<nsIFile> directory;
811 :
812 : // try to get the offline cache parent directory
813 : rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
814 261 : getter_AddRefs(directory));
815 261 : if (NS_FAILED(rv)) {
816 : // try to get the profile directory (there may not be a profile yet)
817 522 : nsCOMPtr<nsIFile> profDir;
818 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
819 261 : getter_AddRefs(profDir));
820 : NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
821 261 : getter_AddRefs(directory));
822 261 : if (!directory)
823 93 : directory = profDir;
824 : }
825 : #if DEBUG
826 261 : if (!directory) {
827 : // use current process directory during development
828 : rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
829 93 : getter_AddRefs(directory));
830 : }
831 : #endif
832 261 : if (directory)
833 261 : mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
834 : }
835 :
836 : // read memory cache device prefs
837 269 : (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
838 :
839 269 : mMemoryCacheCapacity = -1;
840 : (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
841 269 : &mMemoryCacheCapacity);
842 :
843 : (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
844 269 : &mMemoryCacheMaxEntrySize);
845 269 : mMemoryCacheMaxEntrySize = NS_MAX(-1, mMemoryCacheMaxEntrySize);
846 :
847 : // read cache compression level pref
848 269 : mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
849 : (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
850 269 : &mCacheCompressionLevel);
851 269 : mCacheCompressionLevel = NS_MAX(0, mCacheCompressionLevel);
852 269 : mCacheCompressionLevel = NS_MIN(9, mCacheCompressionLevel);
853 :
854 : // read cache shutdown sanitization prefs
855 : (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
856 269 : &mSanitizeOnShutdown);
857 : (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
858 269 : &mClearCacheOnShutdown);
859 :
860 269 : return rv;
861 : }
862 :
863 : nsresult
864 2715 : nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
865 : {
866 2715 : if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
867 2715 : return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
868 : }
869 :
870 : nsresult
871 683 : nsCacheService::SyncWithCacheIOThread()
872 : {
873 683 : gService->mLock.AssertCurrentThreadOwns();
874 683 : if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
875 :
876 1366 : nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
877 :
878 : // dispatch event - it will notify the monitor when it's done
879 : nsresult rv =
880 683 : gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
881 683 : if (NS_FAILED(rv)) {
882 0 : NS_WARNING("Failed dispatching block-event");
883 0 : return NS_ERROR_UNEXPECTED;
884 : }
885 :
886 : // wait until notified, then return
887 683 : rv = gService->mCondVar.Wait();
888 :
889 683 : return rv;
890 : }
891 :
892 :
893 : bool
894 454 : nsCacheProfilePrefObserver::DiskCacheEnabled()
895 : {
896 454 : if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
897 361 : return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
898 : }
899 :
900 :
901 : bool
902 275 : nsCacheProfilePrefObserver::OfflineCacheEnabled()
903 : {
904 275 : if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
905 0 : return false;
906 :
907 275 : return mOfflineCacheEnabled;
908 : }
909 :
910 :
911 : bool
912 271 : nsCacheProfilePrefObserver::MemoryCacheEnabled()
913 : {
914 271 : if (mMemoryCacheCapacity == 0) return false;
915 271 : return mMemoryCacheEnabled;
916 : }
917 :
918 :
919 : /**
920 : * MemoryCacheCapacity
921 : *
922 : * If the browser.cache.memory.capacity preference is positive, we use that
923 : * value for the amount of memory available for the cache.
924 : *
925 : * If browser.cache.memory.capacity is zero, the memory cache is disabled.
926 : *
927 : * If browser.cache.memory.capacity is negative or not present, we use a
928 : * formula that grows less than linearly with the amount of system memory,
929 : * with an upper limit on the cache size. No matter how much physical RAM is
930 : * present, the default cache size would not exceed 32 MB. This maximum would
931 : * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
932 : *
933 : * RAM Cache
934 : * --- -----
935 : * 32 Mb 2 Mb
936 : * 64 Mb 4 Mb
937 : * 128 Mb 6 Mb
938 : * 256 Mb 10 Mb
939 : * 512 Mb 14 Mb
940 : * 1024 Mb 18 Mb
941 : * 2048 Mb 24 Mb
942 : * 4096 Mb 30 Mb
943 : *
944 : * The equation for this is (for cache size C and memory size K (kbytes)):
945 : * x = log2(K) - 14
946 : * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
947 : * if (C > 32) C = 32
948 : */
949 :
950 : PRInt32
951 90 : nsCacheProfilePrefObserver::MemoryCacheCapacity()
952 : {
953 90 : PRInt32 capacity = mMemoryCacheCapacity;
954 90 : if (capacity >= 0) {
955 1 : CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
956 1 : return capacity;
957 : }
958 :
959 89 : static PRUint64 bytes = PR_GetPhysicalMemorySize();
960 89 : CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
961 :
962 : // If getting the physical memory failed, arbitrarily assume
963 : // 32 MB of RAM. We use a low default to have a reasonable
964 : // size on all the devices we support.
965 89 : if (bytes == 0)
966 0 : bytes = 32 * 1024 * 1024;
967 :
968 : // Conversion from unsigned int64 to double doesn't work on all platforms.
969 : // We need to truncate the value at LL_MAXINT to make sure we don't
970 : // overflow.
971 : if (LL_CMP(bytes, >, LL_MAXINT))
972 : bytes = LL_MAXINT;
973 :
974 : PRUint64 kbytes;
975 89 : LL_SHR(kbytes, bytes, 10);
976 :
977 : double kBytesD;
978 89 : LL_L2D(kBytesD, (PRInt64) kbytes);
979 :
980 89 : double x = log(kBytesD)/log(2.0) - 14;
981 89 : if (x > 0) {
982 89 : capacity = (PRInt32)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
983 89 : if (capacity > 32)
984 0 : capacity = 32;
985 89 : capacity *= 1024;
986 : } else {
987 0 : capacity = 0;
988 : }
989 :
990 89 : return capacity;
991 : }
992 :
993 : PRInt32
994 1194 : nsCacheProfilePrefObserver::CacheCompressionLevel()
995 : {
996 1194 : return mCacheCompressionLevel;
997 : }
998 :
999 : /******************************************************************************
1000 : * nsProcessRequestEvent
1001 : *****************************************************************************/
1002 :
1003 : class nsProcessRequestEvent : public nsRunnable {
1004 : public:
1005 1884 : nsProcessRequestEvent(nsCacheRequest *aRequest)
1006 1884 : {
1007 1884 : mRequest = aRequest;
1008 1884 : }
1009 :
1010 1884 : NS_IMETHOD Run()
1011 : {
1012 : nsresult rv;
1013 :
1014 1884 : NS_ASSERTION(mRequest->mListener,
1015 : "Sync OpenCacheEntry() posted to background thread!");
1016 :
1017 3768 : nsCacheServiceAutoLock lock;
1018 : rv = nsCacheService::gService->ProcessRequest(mRequest,
1019 : false,
1020 1884 : nsnull);
1021 :
1022 : // Don't delete the request if it was queued
1023 1884 : if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
1024 1303 : delete mRequest;
1025 :
1026 1884 : return NS_OK;
1027 : }
1028 :
1029 : protected:
1030 7536 : virtual ~nsProcessRequestEvent() {}
1031 :
1032 : private:
1033 : nsCacheRequest *mRequest;
1034 : };
1035 :
1036 : /******************************************************************************
1037 : * nsCacheService
1038 : *****************************************************************************/
1039 : nsCacheService * nsCacheService::gService = nsnull;
1040 :
1041 1464 : static nsCOMPtr<nsIMemoryReporter> MemoryCacheReporter = nsnull;
1042 :
1043 90 : NS_THREADSAFE_MEMORY_REPORTER_IMPLEMENT(NetworkMemoryCache,
1044 : "explicit/network-memory-cache",
1045 : KIND_HEAP,
1046 : UNITS_BYTES,
1047 : nsCacheService::MemoryDeviceSize,
1048 720 : "Memory used by the network memory cache.")
1049 :
1050 26694 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService)
1051 :
1052 269 : nsCacheService::nsCacheService()
1053 : : mLock("nsCacheService.mLock"),
1054 : mCondVar(mLock, "nsCacheService.mCondVar"),
1055 : mInitialized(false),
1056 : mEnableMemoryDevice(true),
1057 : mEnableDiskDevice(true),
1058 : mMemoryDevice(nsnull),
1059 : mDiskDevice(nsnull),
1060 : mOfflineDevice(nsnull),
1061 : mTotalEntries(0),
1062 : mCacheHits(0),
1063 : mCacheMisses(0),
1064 : mMaxKeyLength(0),
1065 : mMaxDataSize(0),
1066 : mMaxMetaSize(0),
1067 : mDeactivateFailures(0),
1068 269 : mDeactivatedUnboundEntries(0)
1069 : {
1070 269 : NS_ASSERTION(gService==nsnull, "multiple nsCacheService instances!");
1071 269 : gService = this;
1072 :
1073 : // create list of cache devices
1074 269 : PR_INIT_CLIST(&mDoomedEntries);
1075 269 : }
1076 :
1077 807 : nsCacheService::~nsCacheService()
1078 : {
1079 269 : if (mInitialized) // Shutdown hasn't been called yet.
1080 0 : (void) Shutdown();
1081 :
1082 269 : gService = nsnull;
1083 1076 : }
1084 :
1085 :
1086 : nsresult
1087 269 : nsCacheService::Init()
1088 : {
1089 : NS_TIME_FUNCTION;
1090 :
1091 269 : NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
1092 269 : if (mInitialized)
1093 0 : return NS_ERROR_ALREADY_INITIALIZED;
1094 :
1095 269 : if (mozilla::net::IsNeckoChild()) {
1096 0 : return NS_ERROR_UNEXPECTED;
1097 : }
1098 :
1099 269 : CACHE_LOG_INIT();
1100 :
1101 269 : nsresult rv = NS_NewThread(getter_AddRefs(mCacheIOThread));
1102 269 : if (NS_FAILED(rv)) {
1103 0 : NS_WARNING("Can't create cache IO thread");
1104 : }
1105 :
1106 269 : rv = nsDeleteDir::Init();
1107 269 : if (NS_FAILED(rv)) {
1108 0 : NS_WARNING("Can't initialize nsDeleteDir");
1109 : }
1110 :
1111 : // initialize hashtable for active cache entries
1112 269 : rv = mActiveEntries.Init();
1113 269 : if (NS_FAILED(rv)) return rv;
1114 :
1115 : // create profile/preference observer
1116 269 : mObserver = new nsCacheProfilePrefObserver();
1117 269 : if (!mObserver) return NS_ERROR_OUT_OF_MEMORY;
1118 269 : NS_ADDREF(mObserver);
1119 :
1120 269 : mObserver->Install();
1121 269 : mEnableDiskDevice = mObserver->DiskCacheEnabled();
1122 269 : mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
1123 269 : mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
1124 :
1125 269 : mInitialized = true;
1126 269 : return NS_OK;
1127 : }
1128 :
1129 :
1130 : void
1131 269 : nsCacheService::Shutdown()
1132 : {
1133 538 : nsCOMPtr<nsIThread> cacheIOThread;
1134 538 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
1135 :
1136 269 : bool shouldSanitize = false;
1137 538 : nsCOMPtr<nsILocalFile> parentDir;
1138 :
1139 : {
1140 538 : nsCacheServiceAutoLock lock;
1141 269 : NS_ASSERTION(mInitialized,
1142 : "can't shutdown nsCacheService unless it has been initialized.");
1143 :
1144 269 : if (mInitialized) {
1145 :
1146 269 : mInitialized = false;
1147 :
1148 : // Clear entries
1149 269 : ClearDoomList();
1150 269 : ClearActiveEntries();
1151 :
1152 269 : if (mSmartSizeTimer) {
1153 175 : mSmartSizeTimer->Cancel();
1154 175 : mSmartSizeTimer = nsnull;
1155 : }
1156 :
1157 : // Make sure to wait for any pending cache-operations before
1158 : // proceeding with destructive actions (bug #620660)
1159 269 : (void) SyncWithCacheIOThread();
1160 :
1161 : // obtain the disk cache directory in case we need to sanitize it
1162 269 : parentDir = mObserver->DiskCacheParentDirectory();
1163 269 : shouldSanitize = mObserver->SanitizeAtShutdown();
1164 269 : mObserver->Remove();
1165 269 : NS_RELEASE(mObserver);
1166 :
1167 : // unregister memory reporter, before deleting the memory device, just
1168 : // to be safe
1169 269 : NS_UnregisterMemoryReporter(MemoryCacheReporter);
1170 269 : MemoryCacheReporter = nsnull;
1171 :
1172 : // deallocate memory and disk caches
1173 269 : delete mMemoryDevice;
1174 269 : mMemoryDevice = nsnull;
1175 :
1176 269 : delete mDiskDevice;
1177 269 : mDiskDevice = nsnull;
1178 :
1179 269 : if (mOfflineDevice)
1180 15 : mOfflineDevice->Shutdown();
1181 :
1182 269 : NS_IF_RELEASE(mOfflineDevice);
1183 :
1184 : #ifdef PR_LOGGING
1185 269 : LogCacheStatistics();
1186 : #endif
1187 :
1188 269 : mCacheIOThread.swap(cacheIOThread);
1189 : }
1190 : } // lock
1191 :
1192 269 : if (cacheIOThread)
1193 269 : cacheIOThread->Shutdown();
1194 :
1195 269 : if (shouldSanitize) {
1196 1 : nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
1197 1 : if (NS_SUCCEEDED(rv)) {
1198 : bool exists;
1199 1 : if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
1200 0 : nsDeleteDir::DeleteDir(parentDir, false);
1201 : }
1202 2 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
1203 1 : nsDeleteDir::Shutdown(shouldSanitize);
1204 : } else {
1205 536 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
1206 268 : nsDeleteDir::Shutdown(shouldSanitize);
1207 : }
1208 269 : }
1209 :
1210 :
1211 : nsresult
1212 269 : nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
1213 : {
1214 : nsresult rv;
1215 :
1216 269 : if (aOuter != nsnull)
1217 0 : return NS_ERROR_NO_AGGREGATION;
1218 :
1219 269 : nsCacheService * cacheService = new nsCacheService();
1220 269 : if (cacheService == nsnull)
1221 0 : return NS_ERROR_OUT_OF_MEMORY;
1222 :
1223 269 : NS_ADDREF(cacheService);
1224 269 : rv = cacheService->Init();
1225 269 : if (NS_SUCCEEDED(rv)) {
1226 269 : rv = cacheService->QueryInterface(aIID, aResult);
1227 : }
1228 269 : NS_RELEASE(cacheService);
1229 269 : return rv;
1230 : }
1231 :
1232 :
1233 : NS_IMETHODIMP
1234 2739 : nsCacheService::CreateSession(const char * clientID,
1235 : nsCacheStoragePolicy storagePolicy,
1236 : bool streamBased,
1237 : nsICacheSession **result)
1238 : {
1239 2739 : *result = nsnull;
1240 :
1241 2739 : if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
1242 :
1243 2739 : nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
1244 2739 : if (!session) return NS_ERROR_OUT_OF_MEMORY;
1245 :
1246 2739 : NS_ADDREF(*result = session);
1247 :
1248 2739 : return NS_OK;
1249 : }
1250 :
1251 :
1252 : nsresult
1253 0 : nsCacheService::EvictEntriesForSession(nsCacheSession * session)
1254 : {
1255 0 : NS_ASSERTION(gService, "nsCacheService::gService is null.");
1256 : return gService->EvictEntriesForClient(session->ClientID()->get(),
1257 0 : session->StoragePolicy());
1258 : }
1259 :
1260 : namespace {
1261 :
1262 : class EvictionNotifierRunnable : public nsRunnable
1263 316 : {
1264 : public:
1265 79 : EvictionNotifierRunnable(nsISupports* aSubject)
1266 79 : : mSubject(aSubject)
1267 79 : { }
1268 :
1269 : NS_DECL_NSIRUNNABLE
1270 :
1271 : private:
1272 : nsCOMPtr<nsISupports> mSubject;
1273 : };
1274 :
1275 : NS_IMETHODIMP
1276 79 : EvictionNotifierRunnable::Run()
1277 : {
1278 : nsCOMPtr<nsIObserverService> obsSvc =
1279 158 : mozilla::services::GetObserverService();
1280 79 : if (obsSvc) {
1281 79 : obsSvc->NotifyObservers(mSubject,
1282 : NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
1283 79 : nsnull);
1284 : }
1285 79 : return NS_OK;
1286 : }
1287 :
1288 : } // anonymous namespace
1289 :
1290 : nsresult
1291 79 : nsCacheService::EvictEntriesForClient(const char * clientID,
1292 : nsCacheStoragePolicy storagePolicy)
1293 : {
1294 158 : nsRefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable(this);
1295 79 : NS_DispatchToMainThread(r);
1296 :
1297 158 : nsCacheServiceAutoLock lock;
1298 79 : nsresult res = NS_OK;
1299 :
1300 79 : if (storagePolicy == nsICache::STORE_ANYWHERE ||
1301 : storagePolicy == nsICache::STORE_ON_DISK) {
1302 :
1303 78 : if (mEnableDiskDevice) {
1304 : nsresult rv;
1305 61 : if (!mDiskDevice)
1306 11 : rv = CreateDiskDevice();
1307 61 : if (mDiskDevice)
1308 61 : rv = mDiskDevice->EvictEntries(clientID);
1309 61 : if (NS_FAILED(rv)) res = rv;
1310 : }
1311 : }
1312 :
1313 : // Only clear the offline cache if it has been specifically asked for.
1314 79 : if (storagePolicy == nsICache::STORE_OFFLINE) {
1315 1 : if (mEnableOfflineDevice) {
1316 : nsresult rv;
1317 1 : if (!mOfflineDevice)
1318 1 : rv = CreateOfflineDevice();
1319 1 : if (mOfflineDevice)
1320 1 : rv = mOfflineDevice->EvictEntries(clientID);
1321 1 : if (NS_FAILED(rv)) res = rv;
1322 : }
1323 : }
1324 :
1325 79 : if (storagePolicy == nsICache::STORE_ANYWHERE ||
1326 : storagePolicy == nsICache::STORE_IN_MEMORY) {
1327 :
1328 : // If there is no memory device, there is no need to evict it...
1329 78 : if (mMemoryDevice) {
1330 : nsresult rv;
1331 9 : rv = mMemoryDevice->EvictEntries(clientID);
1332 9 : if (NS_FAILED(rv)) res = rv;
1333 : }
1334 : }
1335 :
1336 79 : return res;
1337 : }
1338 :
1339 :
1340 : nsresult
1341 0 : nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
1342 : bool * result)
1343 : {
1344 0 : if (gService == nsnull) return NS_ERROR_NOT_AVAILABLE;
1345 0 : nsCacheServiceAutoLock lock;
1346 :
1347 0 : *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
1348 0 : return NS_OK;
1349 : }
1350 :
1351 :
1352 : bool
1353 3298 : nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
1354 : {
1355 3298 : if (gService->mEnableMemoryDevice &&
1356 : (storagePolicy == nsICache::STORE_ANYWHERE ||
1357 : storagePolicy == nsICache::STORE_IN_MEMORY)) {
1358 3222 : return true;
1359 : }
1360 76 : if (gService->mEnableDiskDevice &&
1361 : (storagePolicy == nsICache::STORE_ANYWHERE ||
1362 : storagePolicy == nsICache::STORE_ON_DISK ||
1363 : storagePolicy == nsICache::STORE_ON_DISK_AS_FILE)) {
1364 35 : return true;
1365 : }
1366 41 : if (gService->mEnableOfflineDevice &&
1367 : storagePolicy == nsICache::STORE_OFFLINE) {
1368 39 : return true;
1369 : }
1370 :
1371 2 : return false;
1372 : }
1373 :
1374 6 : NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
1375 : {
1376 6 : NS_ENSURE_ARG_POINTER(visitor);
1377 :
1378 12 : nsCacheServiceAutoLock lock;
1379 :
1380 6 : if (!(mEnableDiskDevice || mEnableMemoryDevice))
1381 0 : return NS_ERROR_NOT_AVAILABLE;
1382 :
1383 : // XXX record the fact that a visitation is in progress,
1384 : // XXX i.e. keep list of visitors in progress.
1385 :
1386 6 : nsresult rv = NS_OK;
1387 : // If there is no memory device, there are then also no entries to visit...
1388 6 : if (mMemoryDevice) {
1389 4 : rv = mMemoryDevice->Visit(visitor);
1390 4 : if (NS_FAILED(rv)) return rv;
1391 : }
1392 :
1393 6 : if (mEnableDiskDevice) {
1394 4 : if (!mDiskDevice) {
1395 0 : rv = CreateDiskDevice();
1396 0 : if (NS_FAILED(rv)) return rv;
1397 : }
1398 4 : rv = mDiskDevice->Visit(visitor);
1399 4 : if (NS_FAILED(rv)) return rv;
1400 : }
1401 :
1402 6 : if (mEnableOfflineDevice) {
1403 4 : if (!mOfflineDevice) {
1404 1 : rv = CreateOfflineDevice();
1405 1 : if (NS_FAILED(rv)) return rv;
1406 : }
1407 4 : rv = mOfflineDevice->Visit(visitor);
1408 4 : if (NS_FAILED(rv)) return rv;
1409 : }
1410 :
1411 : // XXX notify any shutdown process that visitation is complete for THIS visitor.
1412 : // XXX keep queue of visitors
1413 :
1414 6 : return NS_OK;
1415 : }
1416 :
1417 :
1418 79 : NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
1419 : {
1420 79 : return EvictEntriesForClient(nsnull, storagePolicy);
1421 : }
1422 :
1423 1134 : NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
1424 : {
1425 2268 : nsCacheServiceAutoLock lock;
1426 :
1427 1134 : if (!mCacheIOThread)
1428 0 : return NS_ERROR_NOT_AVAILABLE;
1429 :
1430 1134 : NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
1431 1134 : return NS_OK;
1432 : }
1433 :
1434 : /**
1435 : * Internal Methods
1436 : */
1437 : nsresult
1438 175 : nsCacheService::CreateDiskDevice()
1439 : {
1440 175 : if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1441 175 : if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
1442 175 : if (mDiskDevice) return NS_OK;
1443 :
1444 175 : mDiskDevice = new nsDiskCacheDevice;
1445 175 : if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
1446 :
1447 : // set the preferences
1448 175 : mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
1449 175 : mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
1450 175 : mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
1451 :
1452 175 : nsresult rv = mDiskDevice->Init();
1453 175 : if (NS_FAILED(rv)) {
1454 : #if DEBUG
1455 0 : printf("###\n");
1456 0 : printf("### mDiskDevice->Init() failed (0x%.8x)\n", rv);
1457 0 : printf("### - disabling disk cache for this session.\n");
1458 0 : printf("###\n");
1459 : #endif
1460 0 : mEnableDiskDevice = false;
1461 0 : delete mDiskDevice;
1462 0 : mDiskDevice = nsnull;
1463 0 : return rv;
1464 : }
1465 :
1466 175 : NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
1467 :
1468 : // Disk device is usually created during the startup. Delay smart size
1469 : // calculation to avoid possible massive IO caused by eviction of entries
1470 : // in case the new smart size is smaller than current cache usage.
1471 175 : mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1472 175 : if (NS_SUCCEEDED(rv)) {
1473 175 : rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
1474 : 1000*60*3,
1475 350 : nsITimer::TYPE_ONE_SHOT);
1476 175 : if (NS_FAILED(rv)) {
1477 0 : NS_WARNING("Failed to post smart size timer");
1478 0 : mSmartSizeTimer = nsnull;
1479 : }
1480 : } else {
1481 0 : NS_WARNING("Can't create smart size timer");
1482 : }
1483 : // Ignore state of the timer and return success since the purpose of the
1484 : // method (create the disk-device) has been fulfilled
1485 :
1486 175 : return NS_OK;
1487 : }
1488 :
1489 : nsresult
1490 15 : nsCacheService::CreateOfflineDevice()
1491 : {
1492 15 : CACHE_LOG_ALWAYS(("Creating offline device"));
1493 :
1494 15 : if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1495 15 : if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
1496 15 : if (mOfflineDevice) return NS_OK;
1497 :
1498 15 : mOfflineDevice = new nsOfflineCacheDevice;
1499 15 : if (!mOfflineDevice) return NS_ERROR_OUT_OF_MEMORY;
1500 :
1501 15 : NS_ADDREF(mOfflineDevice);
1502 :
1503 : // set the preferences
1504 : mOfflineDevice->SetCacheParentDirectory(
1505 15 : mObserver->OfflineCacheParentDirectory());
1506 15 : mOfflineDevice->SetCapacity(mObserver->OfflineCacheCapacity());
1507 :
1508 15 : nsresult rv = mOfflineDevice->Init();
1509 15 : if (NS_FAILED(rv)) {
1510 0 : CACHE_LOG_DEBUG(("mOfflineDevice->Init() failed (0x%.8x)\n", rv));
1511 0 : CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
1512 :
1513 0 : mEnableOfflineDevice = false;
1514 0 : NS_RELEASE(mOfflineDevice);
1515 : }
1516 15 : return rv;
1517 : }
1518 :
1519 : nsresult
1520 90 : nsCacheService::CreateMemoryDevice()
1521 : {
1522 90 : if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1523 90 : if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
1524 90 : if (mMemoryDevice) return NS_OK;
1525 :
1526 90 : mMemoryDevice = new nsMemoryCacheDevice;
1527 90 : if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
1528 :
1529 : // set preference
1530 90 : PRInt32 capacity = mObserver->MemoryCacheCapacity();
1531 90 : CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
1532 90 : mMemoryDevice->SetCapacity(capacity);
1533 90 : mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
1534 :
1535 90 : nsresult rv = mMemoryDevice->Init();
1536 90 : if (NS_FAILED(rv)) {
1537 0 : NS_WARNING("Initialization of Memory Cache failed.");
1538 0 : delete mMemoryDevice;
1539 0 : mMemoryDevice = nsnull;
1540 : }
1541 :
1542 : MemoryCacheReporter =
1543 90 : new NS_MEMORY_REPORTER_NAME(NetworkMemoryCache);
1544 90 : NS_RegisterMemoryReporter(MemoryCacheReporter);
1545 :
1546 90 : return rv;
1547 : }
1548 :
1549 :
1550 : nsresult
1551 2739 : nsCacheService::CreateRequest(nsCacheSession * session,
1552 : const nsACString & clientKey,
1553 : nsCacheAccessMode accessRequested,
1554 : bool blockingMode,
1555 : nsICacheListener * listener,
1556 : nsCacheRequest ** request)
1557 : {
1558 2739 : NS_ASSERTION(request, "CreateRequest: request is null");
1559 :
1560 5478 : nsCString * key = new nsCString(*session->ClientID());
1561 2739 : if (!key)
1562 0 : return NS_ERROR_OUT_OF_MEMORY;
1563 2739 : key->Append(':');
1564 2739 : key->Append(clientKey);
1565 :
1566 2739 : if (mMaxKeyLength < key->Length()) mMaxKeyLength = key->Length();
1567 :
1568 : // create request
1569 2739 : *request = new nsCacheRequest(key, listener, accessRequested, blockingMode, session);
1570 2739 : if (!*request) {
1571 0 : delete key;
1572 0 : return NS_ERROR_OUT_OF_MEMORY;
1573 : }
1574 :
1575 2739 : if (!listener) return NS_OK; // we're sync, we're done.
1576 :
1577 : // get the request's thread
1578 1884 : (*request)->mThread = do_GetCurrentThread();
1579 :
1580 1884 : return NS_OK;
1581 : }
1582 :
1583 :
1584 : class nsCacheListenerEvent : public nsRunnable
1585 6488 : {
1586 : public:
1587 1622 : nsCacheListenerEvent(nsICacheListener *listener,
1588 : nsICacheEntryDescriptor *descriptor,
1589 : nsCacheAccessMode accessGranted,
1590 : nsresult status)
1591 : : mListener(listener) // transfers reference
1592 : , mDescriptor(descriptor) // transfers reference (may be null)
1593 : , mAccessGranted(accessGranted)
1594 1622 : , mStatus(status)
1595 1622 : {}
1596 :
1597 1622 : NS_IMETHOD Run()
1598 : {
1599 1622 : mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
1600 :
1601 1622 : NS_RELEASE(mListener);
1602 1622 : NS_IF_RELEASE(mDescriptor);
1603 1622 : return NS_OK;
1604 : }
1605 :
1606 : private:
1607 : // We explicitly leak mListener or mDescriptor if Run is not called
1608 : // because otherwise we cannot guarantee that they are destroyed on
1609 : // the right thread.
1610 :
1611 : nsICacheListener *mListener;
1612 : nsICacheEntryDescriptor *mDescriptor;
1613 : nsCacheAccessMode mAccessGranted;
1614 : nsresult mStatus;
1615 : };
1616 :
1617 :
1618 : nsresult
1619 1622 : nsCacheService::NotifyListener(nsCacheRequest * request,
1620 : nsICacheEntryDescriptor * descriptor,
1621 : nsCacheAccessMode accessGranted,
1622 : nsresult status)
1623 : {
1624 1622 : NS_ASSERTION(request->mThread, "no thread set in async request!");
1625 :
1626 : // Swap ownership, and release listener on target thread...
1627 1622 : nsICacheListener *listener = request->mListener;
1628 1622 : request->mListener = nsnull;
1629 :
1630 : nsCOMPtr<nsIRunnable> ev =
1631 : new nsCacheListenerEvent(listener, descriptor,
1632 3244 : accessGranted, status);
1633 1622 : if (!ev) {
1634 : // Better to leak listener and descriptor if we fail because we don't
1635 : // want to destroy them inside the cache service lock or on potentially
1636 : // the wrong thread.
1637 0 : return NS_ERROR_OUT_OF_MEMORY;
1638 : }
1639 :
1640 1622 : return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1641 : }
1642 :
1643 :
1644 : nsresult
1645 3292 : nsCacheService::ProcessRequest(nsCacheRequest * request,
1646 : bool calledFromOpenCacheEntry,
1647 : nsICacheEntryDescriptor ** result)
1648 : {
1649 : // !!! must be called with mLock held !!!
1650 : nsresult rv;
1651 3292 : nsCacheEntry * entry = nsnull;
1652 3292 : nsCacheEntry * doomedEntry = nsnull;
1653 3292 : nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1654 3292 : if (result) *result = nsnull;
1655 :
1656 0 : while(1) { // Activate entry loop
1657 3292 : rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
1658 3292 : if (NS_FAILED(rv)) break;
1659 :
1660 0 : while(1) { // Request Access loop
1661 2596 : NS_ASSERTION(entry, "no entry in Request Access loop!");
1662 : // entry->RequestAccess queues request on entry
1663 2596 : rv = entry->RequestAccess(request, &accessGranted);
1664 2596 : if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
1665 :
1666 1112 : if (request->mListener) // async exits - validate, doom, or close will resume
1667 1109 : return rv;
1668 :
1669 3 : if (request->IsBlocking()) {
1670 : // XXX this is probably wrong...
1671 0 : Unlock();
1672 0 : rv = request->WaitForValidation();
1673 0 : Lock();
1674 : }
1675 :
1676 3 : PR_REMOVE_AND_INIT_LINK(request);
1677 3 : if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
1678 : // okay, we're ready to process this request, request access again
1679 : }
1680 1487 : if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
1681 :
1682 0 : if (entry->IsNotInUse()) {
1683 : // this request was the last one keeping it around, so get rid of it
1684 0 : DeactivateEntry(entry);
1685 : }
1686 : // loop back around to look for another entry
1687 : }
1688 :
1689 2183 : nsICacheEntryDescriptor *descriptor = nsnull;
1690 :
1691 2183 : if (NS_SUCCEEDED(rv))
1692 1484 : rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
1693 :
1694 : // If doomedEntry is set, ActivatEntry() doomed an existing entry and
1695 : // created a new one for that cache-key. However, any pending requests
1696 : // on the doomed entry were not processed and we need to do that here.
1697 : // This must be done after adding the created entry to list of active
1698 : // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
1699 : // (see bug ##561313). It is also important to do this after creating a
1700 : // descriptor for this request, or some other request may end up being
1701 : // executed first for the newly created entry.
1702 : // Finally, it is worth to emphasize that if doomedEntry is set,
1703 : // ActivateEntry() created a new entry for the request, which will be
1704 : // initialized by RequestAccess() and they both should have returned NS_OK.
1705 2183 : if (doomedEntry) {
1706 274 : (void) ProcessPendingRequests(doomedEntry);
1707 274 : if (doomedEntry->IsNotInUse())
1708 118 : DeactivateEntry(doomedEntry);
1709 274 : doomedEntry = nsnull;
1710 : }
1711 :
1712 2183 : if (request->mListener) { // Asynchronous
1713 :
1714 1328 : if (NS_FAILED(rv) && calledFromOpenCacheEntry)
1715 0 : return rv; // skip notifying listener, just return rv to caller
1716 :
1717 : // call listener to report error or descriptor
1718 1328 : nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
1719 1328 : if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
1720 0 : rv = rv2; // trigger delete request
1721 : }
1722 : } else { // Synchronous
1723 855 : *result = descriptor;
1724 : }
1725 2183 : return rv;
1726 : }
1727 :
1728 :
1729 : nsresult
1730 2739 : nsCacheService::OpenCacheEntry(nsCacheSession * session,
1731 : const nsACString & key,
1732 : nsCacheAccessMode accessRequested,
1733 : bool blockingMode,
1734 : nsICacheListener * listener,
1735 : nsICacheEntryDescriptor ** result)
1736 : {
1737 2739 : CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
1738 : session, PromiseFlatCString(key).get(), accessRequested,
1739 : blockingMode));
1740 2739 : NS_ASSERTION(gService, "nsCacheService::gService is null.");
1741 2739 : if (result)
1742 855 : *result = nsnull;
1743 :
1744 2739 : if (!gService->mInitialized)
1745 0 : return NS_ERROR_NOT_INITIALIZED;
1746 :
1747 2739 : nsCacheRequest * request = nsnull;
1748 :
1749 : nsresult rv = gService->CreateRequest(session,
1750 : key,
1751 : accessRequested,
1752 : blockingMode,
1753 : listener,
1754 2739 : &request);
1755 2739 : if (NS_FAILED(rv)) return rv;
1756 :
1757 2739 : CACHE_LOG_DEBUG(("Created request %p\n", request));
1758 :
1759 : // Process the request on the background thread if we are on the main thread
1760 : // and the the request is asynchronous
1761 2739 : if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
1762 : nsCOMPtr<nsIRunnable> ev =
1763 3768 : new nsProcessRequestEvent(request);
1764 1884 : rv = DispatchToCacheIOThread(ev);
1765 :
1766 : // delete request if we didn't post the event
1767 1884 : if (NS_FAILED(rv))
1768 0 : delete request;
1769 : }
1770 : else {
1771 :
1772 1710 : nsCacheServiceAutoLock lock;
1773 855 : rv = gService->ProcessRequest(request, true, result);
1774 :
1775 : // delete requests that have completed
1776 855 : if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
1777 855 : delete request;
1778 : }
1779 :
1780 2739 : return rv;
1781 : }
1782 :
1783 :
1784 : nsresult
1785 3292 : nsCacheService::ActivateEntry(nsCacheRequest * request,
1786 : nsCacheEntry ** result,
1787 : nsCacheEntry ** doomedEntry)
1788 : {
1789 3292 : CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
1790 :
1791 3292 : nsresult rv = NS_OK;
1792 :
1793 3292 : NS_ASSERTION(request != nsnull, "ActivateEntry called with no request");
1794 3292 : if (result) *result = nsnull;
1795 3292 : if (doomedEntry) *doomedEntry = nsnull;
1796 3292 : if ((!request) || (!result) || (!doomedEntry))
1797 0 : return NS_ERROR_NULL_POINTER;
1798 :
1799 : // check if the request can be satisfied
1800 3292 : if (!mEnableMemoryDevice && !request->IsStreamBased())
1801 0 : return NS_ERROR_FAILURE;
1802 3292 : if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
1803 1 : return NS_ERROR_FAILURE;
1804 :
1805 : // search active entries (including those not bound to device)
1806 3291 : nsCacheEntry *entry = mActiveEntries.GetEntry(request->mKey);
1807 3291 : CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
1808 :
1809 3291 : if (!entry) {
1810 : // search cache devices for entry
1811 2013 : bool collision = false;
1812 2013 : entry = SearchCacheDevices(request->mKey, request->StoragePolicy(), &collision);
1813 2013 : CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
1814 : request, entry));
1815 : // When there is a hashkey collision just refuse to cache it...
1816 2013 : if (collision) return NS_ERROR_CACHE_IN_USE;
1817 :
1818 2013 : if (entry) entry->MarkInitialized();
1819 : } else {
1820 1278 : NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
1821 : }
1822 :
1823 3291 : if (entry) {
1824 1770 : ++mCacheHits;
1825 1770 : entry->Fetched();
1826 : } else {
1827 1521 : ++mCacheMisses;
1828 : }
1829 :
1830 8443 : if (entry &&
1831 1770 : ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
1832 1496 : ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
1833 1490 : (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
1834 396 : request->WillDoomEntriesIfExpired()))))
1835 :
1836 : {
1837 : // this is FORCE-WRITE request or the entry has expired
1838 : // we doom entry without processing pending requests, but store it in
1839 : // doomedEntry which causes pending requests to be processed below
1840 274 : rv = DoomEntry_Internal(entry, false);
1841 274 : *doomedEntry = entry;
1842 274 : if (NS_FAILED(rv)) {
1843 : // XXX what to do? Increment FailedDooms counter?
1844 : }
1845 274 : entry = nsnull;
1846 : }
1847 :
1848 3291 : if (!entry) {
1849 1795 : if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
1850 : // this is a READ-ONLY request
1851 695 : rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
1852 695 : goto error;
1853 : }
1854 :
1855 : entry = new nsCacheEntry(request->mKey,
1856 1100 : request->IsStreamBased(),
1857 2200 : request->StoragePolicy());
1858 1100 : if (!entry)
1859 0 : return NS_ERROR_OUT_OF_MEMORY;
1860 :
1861 1100 : entry->Fetched();
1862 1100 : ++mTotalEntries;
1863 :
1864 : // XXX we could perform an early bind in some cases based on storage policy
1865 : }
1866 :
1867 2596 : if (!entry->IsActive()) {
1868 1474 : rv = mActiveEntries.AddEntry(entry);
1869 1474 : if (NS_FAILED(rv)) goto error;
1870 1474 : CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
1871 1474 : entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
1872 : }
1873 2596 : *result = entry;
1874 2596 : return NS_OK;
1875 :
1876 : error:
1877 695 : *result = nsnull;
1878 695 : delete entry;
1879 695 : return rv;
1880 : }
1881 :
1882 :
1883 : nsCacheEntry *
1884 2013 : nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
1885 : {
1886 4026 : Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH> timer;
1887 2013 : nsCacheEntry * entry = nsnull;
1888 :
1889 2013 : CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
1890 :
1891 2013 : *collision = false;
1892 2013 : if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
1893 : // If there is no memory device, then there is nothing to search...
1894 1954 : if (mMemoryDevice) {
1895 409 : entry = mMemoryDevice->FindEntry(key, collision);
1896 409 : CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
1897 : "collision: %d\n", key->get(), entry, collision));
1898 : }
1899 : }
1900 :
1901 2013 : if (!entry &&
1902 : ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
1903 :
1904 1761 : if (mEnableDiskDevice) {
1905 1428 : if (!mDiskDevice) {
1906 164 : nsresult rv = CreateDiskDevice();
1907 164 : if (NS_FAILED(rv))
1908 0 : return nsnull;
1909 : }
1910 :
1911 1428 : entry = mDiskDevice->FindEntry(key, collision);
1912 : }
1913 : }
1914 :
1915 3487 : if (!entry && (policy == nsICache::STORE_OFFLINE ||
1916 : (policy == nsICache::STORE_ANYWHERE &&
1917 1474 : gIOService->IsOffline()))) {
1918 :
1919 49 : if (mEnableOfflineDevice) {
1920 49 : if (!mOfflineDevice) {
1921 5 : nsresult rv = CreateOfflineDevice();
1922 5 : if (NS_FAILED(rv))
1923 0 : return nsnull;
1924 : }
1925 :
1926 49 : entry = mOfflineDevice->FindEntry(key, collision);
1927 : }
1928 : }
1929 :
1930 2013 : return entry;
1931 : }
1932 :
1933 :
1934 : nsCacheDevice *
1935 4303 : nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
1936 : {
1937 4303 : nsCacheDevice * device = entry->CacheDevice();
1938 : // return device if found, possibly null if the entry is doomed i.e prevent
1939 : // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
1940 4303 : if (device || entry->IsDoomed()) return device;
1941 :
1942 814 : PRInt64 predictedDataSize = entry->PredictedDataSize();
1943 814 : if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
1944 : // this is the default
1945 552 : if (!mDiskDevice) {
1946 0 : (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
1947 : }
1948 :
1949 552 : if (mDiskDevice) {
1950 : // Bypass the cache if Content-Length says the entry will be too big
1951 1620 : if (predictedDataSize != -1 &&
1952 534 : entry->StoragePolicy() != nsICache::STORE_ON_DISK_AS_FILE &&
1953 534 : mDiskDevice->EntryIsTooBig(predictedDataSize)) {
1954 2 : DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
1955 1 : NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
1956 1 : return nsnull;
1957 : }
1958 :
1959 551 : entry->MarkBinding(); // enter state of binding
1960 551 : nsresult rv = mDiskDevice->BindEntry(entry);
1961 551 : entry->ClearBinding(); // exit state of binding
1962 551 : if (NS_SUCCEEDED(rv))
1963 551 : device = mDiskDevice;
1964 : }
1965 : }
1966 :
1967 : // if we can't use mDiskDevice, try mMemoryDevice
1968 813 : if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
1969 237 : if (!mMemoryDevice) {
1970 90 : (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
1971 : }
1972 237 : if (mMemoryDevice) {
1973 : // Bypass the cache if Content-Length says entry will be too big
1974 433 : if (predictedDataSize != -1 &&
1975 196 : mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
1976 2 : DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
1977 1 : NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
1978 1 : return nsnull;
1979 : }
1980 :
1981 236 : entry->MarkBinding(); // enter state of binding
1982 236 : nsresult rv = mMemoryDevice->BindEntry(entry);
1983 236 : entry->ClearBinding(); // exit state of binding
1984 236 : if (NS_SUCCEEDED(rv))
1985 236 : device = mMemoryDevice;
1986 : }
1987 : }
1988 :
1989 837 : if (!device && entry->IsStreamData() &&
1990 25 : entry->IsAllowedOffline() && mEnableOfflineDevice) {
1991 25 : if (!mOfflineDevice) {
1992 0 : (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
1993 : }
1994 :
1995 25 : if (mOfflineDevice) {
1996 25 : entry->MarkBinding();
1997 25 : nsresult rv = mOfflineDevice->BindEntry(entry);
1998 25 : entry->ClearBinding();
1999 25 : if (NS_SUCCEEDED(rv))
2000 25 : device = mOfflineDevice;
2001 : }
2002 : }
2003 :
2004 812 : if (device)
2005 812 : entry->SetCacheDevice(device);
2006 812 : return device;
2007 : }
2008 :
2009 : PRInt64
2010 0 : nsCacheService::MemoryDeviceSize()
2011 : {
2012 0 : nsMemoryCacheDevice *memoryDevice = GlobalInstance()->mMemoryDevice;
2013 0 : return memoryDevice ? memoryDevice->TotalSize() : 0;
2014 : }
2015 :
2016 : nsresult
2017 249 : nsCacheService::DoomEntry(nsCacheEntry * entry)
2018 : {
2019 249 : return gService->DoomEntry_Internal(entry, true);
2020 : }
2021 :
2022 :
2023 : nsresult
2024 530 : nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
2025 : bool doProcessPendingRequests)
2026 : {
2027 530 : if (entry->IsDoomed()) return NS_OK;
2028 :
2029 488 : CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
2030 488 : nsresult rv = NS_OK;
2031 488 : entry->MarkDoomed();
2032 :
2033 488 : NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
2034 488 : nsCacheDevice * device = entry->CacheDevice();
2035 488 : if (device) device->DoomEntry(entry);
2036 :
2037 488 : if (entry->IsActive()) {
2038 : // remove from active entries
2039 363 : mActiveEntries.RemoveEntry(entry);
2040 363 : CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
2041 363 : entry->MarkInactive();
2042 : }
2043 :
2044 : // put on doom list to wait for descriptors to close
2045 488 : NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
2046 488 : PR_APPEND_LINK(entry, &mDoomedEntries);
2047 :
2048 : // handle pending requests only if we're supposed to
2049 488 : if (doProcessPendingRequests) {
2050 : // tell pending requests to get on with their lives...
2051 214 : rv = ProcessPendingRequests(entry);
2052 :
2053 : // All requests have been removed, but there may still be open descriptors
2054 214 : if (entry->IsNotInUse()) {
2055 0 : DeactivateEntry(entry); // tell device to get rid of it
2056 : }
2057 : }
2058 488 : return rv;
2059 : }
2060 :
2061 :
2062 : void
2063 178 : nsCacheService::OnProfileShutdown(bool cleanse)
2064 : {
2065 178 : if (!gService) return;
2066 178 : if (!gService->mInitialized) {
2067 : // The cache service has been shut down, but someone is still holding
2068 : // a reference to it. Ignore this call.
2069 0 : return;
2070 : }
2071 356 : nsCacheServiceAutoLock lock;
2072 :
2073 178 : gService->DoomActiveEntries();
2074 178 : gService->ClearDoomList();
2075 :
2076 : // Make sure to wait for any pending cache-operations before
2077 : // proceeding with destructive actions (bug #620660)
2078 178 : (void) SyncWithCacheIOThread();
2079 :
2080 178 : if (gService->mDiskDevice && gService->mEnableDiskDevice) {
2081 175 : if (cleanse)
2082 0 : gService->mDiskDevice->EvictEntries(nsnull);
2083 :
2084 175 : gService->mDiskDevice->Shutdown();
2085 : }
2086 178 : gService->mEnableDiskDevice = false;
2087 :
2088 178 : if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
2089 14 : if (cleanse)
2090 0 : gService->mOfflineDevice->EvictEntries(nsnull);
2091 :
2092 14 : gService->mOfflineDevice->Shutdown();
2093 : }
2094 178 : gService->mEnableOfflineDevice = false;
2095 :
2096 178 : if (gService->mMemoryDevice) {
2097 : // clear memory cache
2098 3 : gService->mMemoryDevice->EvictEntries(nsnull);
2099 : }
2100 :
2101 : }
2102 :
2103 :
2104 : void
2105 0 : nsCacheService::OnProfileChanged()
2106 : {
2107 0 : if (!gService) return;
2108 :
2109 0 : CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
2110 :
2111 0 : nsCacheServiceAutoLock lock;
2112 :
2113 0 : gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2114 0 : gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2115 0 : gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2116 :
2117 0 : if (gService->mDiskDevice) {
2118 0 : gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
2119 0 : gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
2120 :
2121 : // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
2122 0 : nsresult rv = gService->mDiskDevice->Init();
2123 0 : if (NS_FAILED(rv)) {
2124 0 : NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
2125 0 : gService->mEnableDiskDevice = false;
2126 : // XXX delete mDiskDevice?
2127 : }
2128 : }
2129 :
2130 0 : if (gService->mOfflineDevice) {
2131 0 : gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
2132 0 : gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
2133 :
2134 : // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
2135 0 : nsresult rv = gService->mOfflineDevice->Init();
2136 0 : if (NS_FAILED(rv)) {
2137 0 : NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
2138 0 : gService->mEnableOfflineDevice = false;
2139 : // XXX delete mOfflineDevice?
2140 : }
2141 : }
2142 :
2143 : // If memoryDevice exists, reset its size to the new profile
2144 0 : if (gService->mMemoryDevice) {
2145 0 : if (gService->mEnableMemoryDevice) {
2146 : // make sure that capacity is reset to the right value
2147 0 : PRInt32 capacity = gService->mObserver->MemoryCacheCapacity();
2148 0 : CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2149 : capacity));
2150 0 : gService->mMemoryDevice->SetCapacity(capacity);
2151 : } else {
2152 : // tell memory device to evict everything
2153 0 : CACHE_LOG_DEBUG(("memory device disabled\n"));
2154 0 : gService->mMemoryDevice->SetCapacity(0);
2155 : // Don't delete memory device, because some entries may be active still...
2156 : }
2157 : }
2158 : }
2159 :
2160 :
2161 : void
2162 8 : nsCacheService::SetDiskCacheEnabled(bool enabled)
2163 : {
2164 8 : if (!gService) return;
2165 16 : nsCacheServiceAutoLock lock;
2166 8 : gService->mEnableDiskDevice = enabled;
2167 : }
2168 :
2169 :
2170 : void
2171 177 : nsCacheService::SetDiskCacheCapacity(PRInt32 capacity)
2172 : {
2173 177 : if (!gService) return;
2174 354 : nsCacheServiceAutoLock lock;
2175 :
2176 177 : if (gService->mDiskDevice) {
2177 1 : gService->mDiskDevice->SetCapacity(capacity);
2178 : }
2179 :
2180 177 : if (gService->mObserver)
2181 177 : gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2182 : }
2183 :
2184 : void
2185 2 : nsCacheService::SetDiskCacheMaxEntrySize(PRInt32 maxSize)
2186 : {
2187 2 : if (!gService) return;
2188 4 : nsCacheServiceAutoLock lock;
2189 :
2190 2 : if (gService->mDiskDevice) {
2191 2 : gService->mDiskDevice->SetMaxEntrySize(maxSize);
2192 : }
2193 : }
2194 :
2195 : void
2196 2 : nsCacheService::SetMemoryCacheMaxEntrySize(PRInt32 maxSize)
2197 : {
2198 2 : if (!gService) return;
2199 4 : nsCacheServiceAutoLock lock;
2200 :
2201 2 : if (gService->mMemoryDevice) {
2202 2 : gService->mMemoryDevice->SetMaxEntrySize(maxSize);
2203 : }
2204 : }
2205 :
2206 : void
2207 6 : nsCacheService::SetOfflineCacheEnabled(bool enabled)
2208 : {
2209 6 : if (!gService) return;
2210 12 : nsCacheServiceAutoLock lock;
2211 6 : gService->mEnableOfflineDevice = enabled;
2212 : }
2213 :
2214 : void
2215 0 : nsCacheService::SetOfflineCacheCapacity(PRInt32 capacity)
2216 : {
2217 0 : if (!gService) return;
2218 0 : nsCacheServiceAutoLock lock;
2219 :
2220 0 : if (gService->mOfflineDevice) {
2221 0 : gService->mOfflineDevice->SetCapacity(capacity);
2222 : }
2223 :
2224 0 : gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2225 : }
2226 :
2227 :
2228 : void
2229 2 : nsCacheService::SetMemoryCache()
2230 : {
2231 2 : if (!gService) return;
2232 :
2233 2 : CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
2234 :
2235 4 : nsCacheServiceAutoLock lock;
2236 :
2237 2 : gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2238 :
2239 2 : if (gService->mEnableMemoryDevice) {
2240 1 : if (gService->mMemoryDevice) {
2241 0 : PRInt32 capacity = gService->mObserver->MemoryCacheCapacity();
2242 : // make sure that capacity is reset to the right value
2243 0 : CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2244 : capacity));
2245 0 : gService->mMemoryDevice->SetCapacity(capacity);
2246 : }
2247 : } else {
2248 1 : if (gService->mMemoryDevice) {
2249 : // tell memory device to evict everything
2250 1 : CACHE_LOG_DEBUG(("memory device disabled\n"));
2251 1 : gService->mMemoryDevice->SetCapacity(0);
2252 : // Don't delete memory device, because some entries may be active still...
2253 : }
2254 : }
2255 : }
2256 :
2257 :
2258 : /******************************************************************************
2259 : * static methods for nsCacheEntryDescriptor
2260 : *****************************************************************************/
2261 : void
2262 1770 : nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
2263 : {
2264 : // ask entry to remove descriptor
2265 1770 : nsCacheEntry * entry = descriptor->CacheEntry();
2266 1770 : bool stillActive = entry->RemoveDescriptor(descriptor);
2267 1770 : nsresult rv = NS_OK;
2268 :
2269 1770 : if (!entry->IsValid()) {
2270 1474 : rv = gService->ProcessPendingRequests(entry);
2271 : }
2272 :
2273 1770 : if (!stillActive) {
2274 1466 : gService->DeactivateEntry(entry);
2275 : }
2276 1770 : }
2277 :
2278 :
2279 : nsresult
2280 0 : nsCacheService::GetFileForEntry(nsCacheEntry * entry,
2281 : nsIFile ** result)
2282 : {
2283 0 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2284 0 : if (!device) return NS_ERROR_UNEXPECTED;
2285 :
2286 0 : return device->GetFileForEntry(entry, result);
2287 : }
2288 :
2289 :
2290 : nsresult
2291 242 : nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
2292 : nsCacheAccessMode mode,
2293 : PRUint32 offset,
2294 : nsIInputStream ** result)
2295 : {
2296 242 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2297 242 : if (!device) return NS_ERROR_UNEXPECTED;
2298 :
2299 242 : return device->OpenInputStreamForEntry(entry, mode, offset, result);
2300 : }
2301 :
2302 : nsresult
2303 1253 : nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
2304 : nsCacheAccessMode mode,
2305 : PRUint32 offset,
2306 : nsIOutputStream ** result)
2307 : {
2308 1253 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2309 1253 : if (!device) return NS_ERROR_UNEXPECTED;
2310 :
2311 1134 : return device->OpenOutputStreamForEntry(entry, mode, offset, result);
2312 : }
2313 :
2314 :
2315 : nsresult
2316 1496 : nsCacheService::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
2317 : {
2318 1496 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2319 1496 : if (!device) return NS_ERROR_UNEXPECTED;
2320 :
2321 1496 : return device->OnDataSizeChange(entry, deltaSize);
2322 : }
2323 :
2324 : void
2325 28711 : nsCacheService::Lock()
2326 : {
2327 28711 : if (NS_IsMainThread()) {
2328 40468 : Telemetry::AutoTimer<Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD> timer;
2329 20234 : gService->mLock.Lock();
2330 : } else {
2331 16954 : Telemetry::AutoTimer<Telemetry::CACHE_SERVICE_LOCK_WAIT> timer;
2332 8477 : gService->mLock.Lock();
2333 : }
2334 28711 : }
2335 :
2336 : void
2337 28711 : nsCacheService::Unlock()
2338 : {
2339 28711 : gService->mLock.AssertCurrentThreadOwns();
2340 :
2341 57422 : nsTArray<nsISupports*> doomed;
2342 28711 : doomed.SwapElements(gService->mDoomedObjects);
2343 :
2344 28711 : gService->mLock.Unlock();
2345 :
2346 29820 : for (PRUint32 i = 0; i < doomed.Length(); ++i)
2347 1109 : doomed[i]->Release();
2348 28711 : }
2349 :
2350 : void
2351 1338 : nsCacheService::ReleaseObject_Locked(nsISupports * obj,
2352 : nsIEventTarget * target)
2353 : {
2354 1338 : gService->mLock.AssertCurrentThreadOwns();
2355 :
2356 : bool isCur;
2357 1338 : if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
2358 1109 : gService->mDoomedObjects.AppendElement(obj);
2359 : } else {
2360 229 : NS_ProxyRelease(target, obj);
2361 : }
2362 1338 : }
2363 :
2364 :
2365 : nsresult
2366 0 : nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
2367 : {
2368 0 : entry->SetData(element);
2369 0 : entry->TouchData();
2370 0 : return NS_OK;
2371 : }
2372 :
2373 :
2374 : nsresult
2375 212 : nsCacheService::ValidateEntry(nsCacheEntry * entry)
2376 : {
2377 212 : nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2378 212 : if (!device) return NS_ERROR_UNEXPECTED;
2379 :
2380 212 : entry->MarkValid();
2381 212 : nsresult rv = gService->ProcessPendingRequests(entry);
2382 212 : NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
2383 : // XXX what else should be done?
2384 :
2385 212 : return rv;
2386 : }
2387 :
2388 :
2389 : PRInt32
2390 1194 : nsCacheService::CacheCompressionLevel()
2391 : {
2392 1194 : PRInt32 level = gService->mObserver->CacheCompressionLevel();
2393 1194 : return level;
2394 : }
2395 :
2396 :
2397 : void
2398 1592 : nsCacheService::DeactivateEntry(nsCacheEntry * entry)
2399 : {
2400 1592 : CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
2401 1592 : nsresult rv = NS_OK;
2402 1592 : NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
2403 1592 : nsCacheDevice * device = nsnull;
2404 :
2405 1592 : if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
2406 1592 : if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
2407 :
2408 1592 : if (entry->IsDoomed()) {
2409 : // remove from Doomed list
2410 488 : PR_REMOVE_AND_INIT_LINK(entry);
2411 1104 : } else if (entry->IsActive()) {
2412 : // remove from active entries
2413 1100 : mActiveEntries.RemoveEntry(entry);
2414 1100 : CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
2415 : entry));
2416 1100 : entry->MarkInactive();
2417 :
2418 : // bind entry if necessary to store meta-data
2419 1100 : device = EnsureEntryHasDevice(entry);
2420 1100 : if (!device) {
2421 0 : CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
2422 : "entry %p\n",
2423 : entry));
2424 0 : NS_WARNING("DeactivateEntry: unable to bind active entry\n");
2425 0 : return;
2426 : }
2427 : } else {
2428 : // if mInitialized == false,
2429 : // then we're shutting down and this state is okay.
2430 4 : NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
2431 : }
2432 :
2433 1592 : device = entry->CacheDevice();
2434 1592 : if (device) {
2435 1304 : rv = device->DeactivateEntry(entry);
2436 1304 : if (NS_FAILED(rv)) {
2437 : // increment deactivate failure count
2438 0 : ++mDeactivateFailures;
2439 : }
2440 : } else {
2441 : // increment deactivating unbound entry statistic
2442 288 : ++mDeactivatedUnboundEntries;
2443 288 : delete entry; // because no one else will
2444 : }
2445 : }
2446 :
2447 :
2448 : nsresult
2449 2174 : nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
2450 : {
2451 2174 : nsresult rv = NS_OK;
2452 2174 : nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2453 : nsCacheRequest * nextRequest;
2454 2174 : bool newWriter = false;
2455 :
2456 2174 : CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
2457 : (entry->IsInitialized()?"" : "Un"),
2458 : (entry->IsDoomed()?"DOOMED" : ""),
2459 : (entry->IsValid()? "V":"Inv"), entry));
2460 :
2461 2174 : if (request == &entry->mRequestQ) return NS_OK; // no queued requests
2462 :
2463 319 : if (!entry->IsDoomed() && entry->IsInvalid()) {
2464 : // 1st descriptor closed w/o MarkValid()
2465 294 : NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
2466 :
2467 : #if DEBUG
2468 : // verify no ACCESS_WRITE requests(shouldn't have any of these)
2469 885 : while (request != &entry->mRequestQ) {
2470 297 : NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
2471 : "ACCESS_WRITE request should have been given a new entry");
2472 297 : request = (nsCacheRequest *)PR_NEXT_LINK(request);
2473 : }
2474 294 : request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2475 : #endif
2476 : // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
2477 588 : while (request != &entry->mRequestQ) {
2478 294 : if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
2479 294 : newWriter = true;
2480 294 : CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
2481 294 : break;
2482 : }
2483 :
2484 0 : request = (nsCacheRequest *)PR_NEXT_LINK(request);
2485 : }
2486 :
2487 294 : if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
2488 0 : request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2489 :
2490 : // XXX what should we do if there are only READ requests in queue?
2491 : // XXX serialize their accesses, give them only read access, but force them to check validate flag?
2492 : // XXX or do readers simply presume the entry is valid
2493 : // See fix for bug #467392 below
2494 : }
2495 :
2496 319 : nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
2497 :
2498 1191 : while (request != &entry->mRequestQ) {
2499 847 : nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
2500 847 : CACHE_LOG_DEBUG((" %sync request %p for %p\n",
2501 : (request->mListener?"As":"S"), request, entry));
2502 :
2503 847 : if (request->mListener) {
2504 :
2505 : // Async request
2506 847 : PR_REMOVE_AND_INIT_LINK(request);
2507 :
2508 847 : if (entry->IsDoomed()) {
2509 553 : rv = ProcessRequest(request, false, nsnull);
2510 553 : if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
2511 528 : rv = NS_OK;
2512 : else
2513 25 : delete request;
2514 :
2515 553 : if (NS_FAILED(rv)) {
2516 : // XXX what to do?
2517 : }
2518 294 : } else if (entry->IsValid() || newWriter) {
2519 294 : rv = entry->RequestAccess(request, &accessGranted);
2520 294 : NS_ASSERTION(NS_SUCCEEDED(rv),
2521 : "if entry is valid, RequestAccess must succeed.");
2522 : // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
2523 :
2524 : // entry->CreateDescriptor dequeues request, and queues descriptor
2525 294 : nsICacheEntryDescriptor *descriptor = nsnull;
2526 : rv = entry->CreateDescriptor(request,
2527 : accessGranted,
2528 294 : &descriptor);
2529 :
2530 : // post call to listener to report error or descriptor
2531 294 : rv = NotifyListener(request, descriptor, accessGranted, rv);
2532 294 : delete request;
2533 294 : if (NS_FAILED(rv)) {
2534 : // XXX what to do?
2535 : }
2536 :
2537 : } else {
2538 : // read-only request to an invalid entry - need to wait for
2539 : // the entry to become valid so we post an event to process
2540 : // the request again later (bug #467392)
2541 : nsCOMPtr<nsIRunnable> ev =
2542 0 : new nsProcessRequestEvent(request);
2543 0 : rv = DispatchToCacheIOThread(ev);
2544 0 : if (NS_FAILED(rv)) {
2545 0 : delete request; // avoid leak
2546 : }
2547 : }
2548 : } else {
2549 :
2550 : // Synchronous request
2551 0 : request->WakeUp();
2552 : }
2553 847 : if (newWriter) break; // process remaining requests after validation
2554 553 : request = nextRequest;
2555 : }
2556 :
2557 319 : return NS_OK;
2558 : }
2559 :
2560 :
2561 : void
2562 4 : nsCacheService::ClearPendingRequests(nsCacheEntry * entry)
2563 : {
2564 4 : nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2565 :
2566 270 : while (request != &entry->mRequestQ) {
2567 262 : nsCacheRequest * next = (nsCacheRequest *)PR_NEXT_LINK(request);
2568 :
2569 : // XXX we're just dropping these on the floor for now...definitely wrong.
2570 262 : PR_REMOVE_AND_INIT_LINK(request);
2571 262 : delete request;
2572 262 : request = next;
2573 : }
2574 4 : }
2575 :
2576 :
2577 : void
2578 447 : nsCacheService::ClearDoomList()
2579 : {
2580 447 : nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2581 :
2582 898 : while (entry != &mDoomedEntries) {
2583 4 : nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2584 :
2585 4 : entry->DetachDescriptors();
2586 4 : DeactivateEntry(entry);
2587 4 : entry = next;
2588 : }
2589 447 : }
2590 :
2591 :
2592 : void
2593 269 : nsCacheService::ClearActiveEntries()
2594 : {
2595 269 : mActiveEntries.VisitEntries(DeactivateAndClearEntry, nsnull);
2596 269 : mActiveEntries.Shutdown();
2597 269 : }
2598 :
2599 :
2600 : PLDHashOperator
2601 4 : nsCacheService::DeactivateAndClearEntry(PLDHashTable * table,
2602 : PLDHashEntryHdr * hdr,
2603 : PRUint32 number,
2604 : void * arg)
2605 : {
2606 4 : nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
2607 4 : NS_ASSERTION(entry, "### active entry = nsnull!");
2608 : // only called from Shutdown() so we don't worry about pending requests
2609 4 : gService->ClearPendingRequests(entry);
2610 4 : entry->DetachDescriptors();
2611 :
2612 4 : entry->MarkInactive(); // so we don't call Remove() while we're enumerating
2613 4 : gService->DeactivateEntry(entry);
2614 :
2615 4 : return PL_DHASH_REMOVE; // and continue enumerating
2616 : }
2617 :
2618 :
2619 : void
2620 184 : nsCacheService::DoomActiveEntries()
2621 : {
2622 368 : nsAutoTArray<nsCacheEntry*, 8> array;
2623 :
2624 184 : mActiveEntries.VisitEntries(RemoveActiveEntry, &array);
2625 :
2626 184 : PRUint32 count = array.Length();
2627 191 : for (PRUint32 i=0; i < count; ++i)
2628 7 : DoomEntry_Internal(array[i], true);
2629 184 : }
2630 :
2631 :
2632 : PLDHashOperator
2633 7 : nsCacheService::RemoveActiveEntry(PLDHashTable * table,
2634 : PLDHashEntryHdr * hdr,
2635 : PRUint32 number,
2636 : void * arg)
2637 : {
2638 7 : nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
2639 7 : NS_ASSERTION(entry, "### active entry = nsnull!");
2640 :
2641 7 : nsTArray<nsCacheEntry*> * array = (nsTArray<nsCacheEntry*> *) arg;
2642 7 : NS_ASSERTION(array, "### array = nsnull!");
2643 7 : array->AppendElement(entry);
2644 :
2645 : // entry is being removed from the active entry list
2646 7 : entry->MarkInactive();
2647 7 : return PL_DHASH_REMOVE; // and continue enumerating
2648 : }
2649 :
2650 :
2651 : #if defined(PR_LOGGING)
2652 : void
2653 269 : nsCacheService::LogCacheStatistics()
2654 : {
2655 : PRUint32 hitPercentage = (PRUint32)((((double)mCacheHits) /
2656 269 : ((double)(mCacheHits + mCacheMisses))) * 100);
2657 269 : CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
2658 269 : CACHE_LOG_ALWAYS((" TotalEntries = %d\n", mTotalEntries));
2659 269 : CACHE_LOG_ALWAYS((" Cache Hits = %d\n", mCacheHits));
2660 269 : CACHE_LOG_ALWAYS((" Cache Misses = %d\n", mCacheMisses));
2661 269 : CACHE_LOG_ALWAYS((" Cache Hit %% = %d%%\n", hitPercentage));
2662 269 : CACHE_LOG_ALWAYS((" Max Key Length = %d\n", mMaxKeyLength));
2663 269 : CACHE_LOG_ALWAYS((" Max Meta Size = %d\n", mMaxMetaSize));
2664 269 : CACHE_LOG_ALWAYS((" Max Data Size = %d\n", mMaxDataSize));
2665 269 : CACHE_LOG_ALWAYS(("\n"));
2666 269 : CACHE_LOG_ALWAYS((" Deactivate Failures = %d\n",
2667 : mDeactivateFailures));
2668 269 : CACHE_LOG_ALWAYS((" Deactivated Unbound Entries = %d\n",
2669 : mDeactivatedUnboundEntries));
2670 269 : }
2671 : #endif
2672 :
2673 :
2674 : void
2675 6 : nsCacheService::OnEnterExitPrivateBrowsing()
2676 : {
2677 6 : if (!gService) return;
2678 12 : nsCacheServiceAutoLock lock;
2679 :
2680 6 : gService->DoomActiveEntries();
2681 :
2682 6 : if (gService->mMemoryDevice) {
2683 : // clear memory cache
2684 2 : gService->mMemoryDevice->EvictEntries(nsnull);
2685 : }
2686 : }
2687 :
2688 : nsresult
2689 0 : nsCacheService::SetDiskSmartSize()
2690 : {
2691 0 : nsCacheServiceAutoLock lock;
2692 :
2693 0 : if (!gService) return NS_ERROR_NOT_AVAILABLE;
2694 :
2695 0 : return gService->SetDiskSmartSize_Locked();
2696 : }
2697 :
2698 : nsresult
2699 0 : nsCacheService::SetDiskSmartSize_Locked()
2700 : {
2701 : nsresult rv;
2702 :
2703 0 : if (!mObserver->DiskCacheParentDirectory())
2704 0 : return NS_ERROR_NOT_AVAILABLE;
2705 :
2706 0 : if (!mDiskDevice)
2707 0 : return NS_ERROR_NOT_AVAILABLE;
2708 :
2709 0 : if (!mObserver->SmartSizeEnabled())
2710 0 : return NS_ERROR_NOT_AVAILABLE;
2711 :
2712 0 : nsAutoString cachePath;
2713 0 : rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
2714 0 : if (NS_SUCCEEDED(rv)) {
2715 : nsCOMPtr<nsIRunnable> event =
2716 0 : new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize());
2717 0 : DispatchToCacheIOThread(event);
2718 : } else {
2719 0 : return NS_ERROR_FAILURE;
2720 : }
2721 :
2722 0 : return NS_OK;
2723 4392 : }
|