Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/cache2/CacheEntry.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "CacheLog.h"
6
#include "CacheEntry.h"
7
#include "CacheStorageService.h"
8
#include "CacheObserver.h"
9
#include "CacheFileUtils.h"
10
#include "CacheIndex.h"
11
12
#include "nsIInputStream.h"
13
#include "nsIOutputStream.h"
14
#include "nsISeekableStream.h"
15
#include "nsIURI.h"
16
#include "nsICacheEntryOpenCallback.h"
17
#include "nsICacheStorage.h"
18
#include "nsISerializable.h"
19
#include "nsIStreamTransportService.h"
20
#include "nsISizeOf.h"
21
22
#include "nsComponentManagerUtils.h"
23
#include "nsServiceManagerUtils.h"
24
#include "nsString.h"
25
#include "nsProxyRelease.h"
26
#include "nsSerializationHelper.h"
27
#include "nsThreadUtils.h"
28
#include "mozilla/Telemetry.h"
29
#include "mozilla/IntegerPrintfMacros.h"
30
#include <math.h>
31
#include <algorithm>
32
33
namespace mozilla {
34
namespace net {
35
36
static uint32_t const ENTRY_WANTED =
37
  nsICacheEntryOpenCallback::ENTRY_WANTED;
38
static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
39
  nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
40
static uint32_t const ENTRY_NEEDS_REVALIDATION =
41
  nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
42
static uint32_t const ENTRY_NOT_WANTED =
43
  nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
44
45
NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
46
47
// CacheEntryHandle
48
49
CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry)
50
: mEntry(aEntry)
51
, mClosed(false)
52
0
{
53
#ifdef DEBUG
54
  if (!mEntry->HandlesCount()) {
55
    // CacheEntry.mHandlesCount must go from zero to one only under
56
    // the service lock. Can access CacheStorageService::Self() w/o a check
57
    // since CacheEntry hrefs it.
58
    CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
59
  }
60
#endif
61
62
0
  mEntry->AddHandleRef();
63
0
64
0
  LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
65
0
}
66
67
NS_IMETHODIMP CacheEntryHandle::Dismiss()
68
0
{
69
0
  LOG(("CacheEntryHandle::Dismiss %p", this));
70
0
71
0
  if (mClosed.compareExchange(false, true)) {
72
0
    mEntry->OnHandleClosed(this);
73
0
    return NS_OK;
74
0
  }
75
0
76
0
  LOG(("  already dropped"));
77
0
  return NS_ERROR_UNEXPECTED;
78
0
}
79
80
CacheEntryHandle::~CacheEntryHandle()
81
0
{
82
0
  mEntry->ReleaseHandleRef();
83
0
  Dismiss();
84
0
85
0
  LOG(("CacheEntryHandle::~CacheEntryHandle %p", this));
86
0
}
87
88
// CacheEntry::Callback
89
90
CacheEntry::Callback::Callback(CacheEntry* aEntry,
91
                               nsICacheEntryOpenCallback *aCallback,
92
                               bool aReadOnly, bool aCheckOnAnyThread,
93
                               bool aSecret)
94
: mEntry(aEntry)
95
, mCallback(aCallback)
96
, mTarget(GetCurrentThreadEventTarget())
97
, mReadOnly(aReadOnly)
98
, mRevalidating(false)
99
, mCheckOnAnyThread(aCheckOnAnyThread)
100
, mRecheckAfterWrite(false)
101
, mNotWanted(false)
102
, mSecret(aSecret)
103
, mDoomWhenFoundPinned(false)
104
, mDoomWhenFoundNonPinned(false)
105
0
{
106
0
  MOZ_COUNT_CTOR(CacheEntry::Callback);
107
0
108
0
  // The counter may go from zero to non-null only under the service lock
109
0
  // but here we expect it to be already positive.
110
0
  MOZ_ASSERT(mEntry->HandlesCount());
111
0
  mEntry->AddHandleRef();
112
0
}
113
114
CacheEntry::Callback::Callback(CacheEntry* aEntry, bool aDoomWhenFoundInPinStatus)
115
: mEntry(aEntry)
116
, mReadOnly(false)
117
, mRevalidating(false)
118
, mCheckOnAnyThread(true)
119
, mRecheckAfterWrite(false)
120
, mNotWanted(false)
121
, mSecret(false)
122
, mDoomWhenFoundPinned(aDoomWhenFoundInPinStatus == true)
123
, mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false)
124
0
{
125
0
  MOZ_COUNT_CTOR(CacheEntry::Callback);
126
0
  MOZ_ASSERT(mEntry->HandlesCount());
127
0
  mEntry->AddHandleRef();
128
0
}
129
130
CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
131
: mEntry(aThat.mEntry)
132
, mCallback(aThat.mCallback)
133
, mTarget(aThat.mTarget)
134
, mReadOnly(aThat.mReadOnly)
135
, mRevalidating(aThat.mRevalidating)
136
, mCheckOnAnyThread(aThat.mCheckOnAnyThread)
137
, mRecheckAfterWrite(aThat.mRecheckAfterWrite)
138
, mNotWanted(aThat.mNotWanted)
139
, mSecret(aThat.mSecret)
140
, mDoomWhenFoundPinned(aThat.mDoomWhenFoundPinned)
141
, mDoomWhenFoundNonPinned(aThat.mDoomWhenFoundNonPinned)
142
0
{
143
0
  MOZ_COUNT_CTOR(CacheEntry::Callback);
144
0
145
0
  // The counter may go from zero to non-null only under the service lock
146
0
  // but here we expect it to be already positive.
147
0
  MOZ_ASSERT(mEntry->HandlesCount());
148
0
  mEntry->AddHandleRef();
149
0
}
150
151
CacheEntry::Callback::~Callback()
152
0
{
153
0
  ProxyRelease("CacheEntry::Callback::mCallback", mCallback, mTarget);
154
0
155
0
  mEntry->ReleaseHandleRef();
156
0
  MOZ_COUNT_DTOR(CacheEntry::Callback);
157
0
}
158
159
void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
160
0
{
161
0
  if (mEntry == aEntry)
162
0
    return;
163
0
164
0
  // The counter may go from zero to non-null only under the service lock
165
0
  // but here we expect it to be already positive.
166
0
  MOZ_ASSERT(aEntry->HandlesCount());
167
0
  aEntry->AddHandleRef();
168
0
  mEntry->ReleaseHandleRef();
169
0
  mEntry = aEntry;
170
0
}
171
172
bool CacheEntry::Callback::DeferDoom(bool *aDoom) const
173
0
{
174
0
  MOZ_ASSERT(mEntry->mPinningKnown);
175
0
176
0
  if (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) || MOZ_UNLIKELY(mDoomWhenFoundPinned)) {
177
0
    *aDoom = (MOZ_UNLIKELY(mDoomWhenFoundNonPinned) && MOZ_LIKELY(!mEntry->mPinned)) ||
178
0
             (MOZ_UNLIKELY(mDoomWhenFoundPinned) && MOZ_UNLIKELY(mEntry->mPinned));
179
0
180
0
    return true;
181
0
  }
182
0
183
0
  return false;
184
0
}
185
186
nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
187
0
{
188
0
  if (!mCheckOnAnyThread) {
189
0
    // Check we are on the target
190
0
    return mTarget->IsOnCurrentThread(aOnCheckThread);
191
0
  }
192
0
193
0
  // We can invoke check anywhere
194
0
  *aOnCheckThread = true;
195
0
  return NS_OK;
196
0
}
197
198
nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
199
0
{
200
0
  return mTarget->IsOnCurrentThread(aOnAvailThread);
201
0
}
202
203
// CacheEntry
204
205
NS_IMPL_ISUPPORTS(CacheEntry,
206
                  nsIRunnable,
207
                  CacheFileListener)
208
209
/* static */
210
uint64_t CacheEntry::GetNextId()
211
0
{
212
0
  static Atomic<uint64_t, Relaxed> id(0);
213
0
  return ++id;
214
0
}
215
216
CacheEntry::CacheEntry(const nsACString& aStorageID,
217
                       const nsACString& aURI,
218
                       const nsACString& aEnhanceID,
219
                       bool aUseDisk,
220
                       bool aSkipSizeCheck,
221
                       bool aPin)
222
: mFrecency(0)
223
, mSortingExpirationTime(uint32_t(-1))
224
, mLock("CacheEntry")
225
, mFileStatus(NS_ERROR_NOT_INITIALIZED)
226
, mURI(aURI)
227
, mEnhanceID(aEnhanceID)
228
, mStorageID(aStorageID)
229
, mUseDisk(aUseDisk)
230
, mSkipSizeCheck(aSkipSizeCheck)
231
, mIsDoomed(false)
232
, mSecurityInfoLoaded(false)
233
, mPreventCallbacks(false)
234
, mHasData(false)
235
, mPinned(aPin)
236
, mPinningKnown(false)
237
, mState(NOTLOADED)
238
, mRegistration(NEVERREGISTERED)
239
, mWriter(nullptr)
240
, mUseCount(0)
241
, mCacheEntryId(GetNextId())
242
0
{
243
0
  LOG(("CacheEntry::CacheEntry [this=%p]", this));
244
0
245
0
  mService = CacheStorageService::Self();
246
0
247
0
  CacheStorageService::Self()->RecordMemoryOnlyEntry(
248
0
    this, !aUseDisk, true /* overwrite */);
249
0
}
250
251
CacheEntry::~CacheEntry()
252
0
{
253
0
  LOG(("CacheEntry::~CacheEntry [this=%p]", this));
254
0
}
255
256
char const * CacheEntry::StateString(uint32_t aState)
257
0
{
258
0
  switch (aState) {
259
0
  case NOTLOADED:     return "NOTLOADED";
260
0
  case LOADING:       return "LOADING";
261
0
  case EMPTY:         return "EMPTY";
262
0
  case WRITING:       return "WRITING";
263
0
  case READY:         return "READY";
264
0
  case REVALIDATING:  return "REVALIDATING";
265
0
  }
266
0
267
0
  return "?";
268
0
}
269
270
nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult) const
271
0
{
272
0
  return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
273
0
}
274
275
nsresult CacheEntry::HashingKey(nsACString &aResult) const
276
0
{
277
0
  return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
278
0
}
279
280
// static
281
nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
282
                                const nsACString& aEnhanceID,
283
                                nsIURI* aURI,
284
                                nsACString &aResult)
285
0
{
286
0
  nsAutoCString spec;
287
0
  nsresult rv = aURI->GetAsciiSpec(spec);
288
0
  NS_ENSURE_SUCCESS(rv, rv);
289
0
290
0
  return HashingKey(aStorageID, aEnhanceID, spec, aResult);
291
0
}
292
293
// static
294
nsresult CacheEntry::HashingKey(const nsACString& aStorageID,
295
                                const nsACString& aEnhanceID,
296
                                const nsACString& aURISpec,
297
                                nsACString &aResult)
298
0
{
299
0
  /**
300
0
   * This key is used to salt hash that is a base for disk file name.
301
0
   * Changing it will cause we will not be able to find files on disk.
302
0
   */
303
0
304
0
  aResult.Assign(aStorageID);
305
0
306
0
  if (!aEnhanceID.IsEmpty()) {
307
0
    CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
308
0
  }
309
0
310
0
  // Appending directly
311
0
  aResult.Append(':');
312
0
  aResult.Append(aURISpec);
313
0
314
0
  return NS_OK;
315
0
}
316
317
void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
318
0
{
319
0
  LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
320
0
    this, StateString(mState), aFlags, aCallback));
321
0
322
0
  bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
323
0
  bool bypassIfBusy = aFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
324
0
  bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
325
0
  bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
326
0
  bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
327
0
  bool secret = aFlags & nsICacheStorage::OPEN_SECRETLY;
328
0
329
0
  MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
330
0
  MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
331
0
332
0
  Callback callback(this, aCallback, readonly, multithread, secret);
333
0
334
0
  if (!Open(callback, truncate, priority, bypassIfBusy)) {
335
0
    // We get here when the callback wants to bypass cache when it's busy.
336
0
    LOG(("  writing or revalidating, callback wants to bypass cache"));
337
0
    callback.mNotWanted = true;
338
0
    InvokeAvailableCallback(callback);
339
0
  }
340
0
}
341
342
bool CacheEntry::Open(Callback & aCallback, bool aTruncate,
343
                      bool aPriority, bool aBypassIfBusy)
344
0
{
345
0
  mozilla::MutexAutoLock lock(mLock);
346
0
347
0
  // Check state under the lock
348
0
  if (aBypassIfBusy && (mState == WRITING || mState == REVALIDATING)) {
349
0
    return false;
350
0
  }
351
0
352
0
  RememberCallback(aCallback);
353
0
354
0
  // Load() opens the lock
355
0
  if (Load(aTruncate, aPriority)) {
356
0
    // Loading is in progress...
357
0
    return true;
358
0
  }
359
0
360
0
  InvokeCallbacks();
361
0
362
0
  return true;
363
0
}
364
365
bool CacheEntry::Load(bool aTruncate, bool aPriority)
366
0
{
367
0
  LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
368
0
369
0
  mLock.AssertCurrentThreadOwns();
370
0
371
0
  if (mState > LOADING) {
372
0
    LOG(("  already loaded"));
373
0
    return false;
374
0
  }
375
0
376
0
  if (mState == LOADING) {
377
0
    LOG(("  already loading"));
378
0
    return true;
379
0
  }
380
0
381
0
  mState = LOADING;
382
0
383
0
  MOZ_ASSERT(!mFile);
384
0
385
0
  nsresult rv;
386
0
387
0
  nsAutoCString fileKey;
388
0
  rv = HashingKeyWithStorage(fileKey);
389
0
390
0
  bool reportMiss = false;
391
0
392
0
  // Check the index under two conditions for two states and take appropriate action:
393
0
  // 1. When this is a disk entry and not told to truncate, check there is a disk file.
394
0
  //    If not, set the 'truncate' flag to true so that this entry will open instantly
395
0
  //    as a new one.
396
0
  // 2. When this is a memory-only entry, check there is a disk file.
397
0
  //    If there is or could be, doom that file.
398
0
  if ((!aTruncate || !mUseDisk) && NS_SUCCEEDED(rv)) {
399
0
    // Check the index right now to know we have or have not the entry
400
0
    // as soon as possible.
401
0
    CacheIndex::EntryStatus status;
402
0
    if (NS_SUCCEEDED(CacheIndex::HasEntry(fileKey, &status))) {
403
0
      switch (status) {
404
0
      case CacheIndex::DOES_NOT_EXIST:
405
0
        // Doesn't apply to memory-only entries, Load() is called only once for them
406
0
        // and never again for their session lifetime.
407
0
        if (!aTruncate && mUseDisk) {
408
0
          LOG(("  entry doesn't exist according information from the index, truncating"));
409
0
          reportMiss = true;
410
0
          aTruncate = true;
411
0
        }
412
0
        break;
413
0
      case CacheIndex::EXISTS:
414
0
      case CacheIndex::DO_NOT_KNOW:
415
0
        if (!mUseDisk) {
416
0
          LOG(("  entry open as memory-only, but there is a file, status=%d, dooming it", status));
417
0
          CacheFileIOManager::DoomFileByKey(fileKey, nullptr);
418
0
        }
419
0
        break;
420
0
      }
421
0
    }
422
0
  }
423
0
424
0
  mFile = new CacheFile();
425
0
426
0
  BackgroundOp(Ops::REGISTER);
427
0
428
0
  bool directLoad = aTruncate || !mUseDisk;
429
0
  if (directLoad) {
430
0
    // mLoadStart will be used to calculate telemetry of life-time of this entry.
431
0
    // Low resulution is then enough.
432
0
    mLoadStart = TimeStamp::NowLoRes();
433
0
    mPinningKnown = true;
434
0
  } else {
435
0
    mLoadStart = TimeStamp::Now();
436
0
  }
437
0
438
0
  {
439
0
    mozilla::MutexAutoUnlock unlock(mLock);
440
0
441
0
    if (reportMiss) {
442
0
      CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
443
0
        CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
444
0
    }
445
0
446
0
    LOG(("  performing load, file=%p", mFile.get()));
447
0
    if (NS_SUCCEEDED(rv)) {
448
0
      rv = mFile->Init(fileKey,
449
0
                       aTruncate,
450
0
                       !mUseDisk,
451
0
                       mSkipSizeCheck,
452
0
                       aPriority,
453
0
                       mPinned,
454
0
                       directLoad ? nullptr : this);
455
0
    }
456
0
457
0
    if (NS_FAILED(rv)) {
458
0
      mFileStatus = rv;
459
0
      AsyncDoom(nullptr);
460
0
      return false;
461
0
    }
462
0
  }
463
0
464
0
  if (directLoad) {
465
0
    // Just fake the load has already been done as "new".
466
0
    mFileStatus = NS_OK;
467
0
    mState = EMPTY;
468
0
  }
469
0
470
0
  return mState == LOADING;
471
0
}
472
473
NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
474
0
{
475
0
  LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08" PRIx32 ", new=%d]",
476
0
       this, static_cast<uint32_t>(aResult), aIsNew));
477
0
478
0
  MOZ_ASSERT(!mLoadStart.IsNull());
479
0
480
0
  if (NS_SUCCEEDED(aResult)) {
481
0
    if (aIsNew) {
482
0
      CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
483
0
        CacheFileUtils::DetailedCacheHitTelemetry::MISS, mLoadStart);
484
0
    } else {
485
0
      CacheFileUtils::DetailedCacheHitTelemetry::AddRecord(
486
0
        CacheFileUtils::DetailedCacheHitTelemetry::HIT, mLoadStart);
487
0
    }
488
0
  }
489
0
490
0
  // OnFileReady, that is the only code that can transit from LOADING
491
0
  // to any follow-on state and can only be invoked ones on an entry.
492
0
  // Until this moment there is no consumer that could manipulate
493
0
  // the entry state.
494
0
495
0
  mozilla::MutexAutoLock lock(mLock);
496
0
497
0
  MOZ_ASSERT(mState == LOADING);
498
0
499
0
  mState = (aIsNew || NS_FAILED(aResult))
500
0
    ? EMPTY
501
0
    : READY;
502
0
503
0
  mFileStatus = aResult;
504
0
505
0
  mPinned = mFile->IsPinned();;
506
0
  mPinningKnown = true;
507
0
  LOG(("  pinning=%d", mPinned));
508
0
509
0
  if (mState == READY) {
510
0
    mHasData = true;
511
0
512
0
    uint32_t frecency;
513
0
    mFile->GetFrecency(&frecency);
514
0
    // mFrecency is held in a double to increase computance precision.
515
0
    // It is ok to persist frecency only as a uint32 with some math involved.
516
0
    mFrecency = INT2FRECENCY(frecency);
517
0
  }
518
0
519
0
  InvokeCallbacks();
520
0
521
0
  return NS_OK;
522
0
}
523
524
NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
525
0
{
526
0
  if (mDoomCallback) {
527
0
    RefPtr<DoomCallbackRunnable> event =
528
0
      new DoomCallbackRunnable(this, aResult);
529
0
    NS_DispatchToMainThread(event);
530
0
  }
531
0
532
0
  return NS_OK;
533
0
}
534
535
already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
536
                                                               nsICacheEntryOpenCallback* aCallback)
537
0
{
538
0
  LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
539
0
540
0
  mLock.AssertCurrentThreadOwns();
541
0
542
0
  // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
543
0
  mPreventCallbacks = true;
544
0
545
0
  RefPtr<CacheEntryHandle> handle;
546
0
  RefPtr<CacheEntry> newEntry;
547
0
  {
548
0
    if (mPinned) {
549
0
      MOZ_ASSERT(mUseDisk);
550
0
      // We want to pin even no-store entries (the case we recreate a disk entry as
551
0
      // a memory-only entry.)
552
0
      aMemoryOnly = false;
553
0
    }
554
0
555
0
    mozilla::MutexAutoUnlock unlock(mLock);
556
0
557
0
    // The following call dooms this entry (calls DoomAlreadyRemoved on us)
558
0
    nsresult rv = CacheStorageService::Self()->AddStorageEntry(
559
0
      GetStorageID(), GetURI(), GetEnhanceID(),
560
0
      mUseDisk && !aMemoryOnly,
561
0
      mSkipSizeCheck,
562
0
      mPinned,
563
0
      true, // truncate existing (this one)
564
0
      getter_AddRefs(handle));
565
0
566
0
    if (NS_SUCCEEDED(rv)) {
567
0
      newEntry = handle->Entry();
568
0
      LOG(("  exchanged entry %p by entry %p, rv=0x%08" PRIx32,
569
0
           this, newEntry.get(), static_cast<uint32_t>(rv)));
570
0
      newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
571
0
    } else {
572
0
      LOG(("  exchanged of entry %p failed, rv=0x%08" PRIx32,
573
0
           this, static_cast<uint32_t>(rv)));
574
0
      AsyncDoom(nullptr);
575
0
    }
576
0
  }
577
0
578
0
  mPreventCallbacks = false;
579
0
580
0
  if (!newEntry)
581
0
    return nullptr;
582
0
583
0
  newEntry->TransferCallbacks(*this);
584
0
  mCallbacks.Clear();
585
0
586
0
  // Must return a new write handle, since the consumer is expected to
587
0
  // write to this newly recreated entry.  The |handle| is only a common
588
0
  // reference counter and doesn't revert entry state back when write
589
0
  // fails and also doesn't update the entry frecency.  Not updating
590
0
  // frecency causes entries to not be purged from our memory pools.
591
0
  RefPtr<CacheEntryHandle> writeHandle =
592
0
    newEntry->NewWriteHandle();
593
0
  return writeHandle.forget();
594
0
}
595
596
void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
597
0
{
598
0
  mozilla::MutexAutoLock lock(mLock);
599
0
600
0
  LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
601
0
    this, &aFromEntry));
602
0
603
0
  if (!mCallbacks.Length())
604
0
    mCallbacks.SwapElements(aFromEntry.mCallbacks);
605
0
  else
606
0
    mCallbacks.AppendElements(aFromEntry.mCallbacks);
607
0
608
0
  uint32_t callbacksLength = mCallbacks.Length();
609
0
  if (callbacksLength) {
610
0
    // Carry the entry reference (unfortunately, needs to be done manually...)
611
0
    for (uint32_t i = 0; i < callbacksLength; ++i)
612
0
      mCallbacks[i].ExchangeEntry(this);
613
0
614
0
    BackgroundOp(Ops::CALLBACKS, true);
615
0
  }
616
0
}
617
618
void CacheEntry::RememberCallback(Callback & aCallback)
619
0
{
620
0
  mLock.AssertCurrentThreadOwns();
621
0
622
0
  LOG(("CacheEntry::RememberCallback [this=%p, cb=%p, state=%s]",
623
0
    this, aCallback.mCallback.get(), StateString(mState)));
624
0
625
0
  mCallbacks.AppendElement(aCallback);
626
0
}
627
628
void CacheEntry::InvokeCallbacksLock()
629
0
{
630
0
  mozilla::MutexAutoLock lock(mLock);
631
0
  InvokeCallbacks();
632
0
}
633
634
void CacheEntry::InvokeCallbacks()
635
0
{
636
0
  mLock.AssertCurrentThreadOwns();
637
0
638
0
  LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
639
0
640
0
  // Invoke first all r/w callbacks, then all r/o callbacks.
641
0
  if (InvokeCallbacks(false))
642
0
    InvokeCallbacks(true);
643
0
644
0
  LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
645
0
}
646
647
bool CacheEntry::InvokeCallbacks(bool aReadOnly)
648
0
{
649
0
  mLock.AssertCurrentThreadOwns();
650
0
651
0
  RefPtr<CacheEntryHandle> recreatedHandle;
652
0
653
0
  uint32_t i = 0;
654
0
  while (i < mCallbacks.Length()) {
655
0
    if (mPreventCallbacks) {
656
0
      LOG(("  callbacks prevented!"));
657
0
      return false;
658
0
    }
659
0
660
0
    if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
661
0
      LOG(("  entry is being written/revalidated"));
662
0
      return false;
663
0
    }
664
0
665
0
    bool recreate;
666
0
    if (mCallbacks[i].DeferDoom(&recreate)) {
667
0
      mCallbacks.RemoveElementAt(i);
668
0
      if (!recreate) {
669
0
        continue;
670
0
      }
671
0
672
0
      LOG(("  defer doom marker callback hit positive, recreating"));
673
0
      recreatedHandle = ReopenTruncated(!mUseDisk, nullptr);
674
0
      break;
675
0
    }
676
0
677
0
    if (mCallbacks[i].mReadOnly != aReadOnly) {
678
0
      // Callback is not r/w or r/o, go to another one in line
679
0
      ++i;
680
0
      continue;
681
0
    }
682
0
683
0
    bool onCheckThread;
684
0
    nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
685
0
686
0
    if (NS_SUCCEEDED(rv) && !onCheckThread) {
687
0
      // Redispatch to the target thread
688
0
      rv = mCallbacks[i].mTarget->Dispatch(NewRunnableMethod("net::CacheEntry::InvokeCallbacksLock",
689
0
                                                             this,
690
0
                                                             &CacheEntry::InvokeCallbacksLock),
691
0
                                           nsIEventTarget::DISPATCH_NORMAL);
692
0
      if (NS_SUCCEEDED(rv)) {
693
0
        LOG(("  re-dispatching to target thread"));
694
0
        return false;
695
0
      }
696
0
    }
697
0
698
0
    Callback callback = mCallbacks[i];
699
0
    mCallbacks.RemoveElementAt(i);
700
0
701
0
    if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
702
0
      // Callback didn't fire, put it back and go to another one in line.
703
0
      // Only reason InvokeCallback returns false is that onCacheEntryCheck
704
0
      // returns RECHECK_AFTER_WRITE_FINISHED.  If we would stop the loop, other
705
0
      // readers or potential writers would be unnecessarily kept from being
706
0
      // invoked.
707
0
      size_t pos = std::min(mCallbacks.Length(), static_cast<size_t>(i));
708
0
      mCallbacks.InsertElementAt(pos, callback);
709
0
      ++i;
710
0
    }
711
0
  }
712
0
713
0
  if (recreatedHandle) {
714
0
    // Must be released outside of the lock, enters InvokeCallback on the new entry
715
0
    mozilla::MutexAutoUnlock unlock(mLock);
716
0
    recreatedHandle = nullptr;
717
0
  }
718
0
719
0
  return true;
720
0
}
721
722
bool CacheEntry::InvokeCallback(Callback & aCallback)
723
0
{
724
0
  LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
725
0
    this, StateString(mState), aCallback.mCallback.get()));
726
0
727
0
  mLock.AssertCurrentThreadOwns();
728
0
729
0
  // When this entry is doomed we want to notify the callback any time
730
0
  if (!mIsDoomed) {
731
0
    // When we are here, the entry must be loaded from disk
732
0
    MOZ_ASSERT(mState > LOADING);
733
0
734
0
    if (mState == WRITING || mState == REVALIDATING) {
735
0
      // Prevent invoking other callbacks since one of them is now writing
736
0
      // or revalidating this entry.  No consumers should get this entry
737
0
      // until metadata are filled with values downloaded from the server
738
0
      // or the entry revalidated and output stream has been opened.
739
0
      LOG(("  entry is being written/revalidated, callback bypassed"));
740
0
      return false;
741
0
    }
742
0
743
0
    // mRecheckAfterWrite flag already set means the callback has already passed
744
0
    // the onCacheEntryCheck call. Until the current write is not finished this
745
0
    // callback will be bypassed.
746
0
    if (!aCallback.mRecheckAfterWrite) {
747
0
748
0
      if (!aCallback.mReadOnly) {
749
0
        if (mState == EMPTY) {
750
0
          // Advance to writing state, we expect to invoke the callback and let
751
0
          // it fill content of this entry.  Must set and check the state here
752
0
          // to prevent more then one
753
0
          mState = WRITING;
754
0
          LOG(("  advancing to WRITING state"));
755
0
        }
756
0
757
0
        if (!aCallback.mCallback) {
758
0
          // We can be given no callback only in case of recreate, it is ok
759
0
          // to advance to WRITING state since the caller of recreate is expected
760
0
          // to write this entry now.
761
0
          return true;
762
0
        }
763
0
      }
764
0
765
0
      if (mState == READY) {
766
0
        // Metadata present, validate the entry
767
0
        uint32_t checkResult;
768
0
        {
769
0
          // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
770
0
          mozilla::MutexAutoUnlock unlock(mLock);
771
0
772
0
          RefPtr<CacheEntryHandle> handle = NewHandle();
773
0
774
0
          nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
775
0
            handle, nullptr, &checkResult);
776
0
          LOG(("  OnCacheEntryCheck: rv=0x%08" PRIx32 ", result=%" PRId32,
777
0
               static_cast<uint32_t>(rv), static_cast<uint32_t>(checkResult)));
778
0
779
0
          if (NS_FAILED(rv))
780
0
            checkResult = ENTRY_NOT_WANTED;
781
0
        }
782
0
783
0
        aCallback.mRevalidating = checkResult == ENTRY_NEEDS_REVALIDATION;
784
0
785
0
        switch (checkResult) {
786
0
        case ENTRY_WANTED:
787
0
          // Nothing more to do here, the consumer is responsible to handle
788
0
          // the result of OnCacheEntryCheck it self.
789
0
          // Proceed to callback...
790
0
          break;
791
0
792
0
        case RECHECK_AFTER_WRITE_FINISHED:
793
0
          LOG(("  consumer will check on the entry again after write is done"));
794
0
          // The consumer wants the entry to complete first.
795
0
          aCallback.mRecheckAfterWrite = true;
796
0
          break;
797
0
798
0
        case ENTRY_NEEDS_REVALIDATION:
799
0
          LOG(("  will be holding callbacks until entry is revalidated"));
800
0
          // State is READY now and from that state entry cannot transit to any other
801
0
          // state then REVALIDATING for which cocurrency is not an issue.  Potentially
802
0
          // no need to lock here.
803
0
          mState = REVALIDATING;
804
0
          break;
805
0
806
0
        case ENTRY_NOT_WANTED:
807
0
          LOG(("  consumer not interested in the entry"));
808
0
          // Do not give this entry to the consumer, it is not interested in us.
809
0
          aCallback.mNotWanted = true;
810
0
          break;
811
0
        }
812
0
      }
813
0
    }
814
0
  }
815
0
816
0
  if (aCallback.mCallback) {
817
0
    if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
818
0
      // If we don't have data and the callback wants a complete entry,
819
0
      // don't invoke now.
820
0
      bool bypass = !mHasData;
821
0
      if (!bypass && NS_SUCCEEDED(mFileStatus)) {
822
0
        int64_t _unused;
823
0
        bypass = !mFile->DataSize(&_unused);
824
0
      }
825
0
826
0
      if (bypass) {
827
0
        LOG(("  bypassing, entry data still being written"));
828
0
        return false;
829
0
      }
830
0
831
0
      // Entry is complete now, do the check+avail call again
832
0
      aCallback.mRecheckAfterWrite = false;
833
0
      return InvokeCallback(aCallback);
834
0
    }
835
0
836
0
    mozilla::MutexAutoUnlock unlock(mLock);
837
0
    InvokeAvailableCallback(aCallback);
838
0
  }
839
0
840
0
  return true;
841
0
}
842
843
void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
844
0
{
845
0
  LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
846
0
    this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
847
0
848
0
  nsresult rv;
849
0
850
0
  uint32_t const state = mState;
851
0
852
0
  // When we are here, the entry must be loaded from disk
853
0
  MOZ_ASSERT(state > LOADING || mIsDoomed);
854
0
855
0
  bool onAvailThread;
856
0
  rv = aCallback.OnAvailThread(&onAvailThread);
857
0
  if (NS_FAILED(rv)) {
858
0
    LOG(("  target thread dead?"));
859
0
    return;
860
0
  }
861
0
862
0
  if (!onAvailThread) {
863
0
    // Dispatch to the right thread
864
0
    RefPtr<AvailableCallbackRunnable> event =
865
0
      new AvailableCallbackRunnable(this, aCallback);
866
0
867
0
    rv = aCallback.mTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
868
0
    LOG(("  redispatched, (rv = 0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
869
0
    return;
870
0
  }
871
0
872
0
  if (mIsDoomed || aCallback.mNotWanted) {
873
0
    LOG(("  doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
874
0
    aCallback.mCallback->OnCacheEntryAvailable(
875
0
      nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
876
0
    return;
877
0
  }
878
0
879
0
  if (state == READY) {
880
0
    LOG(("  ready/has-meta, notifying OCEA with entry and NS_OK"));
881
0
882
0
    if (!aCallback.mSecret)
883
0
    {
884
0
      mozilla::MutexAutoLock lock(mLock);
885
0
      BackgroundOp(Ops::FRECENCYUPDATE);
886
0
    }
887
0
888
0
    OnFetched(aCallback);
889
0
890
0
    RefPtr<CacheEntryHandle> handle = NewHandle();
891
0
    aCallback.mCallback->OnCacheEntryAvailable(
892
0
      handle, false, nullptr, NS_OK);
893
0
    return;
894
0
  }
895
0
896
0
  // R/O callbacks may do revalidation, let them fall through
897
0
  if (aCallback.mReadOnly && !aCallback.mRevalidating) {
898
0
    LOG(("  r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
899
0
    aCallback.mCallback->OnCacheEntryAvailable(
900
0
      nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
901
0
    return;
902
0
  }
903
0
904
0
  // This is a new or potentially non-valid entry and needs to be fetched first.
905
0
  // The CacheEntryHandle blocks other consumers until the channel
906
0
  // either releases the entry or marks metadata as filled or whole entry valid,
907
0
  // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
908
0
909
0
  // Consumer will be responsible to fill or validate the entry metadata and data.
910
0
911
0
  OnFetched(aCallback);
912
0
913
0
  RefPtr<CacheEntryHandle> handle = NewWriteHandle();
914
0
  rv = aCallback.mCallback->OnCacheEntryAvailable(
915
0
    handle, state == WRITING, nullptr, NS_OK);
916
0
917
0
  if (NS_FAILED(rv)) {
918
0
    LOG(("  writing/revalidating failed (0x%08" PRIx32 ")", static_cast<uint32_t>(rv)));
919
0
920
0
    // Consumer given a new entry failed to take care of the entry.
921
0
    OnHandleClosed(handle);
922
0
    return;
923
0
  }
924
0
925
0
  LOG(("  writing/revalidating"));
926
0
}
927
928
void CacheEntry::OnFetched(Callback const & aCallback)
929
0
{
930
0
  if (NS_SUCCEEDED(mFileStatus) && !aCallback.mSecret) {
931
0
    // Let the last-fetched and fetch-count properties be updated.
932
0
    mFile->OnFetched();
933
0
  }
934
0
}
935
936
CacheEntryHandle* CacheEntry::NewHandle()
937
0
{
938
0
  return new CacheEntryHandle(this);
939
0
}
940
941
CacheEntryHandle* CacheEntry::NewWriteHandle()
942
0
{
943
0
  mozilla::MutexAutoLock lock(mLock);
944
0
945
0
  // Ignore the OPEN_SECRETLY flag on purpose here, which should actually be
946
0
  // used only along with OPEN_READONLY, but there is no need to enforce that.
947
0
  BackgroundOp(Ops::FRECENCYUPDATE);
948
0
949
0
  return (mWriter = NewHandle());
950
0
}
951
952
void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
953
0
{
954
0
  LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
955
0
956
0
  mozilla::MutexAutoLock lock(mLock);
957
0
958
0
  if (IsDoomed() && NS_SUCCEEDED(mFileStatus) &&
959
0
      // Note: mHandlesCount is dropped before this method is called
960
0
      (mHandlesCount == 0 ||
961
0
       (mHandlesCount == 1 && mWriter && mWriter != aHandle))
962
0
      ) {
963
0
    // This entry is no longer referenced from outside and is doomed.
964
0
    // We can do this also when there is just reference from the writer,
965
0
    // no one else could ever reach the written data.
966
0
    // Tell the file to kill the handle, i.e. bypass any I/O operations
967
0
    // on it except removing the file.
968
0
    mFile->Kill();
969
0
  }
970
0
971
0
  if (mWriter != aHandle) {
972
0
    LOG(("  not the writer"));
973
0
    return;
974
0
  }
975
0
976
0
  if (mOutputStream) {
977
0
    LOG(("  abandoning phantom output stream"));
978
0
    // No one took our internal output stream, so there are no data
979
0
    // and output stream has to be open symultaneously with input stream
980
0
    // on this entry again.
981
0
    mHasData = false;
982
0
    // This asynchronously ends up invoking callbacks on this entry
983
0
    // through OnOutputClosed() call.
984
0
    mOutputStream->Close();
985
0
    mOutputStream = nullptr;
986
0
  } else {
987
0
    // We must always redispatch, otherwise there is a risk of stack
988
0
    // overflow.  This code can recurse deeply.  It won't execute sooner
989
0
    // than we release mLock.
990
0
    BackgroundOp(Ops::CALLBACKS, true);
991
0
  }
992
0
993
0
  mWriter = nullptr;
994
0
995
0
  if (mState == WRITING) {
996
0
    LOG(("  reverting to state EMPTY - write failed"));
997
0
    mState = EMPTY;
998
0
  }
999
0
  else if (mState == REVALIDATING) {
1000
0
    LOG(("  reverting to state READY - reval failed"));
1001
0
    mState = READY;
1002
0
  }
1003
0
1004
0
  if (mState == READY && !mHasData) {
1005
0
    // We may get to this state when following steps happen:
1006
0
    // 1. a new entry is given to a consumer
1007
0
    // 2. the consumer calls MetaDataReady(), we transit to READY
1008
0
    // 3. abandons the entry w/o opening the output stream, mHasData left false
1009
0
    //
1010
0
    // In this case any following consumer will get a ready entry (with metadata)
1011
0
    // but in state like the entry data write was still happening (was in progress)
1012
0
    // and will indefinitely wait for the entry data or even the entry itself when
1013
0
    // RECHECK_AFTER_WRITE is returned from onCacheEntryCheck.
1014
0
    LOG(("  we are in READY state, pretend we have data regardless it"
1015
0
          " has actully been never touched"));
1016
0
    mHasData = true;
1017
0
  }
1018
0
}
1019
1020
void CacheEntry::OnOutputClosed()
1021
0
{
1022
0
  // Called when the file's output stream is closed.  Invoke any callbacks
1023
0
  // waiting for complete entry.
1024
0
1025
0
  mozilla::MutexAutoLock lock(mLock);
1026
0
  InvokeCallbacks();
1027
0
}
1028
1029
bool CacheEntry::IsReferenced() const
1030
0
{
1031
0
  CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
1032
0
1033
0
  // Increasing this counter from 0 to non-null and this check both happen only
1034
0
  // under the service lock.
1035
0
  return mHandlesCount > 0;
1036
0
}
1037
1038
bool CacheEntry::IsFileDoomed()
1039
0
{
1040
0
  if (NS_SUCCEEDED(mFileStatus)) {
1041
0
    return mFile->IsDoomed();
1042
0
  }
1043
0
1044
0
  return false;
1045
0
}
1046
1047
uint32_t CacheEntry::GetMetadataMemoryConsumption()
1048
0
{
1049
0
  NS_ENSURE_SUCCESS(mFileStatus, 0);
1050
0
1051
0
  uint32_t size;
1052
0
  if (NS_FAILED(mFile->ElementsSize(&size)))
1053
0
    return 0;
1054
0
1055
0
  return size;
1056
0
}
1057
1058
// nsICacheEntry
1059
1060
nsresult CacheEntry::GetPersistent(bool *aPersistToDisk)
1061
0
{
1062
0
  // No need to sync when only reading.
1063
0
  // When consumer needs to be consistent with state of the memory storage entries
1064
0
  // table, then let it use GetUseDisk getter that must be called under the service lock.
1065
0
  *aPersistToDisk = mUseDisk;
1066
0
  return NS_OK;
1067
0
}
1068
1069
nsresult CacheEntry::GetKey(nsACString & aKey)
1070
0
{
1071
0
  aKey.Assign(mURI);
1072
0
  return NS_OK;
1073
0
}
1074
1075
nsresult CacheEntry::GetCacheEntryId(uint64_t *aCacheEntryId)
1076
0
{
1077
0
  *aCacheEntryId = mCacheEntryId;
1078
0
  return NS_OK;
1079
0
}
1080
1081
nsresult CacheEntry::GetFetchCount(int32_t *aFetchCount)
1082
0
{
1083
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1084
0
1085
0
  return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
1086
0
}
1087
1088
nsresult CacheEntry::GetLastFetched(uint32_t *aLastFetched)
1089
0
{
1090
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1091
0
1092
0
  return mFile->GetLastFetched(aLastFetched);
1093
0
}
1094
1095
nsresult CacheEntry::GetLastModified(uint32_t *aLastModified)
1096
0
{
1097
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1098
0
1099
0
  return mFile->GetLastModified(aLastModified);
1100
0
}
1101
1102
nsresult CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
1103
0
{
1104
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1105
0
1106
0
  return mFile->GetExpirationTime(aExpirationTime);
1107
0
}
1108
1109
nsresult CacheEntry::GetOnStartTime(uint64_t *aTime)
1110
0
{
1111
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1112
0
  return mFile->GetOnStartTime(aTime);
1113
0
}
1114
1115
nsresult CacheEntry::GetOnStopTime(uint64_t *aTime)
1116
0
{
1117
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1118
0
  return mFile->GetOnStopTime(aTime);
1119
0
}
1120
1121
nsresult CacheEntry::SetNetworkTimes(uint64_t aOnStartTime, uint64_t aOnStopTime)
1122
0
{
1123
0
  if (NS_SUCCEEDED(mFileStatus)) {
1124
0
    return mFile->SetNetworkTimes(aOnStartTime, aOnStopTime);
1125
0
  }
1126
0
  return NS_ERROR_NOT_AVAILABLE;
1127
0
}
1128
1129
nsresult CacheEntry::GetIsForcedValid(bool *aIsForcedValid)
1130
0
{
1131
0
  NS_ENSURE_ARG(aIsForcedValid);
1132
0
1133
0
  MOZ_ASSERT(mState > LOADING);
1134
0
1135
0
  if (mPinned) {
1136
0
    *aIsForcedValid = true;
1137
0
    return NS_OK;
1138
0
  }
1139
0
1140
0
  nsAutoCString key;
1141
0
  nsresult rv = HashingKey(key);
1142
0
  if (NS_FAILED(rv)) {
1143
0
    return rv;
1144
0
  }
1145
0
1146
0
  *aIsForcedValid = CacheStorageService::Self()->IsForcedValidEntry(mStorageID, key);
1147
0
  LOG(("CacheEntry::GetIsForcedValid [this=%p, IsForcedValid=%d]", this, *aIsForcedValid));
1148
0
1149
0
  return NS_OK;
1150
0
}
1151
1152
nsresult CacheEntry::ForceValidFor(uint32_t aSecondsToTheFuture)
1153
0
{
1154
0
  LOG(("CacheEntry::ForceValidFor [this=%p, aSecondsToTheFuture=%d]", this, aSecondsToTheFuture));
1155
0
1156
0
  nsAutoCString key;
1157
0
  nsresult rv = HashingKey(key);
1158
0
  if (NS_FAILED(rv)) {
1159
0
    return rv;
1160
0
  }
1161
0
1162
0
  CacheStorageService::Self()->ForceEntryValidFor(mStorageID, key, aSecondsToTheFuture);
1163
0
1164
0
  return NS_OK;
1165
0
}
1166
1167
nsresult CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
1168
0
{
1169
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1170
0
1171
0
  nsresult rv = mFile->SetExpirationTime(aExpirationTime);
1172
0
  NS_ENSURE_SUCCESS(rv, rv);
1173
0
1174
0
  // Aligned assignment, thus atomic.
1175
0
  mSortingExpirationTime = aExpirationTime;
1176
0
  return NS_OK;
1177
0
}
1178
1179
nsresult CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
1180
0
{
1181
0
  LOG(("CacheEntry::OpenInputStream [this=%p]", this));
1182
0
  return OpenInputStreamInternal(offset, nullptr, _retval);
1183
0
}
1184
1185
nsresult CacheEntry::OpenAlternativeInputStream(const nsACString & type, nsIInputStream * *_retval)
1186
0
{
1187
0
  LOG(("CacheEntry::OpenAlternativeInputStream [this=%p, type=%s]", this,
1188
0
       PromiseFlatCString(type).get()));
1189
0
  return OpenInputStreamInternal(0, PromiseFlatCString(type).get(), _retval);
1190
0
}
1191
1192
nsresult CacheEntry::OpenInputStreamInternal(int64_t offset, const char *aAltDataType, nsIInputStream * *_retval)
1193
0
{
1194
0
  LOG(("CacheEntry::OpenInputStreamInternal [this=%p]", this));
1195
0
1196
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1197
0
1198
0
  nsresult rv;
1199
0
1200
0
  RefPtr<CacheEntryHandle> selfHandle = NewHandle();
1201
0
1202
0
  nsCOMPtr<nsIInputStream> stream;
1203
0
  if (aAltDataType) {
1204
0
    rv = mFile->OpenAlternativeInputStream(selfHandle, aAltDataType,
1205
0
                                           getter_AddRefs(stream));
1206
0
    if (NS_FAILED(rv)) {
1207
0
      // Failure of this method may be legal when the alternative data requested
1208
0
      // is not avaialble or of a different type.  Console error logs are ensured
1209
0
      // by CacheFile::OpenAlternativeInputStream.
1210
0
      return rv;
1211
0
    }
1212
0
  } else {
1213
0
    rv = mFile->OpenInputStream(selfHandle, getter_AddRefs(stream));
1214
0
    NS_ENSURE_SUCCESS(rv, rv);
1215
0
  }
1216
0
1217
0
  nsCOMPtr<nsISeekableStream> seekable =
1218
0
    do_QueryInterface(stream, &rv);
1219
0
  NS_ENSURE_SUCCESS(rv, rv);
1220
0
1221
0
  rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1222
0
  NS_ENSURE_SUCCESS(rv, rv);
1223
0
1224
0
  mozilla::MutexAutoLock lock(mLock);
1225
0
1226
0
  if (!mHasData) {
1227
0
    // So far output stream on this new entry not opened, do it now.
1228
0
    LOG(("  creating phantom output stream"));
1229
0
    rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
1230
0
    NS_ENSURE_SUCCESS(rv, rv);
1231
0
  }
1232
0
1233
0
  stream.forget(_retval);
1234
0
  return NS_OK;
1235
0
}
1236
1237
nsresult CacheEntry::OpenOutputStream(int64_t offset, int64_t predictedSize, nsIOutputStream * *_retval)
1238
0
{
1239
0
  LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
1240
0
1241
0
  nsresult rv;
1242
0
1243
0
  mozilla::MutexAutoLock lock(mLock);
1244
0
1245
0
  MOZ_ASSERT(mState > EMPTY);
1246
0
1247
0
  if (mFile->EntryWouldExceedLimit(0, predictedSize, false)) {
1248
0
    LOG(("  entry would exceed size limit"));
1249
0
    return NS_ERROR_FILE_TOO_BIG;
1250
0
  }
1251
0
1252
0
  if (mOutputStream && !mIsDoomed) {
1253
0
    LOG(("  giving phantom output stream"));
1254
0
    mOutputStream.forget(_retval);
1255
0
  }
1256
0
  else {
1257
0
    rv = OpenOutputStreamInternal(offset, _retval);
1258
0
    if (NS_FAILED(rv)) return rv;
1259
0
  }
1260
0
1261
0
  // Entry considered ready when writer opens output stream.
1262
0
  if (mState < READY)
1263
0
    mState = READY;
1264
0
1265
0
  // Invoke any pending readers now.
1266
0
  InvokeCallbacks();
1267
0
1268
0
  return NS_OK;
1269
0
}
1270
1271
nsresult CacheEntry::OpenAlternativeOutputStream(const nsACString & type, int64_t predictedSize, nsIOutputStream * *_retval)
1272
0
{
1273
0
  LOG(("CacheEntry::OpenAlternativeOutputStream [this=%p, type=%s]", this,
1274
0
       PromiseFlatCString(type).get()));
1275
0
1276
0
  nsresult rv;
1277
0
1278
0
  mozilla::MutexAutoLock lock(mLock);
1279
0
1280
0
  if (!mHasData || mState < READY || mOutputStream || mIsDoomed) {
1281
0
    LOG(("  entry not in state to write alt-data"));
1282
0
    return NS_ERROR_NOT_AVAILABLE;
1283
0
  }
1284
0
1285
0
  if (mFile->EntryWouldExceedLimit(0, predictedSize, true)) {
1286
0
    LOG(("  entry would exceed size limit"));
1287
0
    return NS_ERROR_FILE_TOO_BIG;
1288
0
  }
1289
0
1290
0
  nsCOMPtr<nsIOutputStream> stream;
1291
0
  rv = mFile->OpenAlternativeOutputStream(nullptr,
1292
0
                                          PromiseFlatCString(type).get(),
1293
0
                                          getter_AddRefs(stream));
1294
0
  NS_ENSURE_SUCCESS(rv, rv);
1295
0
1296
0
  stream.swap(*_retval);
1297
0
  return NS_OK;
1298
0
}
1299
1300
nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
1301
0
{
1302
0
  LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
1303
0
1304
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1305
0
1306
0
  mLock.AssertCurrentThreadOwns();
1307
0
1308
0
  if (mIsDoomed) {
1309
0
    LOG(("  doomed..."));
1310
0
    return NS_ERROR_NOT_AVAILABLE;
1311
0
  }
1312
0
1313
0
  MOZ_ASSERT(mState > LOADING);
1314
0
1315
0
  nsresult rv;
1316
0
1317
0
  // No need to sync on mUseDisk here, we don't need to be consistent
1318
0
  // with content of the memory storage entries hash table.
1319
0
  if (!mUseDisk) {
1320
0
    rv = mFile->SetMemoryOnly();
1321
0
    NS_ENSURE_SUCCESS(rv, rv);
1322
0
  }
1323
0
1324
0
  RefPtr<CacheOutputCloseListener> listener =
1325
0
    new CacheOutputCloseListener(this);
1326
0
1327
0
  nsCOMPtr<nsIOutputStream> stream;
1328
0
  rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
1329
0
  NS_ENSURE_SUCCESS(rv, rv);
1330
0
1331
0
  nsCOMPtr<nsISeekableStream> seekable =
1332
0
    do_QueryInterface(stream, &rv);
1333
0
  NS_ENSURE_SUCCESS(rv, rv);
1334
0
1335
0
  rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1336
0
  NS_ENSURE_SUCCESS(rv, rv);
1337
0
1338
0
  // Prevent opening output stream again.
1339
0
  mHasData = true;
1340
0
1341
0
  stream.swap(*_retval);
1342
0
  return NS_OK;
1343
0
}
1344
1345
nsresult CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
1346
0
{
1347
0
  {
1348
0
    mozilla::MutexAutoLock lock(mLock);
1349
0
    if (mSecurityInfoLoaded) {
1350
0
      NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1351
0
      return NS_OK;
1352
0
    }
1353
0
  }
1354
0
1355
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1356
0
1357
0
  nsCString info;
1358
0
  nsCOMPtr<nsISupports> secInfo;
1359
0
  nsresult rv;
1360
0
1361
0
  rv = mFile->GetElement("security-info", getter_Copies(info));
1362
0
  NS_ENSURE_SUCCESS(rv, rv);
1363
0
1364
0
  if (!info.IsVoid()) {
1365
0
    rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
1366
0
    NS_ENSURE_SUCCESS(rv, rv);
1367
0
  }
1368
0
1369
0
  {
1370
0
    mozilla::MutexAutoLock lock(mLock);
1371
0
1372
0
    mSecurityInfo.swap(secInfo);
1373
0
    mSecurityInfoLoaded = true;
1374
0
1375
0
    NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
1376
0
  }
1377
0
1378
0
  return NS_OK;
1379
0
}
1380
nsresult CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
1381
0
{
1382
0
  nsresult rv;
1383
0
1384
0
  NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1385
0
1386
0
  {
1387
0
    mozilla::MutexAutoLock lock(mLock);
1388
0
1389
0
    mSecurityInfo = aSecurityInfo;
1390
0
    mSecurityInfoLoaded = true;
1391
0
  }
1392
0
1393
0
  nsCOMPtr<nsISerializable> serializable =
1394
0
    do_QueryInterface(aSecurityInfo);
1395
0
  if (aSecurityInfo && !serializable)
1396
0
    return NS_ERROR_UNEXPECTED;
1397
0
1398
0
  nsCString info;
1399
0
  if (serializable) {
1400
0
    rv = NS_SerializeToString(serializable, info);
1401
0
    NS_ENSURE_SUCCESS(rv, rv);
1402
0
  }
1403
0
1404
0
  rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
1405
0
  NS_ENSURE_SUCCESS(rv, rv);
1406
0
1407
0
  return NS_OK;
1408
0
}
1409
1410
nsresult CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
1411
0
{
1412
0
  NS_ENSURE_ARG(aStorageDataSize);
1413
0
1414
0
  int64_t dataSize;
1415
0
  nsresult rv = GetDataSize(&dataSize);
1416
0
  if (NS_FAILED(rv))
1417
0
    return rv;
1418
0
1419
0
  *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
1420
0
1421
0
  return NS_OK;
1422
0
}
1423
1424
nsresult CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
1425
0
{
1426
0
  LOG(("CacheEntry::AsyncDoom [this=%p]", this));
1427
0
1428
0
  {
1429
0
    mozilla::MutexAutoLock lock(mLock);
1430
0
1431
0
    if (mIsDoomed || mDoomCallback)
1432
0
      return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
1433
0
1434
0
    RemoveForcedValidity();
1435
0
1436
0
    mIsDoomed = true;
1437
0
    mDoomCallback = aCallback;
1438
0
  }
1439
0
1440
0
  // This immediately removes the entry from the master hashtable and also
1441
0
  // immediately dooms the file.  This way we make sure that any consumer
1442
0
  // after this point asking for the same entry won't get
1443
0
  //   a) this entry
1444
0
  //   b) a new entry with the same file
1445
0
  PurgeAndDoom();
1446
0
1447
0
  return NS_OK;
1448
0
}
1449
1450
nsresult CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
1451
0
{
1452
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1453
0
1454
0
  return mFile->GetElement(aKey, aRetval);
1455
0
}
1456
1457
nsresult CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
1458
0
{
1459
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1460
0
1461
0
  return mFile->SetElement(aKey, aValue);
1462
0
}
1463
1464
nsresult CacheEntry::VisitMetaData(nsICacheEntryMetaDataVisitor *aVisitor)
1465
0
{
1466
0
  NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
1467
0
1468
0
  return mFile->VisitMetaData(aVisitor);
1469
0
}
1470
1471
nsresult CacheEntry::MetaDataReady()
1472
0
{
1473
0
  mozilla::MutexAutoLock lock(mLock);
1474
0
1475
0
  LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
1476
0
1477
0
  MOZ_ASSERT(mState > EMPTY);
1478
0
1479
0
  if (mState == WRITING)
1480
0
    mState = READY;
1481
0
1482
0
  InvokeCallbacks();
1483
0
1484
0
  return NS_OK;
1485
0
}
1486
1487
nsresult CacheEntry::SetValid()
1488
0
{
1489
0
  LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
1490
0
1491
0
  nsCOMPtr<nsIOutputStream> outputStream;
1492
0
1493
0
  {
1494
0
    mozilla::MutexAutoLock lock(mLock);
1495
0
1496
0
    MOZ_ASSERT(mState > EMPTY);
1497
0
1498
0
    mState = READY;
1499
0
    mHasData = true;
1500
0
1501
0
    InvokeCallbacks();
1502
0
1503
0
    outputStream.swap(mOutputStream);
1504
0
  }
1505
0
1506
0
  if (outputStream) {
1507
0
    LOG(("  abandoning phantom output stream"));
1508
0
    outputStream->Close();
1509
0
  }
1510
0
1511
0
  return NS_OK;
1512
0
}
1513
1514
nsresult CacheEntry::Recreate(bool aMemoryOnly,
1515
                                   nsICacheEntry **_retval)
1516
0
{
1517
0
  LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
1518
0
1519
0
  mozilla::MutexAutoLock lock(mLock);
1520
0
1521
0
  RefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
1522
0
  if (handle) {
1523
0
    handle.forget(_retval);
1524
0
    return NS_OK;
1525
0
  }
1526
0
1527
0
  BackgroundOp(Ops::CALLBACKS, true);
1528
0
  return NS_ERROR_NOT_AVAILABLE;
1529
0
}
1530
1531
nsresult CacheEntry::GetDataSize(int64_t *aDataSize)
1532
0
{
1533
0
  LOG(("CacheEntry::GetDataSize [this=%p]", this));
1534
0
  *aDataSize = 0;
1535
0
1536
0
  {
1537
0
    mozilla::MutexAutoLock lock(mLock);
1538
0
1539
0
    if (!mHasData) {
1540
0
      LOG(("  write in progress (no data)"));
1541
0
      return NS_ERROR_IN_PROGRESS;
1542
0
    }
1543
0
  }
1544
0
1545
0
  NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
1546
0
1547
0
  // mayhemer: TODO Problem with compression?
1548
0
  if (!mFile->DataSize(aDataSize)) {
1549
0
    LOG(("  write in progress (stream active)"));
1550
0
    return NS_ERROR_IN_PROGRESS;
1551
0
  }
1552
0
1553
0
  LOG(("  size=%" PRId64, *aDataSize));
1554
0
  return NS_OK;
1555
0
}
1556
1557
1558
nsresult CacheEntry::GetAltDataSize(int64_t *aDataSize)
1559
0
{
1560
0
  LOG(("CacheEntry::GetAltDataSize [this=%p]", this));
1561
0
  if (NS_FAILED(mFileStatus)) {
1562
0
    return mFileStatus;
1563
0
  }
1564
0
  return mFile->GetAltDataSize(aDataSize);
1565
0
}
1566
1567
1568
nsresult CacheEntry::MarkValid()
1569
0
{
1570
0
  // NOT IMPLEMENTED ACTUALLY
1571
0
  return NS_OK;
1572
0
}
1573
1574
nsresult CacheEntry::MaybeMarkValid()
1575
0
{
1576
0
  // NOT IMPLEMENTED ACTUALLY
1577
0
  return NS_OK;
1578
0
}
1579
1580
nsresult CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
1581
0
{
1582
0
  *aWriteAccess = aWriteAllowed;
1583
0
  return NS_OK;
1584
0
}
1585
1586
nsresult CacheEntry::Close()
1587
0
{
1588
0
  // NOT IMPLEMENTED ACTUALLY
1589
0
  return NS_OK;
1590
0
}
1591
1592
nsresult CacheEntry::GetDiskStorageSizeInKB(uint32_t *aDiskStorageSize)
1593
0
{
1594
0
  if (NS_FAILED(mFileStatus)) {
1595
0
    return NS_ERROR_NOT_AVAILABLE;
1596
0
  }
1597
0
1598
0
  return mFile->GetDiskStorageSizeInKB(aDiskStorageSize);
1599
0
}
1600
1601
nsresult CacheEntry::GetLoadContextInfo(nsILoadContextInfo** aInfo)
1602
0
{
1603
0
  nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(mStorageID);
1604
0
  if (!info) {
1605
0
    return NS_ERROR_FAILURE;
1606
0
  }
1607
0
1608
0
  info.forget(aInfo);
1609
0
1610
0
  return NS_OK;
1611
0
}
1612
1613
// nsIRunnable
1614
1615
NS_IMETHODIMP CacheEntry::Run()
1616
0
{
1617
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1618
0
1619
0
  mozilla::MutexAutoLock lock(mLock);
1620
0
1621
0
  BackgroundOp(mBackgroundOperations.Grab());
1622
0
  return NS_OK;
1623
0
}
1624
1625
// Management methods
1626
1627
double CacheEntry::GetFrecency() const
1628
0
{
1629
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1630
0
  return mFrecency;
1631
0
}
1632
1633
uint32_t CacheEntry::GetExpirationTime() const
1634
0
{
1635
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1636
0
  return mSortingExpirationTime;
1637
0
}
1638
1639
bool CacheEntry::IsRegistered() const
1640
0
{
1641
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1642
0
  return mRegistration == REGISTERED;
1643
0
}
1644
1645
bool CacheEntry::CanRegister() const
1646
0
{
1647
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1648
0
  return mRegistration == NEVERREGISTERED;
1649
0
}
1650
1651
void CacheEntry::SetRegistered(bool aRegistered)
1652
0
{
1653
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1654
0
1655
0
  if (aRegistered) {
1656
0
    MOZ_ASSERT(mRegistration == NEVERREGISTERED);
1657
0
    mRegistration = REGISTERED;
1658
0
  }
1659
0
  else {
1660
0
    MOZ_ASSERT(mRegistration == REGISTERED);
1661
0
    mRegistration = DEREGISTERED;
1662
0
  }
1663
0
}
1664
1665
bool CacheEntry::DeferOrBypassRemovalOnPinStatus(bool aPinned)
1666
0
{
1667
0
  LOG(("CacheEntry::DeferOrBypassRemovalOnPinStatus [this=%p]", this));
1668
0
1669
0
  mozilla::MutexAutoLock lock(mLock);
1670
0
1671
0
  if (mPinningKnown) {
1672
0
    LOG(("  pinned=%d, caller=%d", mPinned, aPinned));
1673
0
    // Bypass when the pin status of this entry doesn't match the pin status
1674
0
    // caller wants to remove
1675
0
    return mPinned != aPinned;
1676
0
  }
1677
0
1678
0
  LOG(("  pinning unknown, caller=%d", aPinned));
1679
0
  // Oterwise, remember to doom after the status is determined for any
1680
0
  // callback opening the entry after this point...
1681
0
  Callback c(this, aPinned);
1682
0
  RememberCallback(c);
1683
0
  // ...and always bypass
1684
0
  return true;
1685
0
}
1686
1687
bool CacheEntry::Purge(uint32_t aWhat)
1688
0
{
1689
0
  LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
1690
0
1691
0
  MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1692
0
1693
0
  switch (aWhat) {
1694
0
  case PURGE_DATA_ONLY_DISK_BACKED:
1695
0
  case PURGE_WHOLE_ONLY_DISK_BACKED:
1696
0
    // This is an in-memory only entry, don't purge it
1697
0
    if (!mUseDisk) {
1698
0
      LOG(("  not using disk"));
1699
0
      return false;
1700
0
    }
1701
0
  }
1702
0
1703
0
  if (mState == WRITING || mState == LOADING || mFrecency == 0) {
1704
0
    // In-progress (write or load) entries should (at least for consistency and from
1705
0
    // the logical point of view) stay in memory.
1706
0
    // Zero-frecency entries are those which have never been given to any consumer, those
1707
0
    // are actually very fresh and should not go just because frecency had not been set
1708
0
    // so far.
1709
0
    LOG(("  state=%s, frecency=%1.10f", StateString(mState), mFrecency));
1710
0
    return false;
1711
0
  }
1712
0
1713
0
  if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
1714
0
    // The file is used when there are open streams or chunks/metadata still waiting for
1715
0
    // write.  In this case, this entry cannot be purged, otherwise reopenned entry
1716
0
    // would may not even find the data on disk - CacheFile is not shared and cannot be
1717
0
    // left orphan when its job is not done, hence keep the whole entry.
1718
0
    LOG(("  file still under use"));
1719
0
    return false;
1720
0
  }
1721
0
1722
0
  switch (aWhat) {
1723
0
  case PURGE_WHOLE_ONLY_DISK_BACKED:
1724
0
  case PURGE_WHOLE:
1725
0
    {
1726
0
      if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
1727
0
        LOG(("  not purging, still referenced"));
1728
0
        return false;
1729
0
      }
1730
0
1731
0
      CacheStorageService::Self()->UnregisterEntry(this);
1732
0
1733
0
      // Entry removed it self from control arrays, return true
1734
0
      return true;
1735
0
    }
1736
0
1737
0
  case PURGE_DATA_ONLY_DISK_BACKED:
1738
0
    {
1739
0
      NS_ENSURE_SUCCESS(mFileStatus, false);
1740
0
1741
0
      mFile->ThrowMemoryCachedData();
1742
0
1743
0
      // Entry has been left in control arrays, return false (not purged)
1744
0
      return false;
1745
0
    }
1746
0
  }
1747
0
1748
0
  LOG(("  ?"));
1749
0
  return false;
1750
0
}
1751
1752
void CacheEntry::PurgeAndDoom()
1753
0
{
1754
0
  LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
1755
0
1756
0
  CacheStorageService::Self()->RemoveEntry(this);
1757
0
  DoomAlreadyRemoved();
1758
0
}
1759
1760
void CacheEntry::DoomAlreadyRemoved()
1761
0
{
1762
0
  LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
1763
0
1764
0
  mozilla::MutexAutoLock lock(mLock);
1765
0
1766
0
  RemoveForcedValidity();
1767
0
1768
0
  mIsDoomed = true;
1769
0
1770
0
  // Pretend pinning is know.  This entry is now doomed for good, so don't
1771
0
  // bother with defering doom because of unknown pinning state any more.
1772
0
  mPinningKnown = true;
1773
0
1774
0
  // This schedules dooming of the file, dooming is ensured to happen
1775
0
  // sooner than demand to open the same file made after this point
1776
0
  // so that we don't get this file for any newer opened entry(s).
1777
0
  DoomFile();
1778
0
1779
0
  // Must force post here since may be indirectly called from
1780
0
  // InvokeCallbacks of this entry and we don't want reentrancy here.
1781
0
  BackgroundOp(Ops::CALLBACKS, true);
1782
0
  // Process immediately when on the management thread.
1783
0
  BackgroundOp(Ops::UNREGISTER);
1784
0
}
1785
1786
void CacheEntry::DoomFile()
1787
0
{
1788
0
  nsresult rv = NS_ERROR_NOT_AVAILABLE;
1789
0
1790
0
  if (NS_SUCCEEDED(mFileStatus)) {
1791
0
    if (mHandlesCount == 0 ||
1792
0
        (mHandlesCount == 1 && mWriter)) {
1793
0
      // We kill the file also when there is just reference from the writer,
1794
0
      // no one else could ever reach the written data.  Obvisouly also
1795
0
      // when there is no reference at all (should we ever end up here
1796
0
      // in that case.)
1797
0
      // Tell the file to kill the handle, i.e. bypass any I/O operations
1798
0
      // on it except removing the file.
1799
0
      mFile->Kill();
1800
0
    }
1801
0
1802
0
    // Always calls the callback asynchronously.
1803
0
    rv = mFile->Doom(mDoomCallback ? this : nullptr);
1804
0
    if (NS_SUCCEEDED(rv)) {
1805
0
      LOG(("  file doomed"));
1806
0
      return;
1807
0
    }
1808
0
1809
0
    if (NS_ERROR_FILE_NOT_FOUND == rv) {
1810
0
      // File is set to be just memory-only, notify the callbacks
1811
0
      // and pretend dooming has succeeded.  From point of view of
1812
0
      // the entry it actually did - the data is gone and cannot be
1813
0
      // reused.
1814
0
      rv = NS_OK;
1815
0
    }
1816
0
  }
1817
0
1818
0
  // Always posts to the main thread.
1819
0
  OnFileDoomed(rv);
1820
0
}
1821
1822
void CacheEntry::RemoveForcedValidity()
1823
0
{
1824
0
  mLock.AssertCurrentThreadOwns();
1825
0
1826
0
  nsresult rv;
1827
0
1828
0
  if (mIsDoomed) {
1829
0
    return;
1830
0
  }
1831
0
1832
0
  nsAutoCString entryKey;
1833
0
  rv = HashingKey(entryKey);
1834
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1835
0
    return;
1836
0
  }
1837
0
1838
0
  CacheStorageService::Self()->RemoveEntryForceValid(mStorageID, entryKey);
1839
0
}
1840
1841
void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
1842
0
{
1843
0
  mLock.AssertCurrentThreadOwns();
1844
0
1845
0
  if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
1846
0
    if (mBackgroundOperations.Set(aOperations))
1847
0
      CacheStorageService::Self()->Dispatch(this);
1848
0
1849
0
    LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
1850
0
    return;
1851
0
  }
1852
0
1853
0
  {
1854
0
    mozilla::MutexAutoUnlock unlock(mLock);
1855
0
1856
0
    MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
1857
0
1858
0
    if (aOperations & Ops::FRECENCYUPDATE) {
1859
0
      ++mUseCount;
1860
0
1861
      #ifndef M_LN2
1862
      #define M_LN2 0.69314718055994530942
1863
      #endif
1864
1865
0
      // Half-life is dynamic, in seconds.
1866
0
      static double half_life = CacheObserver::HalfLifeSeconds();
1867
0
      // Must convert from seconds to milliseconds since PR_Now() gives usecs.
1868
0
      static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
1869
0
1870
0
      double now_decay = static_cast<double>(PR_Now()) * decay;
1871
0
1872
0
      if (mFrecency == 0) {
1873
0
        mFrecency = now_decay;
1874
0
      }
1875
0
      else {
1876
0
        // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
1877
0
        // more precise.
1878
0
        mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
1879
0
      }
1880
0
      LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
1881
0
1882
0
      // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
1883
0
      // is not thread-safe) we must post to the main thread...
1884
0
      NS_DispatchToMainThread(
1885
0
        NewRunnableMethod<double>("net::CacheEntry::StoreFrecency",
1886
0
                                  this,
1887
0
                                  &CacheEntry::StoreFrecency,
1888
0
                                  mFrecency));
1889
0
    }
1890
0
1891
0
    if (aOperations & Ops::REGISTER) {
1892
0
      LOG(("CacheEntry REGISTER [this=%p]", this));
1893
0
1894
0
      CacheStorageService::Self()->RegisterEntry(this);
1895
0
    }
1896
0
1897
0
    if (aOperations & Ops::UNREGISTER) {
1898
0
      LOG(("CacheEntry UNREGISTER [this=%p]", this));
1899
0
1900
0
      CacheStorageService::Self()->UnregisterEntry(this);
1901
0
    }
1902
0
  } // unlock
1903
0
1904
0
  if (aOperations & Ops::CALLBACKS) {
1905
0
    LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
1906
0
1907
0
    InvokeCallbacks();
1908
0
  }
1909
0
}
1910
1911
void CacheEntry::StoreFrecency(double aFrecency)
1912
0
{
1913
0
  MOZ_ASSERT(NS_IsMainThread());
1914
0
1915
0
  if (NS_SUCCEEDED(mFileStatus)) {
1916
0
    mFile->SetFrecency(FRECENCY2INT(aFrecency));
1917
0
  }
1918
0
}
1919
1920
// CacheOutputCloseListener
1921
1922
CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
1923
  : Runnable("net::CacheOutputCloseListener")
1924
  , mEntry(aEntry)
1925
0
{
1926
0
}
1927
1928
void CacheOutputCloseListener::OnOutputClosed()
1929
0
{
1930
0
  // We need this class and to redispatch since this callback is invoked
1931
0
  // under the file's lock and to do the job we need to enter the entry's
1932
0
  // lock too.  That would lead to potential deadlocks.
1933
0
  NS_DispatchToCurrentThread(this);
1934
0
}
1935
1936
NS_IMETHODIMP CacheOutputCloseListener::Run()
1937
0
{
1938
0
  mEntry->OnOutputClosed();
1939
0
  return NS_OK;
1940
0
}
1941
1942
// Memory reporting
1943
1944
size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1945
0
{
1946
0
  size_t n = 0;
1947
0
1948
0
  n += mCallbacks.ShallowSizeOfExcludingThis(mallocSizeOf);
1949
0
  if (mFile) {
1950
0
    n += mFile->SizeOfIncludingThis(mallocSizeOf);
1951
0
  }
1952
0
1953
0
  n += mURI.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1954
0
  n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1955
0
  n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1956
0
1957
0
  // mDoomCallback is an arbitrary class that is probably reported elsewhere.
1958
0
  // mOutputStream is reported in mFile.
1959
0
  // mWriter is one of many handles we create, but (intentionally) not keep
1960
0
  // any reference to, so those unfortunately cannot be reported.  Handles are
1961
0
  // small, though.
1962
0
  // mSecurityInfo doesn't impl nsISizeOf.
1963
0
1964
0
  return n;
1965
0
}
1966
1967
size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1968
0
{
1969
0
  return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1970
0
}
1971
1972
} // namespace net
1973
} // namespace mozilla