Coverage Report

Created: 2018-09-25 14:53

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