netwerk/cache2/CacheEntry.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include "CacheLog.h"
michael@0 6 #include "CacheEntry.h"
michael@0 7 #include "CacheStorageService.h"
michael@0 8 #include "CacheObserver.h"
michael@0 9 #include "CacheFileUtils.h"
michael@0 10
michael@0 11 #include "nsIInputStream.h"
michael@0 12 #include "nsIOutputStream.h"
michael@0 13 #include "nsISeekableStream.h"
michael@0 14 #include "nsIURI.h"
michael@0 15 #include "nsICacheEntryOpenCallback.h"
michael@0 16 #include "nsICacheStorage.h"
michael@0 17 #include "nsISerializable.h"
michael@0 18 #include "nsIStreamTransportService.h"
michael@0 19 #include "nsISizeOf.h"
michael@0 20
michael@0 21 #include "nsComponentManagerUtils.h"
michael@0 22 #include "nsServiceManagerUtils.h"
michael@0 23 #include "nsString.h"
michael@0 24 #include "nsProxyRelease.h"
michael@0 25 #include "nsSerializationHelper.h"
michael@0 26 #include "nsThreadUtils.h"
michael@0 27 #include "mozilla/Telemetry.h"
michael@0 28 #include <math.h>
michael@0 29 #include <algorithm>
michael@0 30
michael@0 31 namespace mozilla {
michael@0 32 namespace net {
michael@0 33
michael@0 34 static uint32_t const ENTRY_WANTED =
michael@0 35 nsICacheEntryOpenCallback::ENTRY_WANTED;
michael@0 36 static uint32_t const RECHECK_AFTER_WRITE_FINISHED =
michael@0 37 nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
michael@0 38 static uint32_t const ENTRY_NEEDS_REVALIDATION =
michael@0 39 nsICacheEntryOpenCallback::ENTRY_NEEDS_REVALIDATION;
michael@0 40 static uint32_t const ENTRY_NOT_WANTED =
michael@0 41 nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
michael@0 42
michael@0 43 NS_IMPL_ISUPPORTS(CacheEntryHandle, nsICacheEntry)
michael@0 44
michael@0 45 // CacheEntryHandle
michael@0 46
michael@0 47 CacheEntryHandle::CacheEntryHandle(CacheEntry* aEntry)
michael@0 48 : mEntry(aEntry)
michael@0 49 {
michael@0 50 MOZ_COUNT_CTOR(CacheEntryHandle);
michael@0 51
michael@0 52 #ifdef DEBUG
michael@0 53 if (!mEntry->HandlesCount()) {
michael@0 54 // CacheEntry.mHandlesCount must go from zero to one only under
michael@0 55 // the service lock. Can access CacheStorageService::Self() w/o a check
michael@0 56 // since CacheEntry hrefs it.
michael@0 57 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 58 }
michael@0 59 #endif
michael@0 60
michael@0 61 mEntry->AddHandleRef();
michael@0 62
michael@0 63 LOG(("New CacheEntryHandle %p for entry %p", this, aEntry));
michael@0 64 }
michael@0 65
michael@0 66 CacheEntryHandle::~CacheEntryHandle()
michael@0 67 {
michael@0 68 mEntry->ReleaseHandleRef();
michael@0 69 mEntry->OnHandleClosed(this);
michael@0 70
michael@0 71 MOZ_COUNT_DTOR(CacheEntryHandle);
michael@0 72 }
michael@0 73
michael@0 74 // CacheEntry::Callback
michael@0 75
michael@0 76 CacheEntry::Callback::Callback(CacheEntry* aEntry,
michael@0 77 nsICacheEntryOpenCallback *aCallback,
michael@0 78 bool aReadOnly, bool aCheckOnAnyThread)
michael@0 79 : mEntry(aEntry)
michael@0 80 , mCallback(aCallback)
michael@0 81 , mTargetThread(do_GetCurrentThread())
michael@0 82 , mReadOnly(aReadOnly)
michael@0 83 , mCheckOnAnyThread(aCheckOnAnyThread)
michael@0 84 , mRecheckAfterWrite(false)
michael@0 85 , mNotWanted(false)
michael@0 86 {
michael@0 87 MOZ_COUNT_CTOR(CacheEntry::Callback);
michael@0 88
michael@0 89 // The counter may go from zero to non-null only under the service lock
michael@0 90 // but here we expect it to be already positive.
michael@0 91 MOZ_ASSERT(mEntry->HandlesCount());
michael@0 92 mEntry->AddHandleRef();
michael@0 93 }
michael@0 94
michael@0 95 CacheEntry::Callback::Callback(CacheEntry::Callback const &aThat)
michael@0 96 : mEntry(aThat.mEntry)
michael@0 97 , mCallback(aThat.mCallback)
michael@0 98 , mTargetThread(aThat.mTargetThread)
michael@0 99 , mReadOnly(aThat.mReadOnly)
michael@0 100 , mCheckOnAnyThread(aThat.mCheckOnAnyThread)
michael@0 101 , mRecheckAfterWrite(aThat.mRecheckAfterWrite)
michael@0 102 , mNotWanted(aThat.mNotWanted)
michael@0 103 {
michael@0 104 MOZ_COUNT_CTOR(CacheEntry::Callback);
michael@0 105
michael@0 106 // The counter may go from zero to non-null only under the service lock
michael@0 107 // but here we expect it to be already positive.
michael@0 108 MOZ_ASSERT(mEntry->HandlesCount());
michael@0 109 mEntry->AddHandleRef();
michael@0 110 }
michael@0 111
michael@0 112 CacheEntry::Callback::~Callback()
michael@0 113 {
michael@0 114 ProxyRelease(mCallback, mTargetThread);
michael@0 115
michael@0 116 mEntry->ReleaseHandleRef();
michael@0 117 MOZ_COUNT_DTOR(CacheEntry::Callback);
michael@0 118 }
michael@0 119
michael@0 120 void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry)
michael@0 121 {
michael@0 122 if (mEntry == aEntry)
michael@0 123 return;
michael@0 124
michael@0 125 // The counter may go from zero to non-null only under the service lock
michael@0 126 // but here we expect it to be already positive.
michael@0 127 MOZ_ASSERT(aEntry->HandlesCount());
michael@0 128 aEntry->AddHandleRef();
michael@0 129 mEntry->ReleaseHandleRef();
michael@0 130 mEntry = aEntry;
michael@0 131 }
michael@0 132
michael@0 133 nsresult CacheEntry::Callback::OnCheckThread(bool *aOnCheckThread) const
michael@0 134 {
michael@0 135 if (!mCheckOnAnyThread) {
michael@0 136 // Check we are on the target
michael@0 137 return mTargetThread->IsOnCurrentThread(aOnCheckThread);
michael@0 138 }
michael@0 139
michael@0 140 // We can invoke check anywhere
michael@0 141 *aOnCheckThread = true;
michael@0 142 return NS_OK;
michael@0 143 }
michael@0 144
michael@0 145 nsresult CacheEntry::Callback::OnAvailThread(bool *aOnAvailThread) const
michael@0 146 {
michael@0 147 return mTargetThread->IsOnCurrentThread(aOnAvailThread);
michael@0 148 }
michael@0 149
michael@0 150 // CacheEntry
michael@0 151
michael@0 152 NS_IMPL_ISUPPORTS(CacheEntry,
michael@0 153 nsICacheEntry,
michael@0 154 nsIRunnable,
michael@0 155 CacheFileListener)
michael@0 156
michael@0 157 CacheEntry::CacheEntry(const nsACString& aStorageID,
michael@0 158 nsIURI* aURI,
michael@0 159 const nsACString& aEnhanceID,
michael@0 160 bool aUseDisk)
michael@0 161 : mFrecency(0)
michael@0 162 , mSortingExpirationTime(uint32_t(-1))
michael@0 163 , mLock("CacheEntry")
michael@0 164 , mFileStatus(NS_ERROR_NOT_INITIALIZED)
michael@0 165 , mURI(aURI)
michael@0 166 , mEnhanceID(aEnhanceID)
michael@0 167 , mStorageID(aStorageID)
michael@0 168 , mUseDisk(aUseDisk)
michael@0 169 , mIsDoomed(false)
michael@0 170 , mSecurityInfoLoaded(false)
michael@0 171 , mPreventCallbacks(false)
michael@0 172 , mHasData(false)
michael@0 173 , mState(NOTLOADED)
michael@0 174 , mRegistration(NEVERREGISTERED)
michael@0 175 , mWriter(nullptr)
michael@0 176 , mPredictedDataSize(0)
michael@0 177 , mReleaseThread(NS_GetCurrentThread())
michael@0 178 {
michael@0 179 MOZ_COUNT_CTOR(CacheEntry);
michael@0 180
michael@0 181 mService = CacheStorageService::Self();
michael@0 182
michael@0 183 CacheStorageService::Self()->RecordMemoryOnlyEntry(
michael@0 184 this, !aUseDisk, true /* overwrite */);
michael@0 185 }
michael@0 186
michael@0 187 CacheEntry::~CacheEntry()
michael@0 188 {
michael@0 189 ProxyRelease(mURI, mReleaseThread);
michael@0 190
michael@0 191 LOG(("CacheEntry::~CacheEntry [this=%p]", this));
michael@0 192 MOZ_COUNT_DTOR(CacheEntry);
michael@0 193 }
michael@0 194
michael@0 195 #ifdef PR_LOG
michael@0 196
michael@0 197 char const * CacheEntry::StateString(uint32_t aState)
michael@0 198 {
michael@0 199 switch (aState) {
michael@0 200 case NOTLOADED: return "NOTLOADED";
michael@0 201 case LOADING: return "LOADING";
michael@0 202 case EMPTY: return "EMPTY";
michael@0 203 case WRITING: return "WRITING";
michael@0 204 case READY: return "READY";
michael@0 205 case REVALIDATING: return "REVALIDATING";
michael@0 206 }
michael@0 207
michael@0 208 return "?";
michael@0 209 }
michael@0 210
michael@0 211 #endif
michael@0 212
michael@0 213 nsresult CacheEntry::HashingKeyWithStorage(nsACString &aResult)
michael@0 214 {
michael@0 215 return HashingKey(mStorageID, mEnhanceID, mURI, aResult);
michael@0 216 }
michael@0 217
michael@0 218 nsresult CacheEntry::HashingKey(nsACString &aResult)
michael@0 219 {
michael@0 220 return HashingKey(EmptyCString(), mEnhanceID, mURI, aResult);
michael@0 221 }
michael@0 222
michael@0 223 // static
michael@0 224 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
michael@0 225 nsCSubstring const& aEnhanceID,
michael@0 226 nsIURI* aURI,
michael@0 227 nsACString &aResult)
michael@0 228 {
michael@0 229 nsAutoCString spec;
michael@0 230 nsresult rv = aURI->GetAsciiSpec(spec);
michael@0 231 NS_ENSURE_SUCCESS(rv, rv);
michael@0 232
michael@0 233 return HashingKey(aStorageID, aEnhanceID, spec, aResult);
michael@0 234 }
michael@0 235
michael@0 236 // static
michael@0 237 nsresult CacheEntry::HashingKey(nsCSubstring const& aStorageID,
michael@0 238 nsCSubstring const& aEnhanceID,
michael@0 239 nsCSubstring const& aURISpec,
michael@0 240 nsACString &aResult)
michael@0 241 {
michael@0 242 /**
michael@0 243 * This key is used to salt hash that is a base for disk file name.
michael@0 244 * Changing it will cause we will not be able to find files on disk.
michael@0 245 */
michael@0 246
michael@0 247 aResult.Append(aStorageID);
michael@0 248
michael@0 249 if (!aEnhanceID.IsEmpty()) {
michael@0 250 CacheFileUtils::AppendTagWithValue(aResult, '~', aEnhanceID);
michael@0 251 }
michael@0 252
michael@0 253 // Appending directly
michael@0 254 aResult.Append(':');
michael@0 255 aResult.Append(aURISpec);
michael@0 256
michael@0 257 return NS_OK;
michael@0 258 }
michael@0 259
michael@0 260 void CacheEntry::AsyncOpen(nsICacheEntryOpenCallback* aCallback, uint32_t aFlags)
michael@0 261 {
michael@0 262 LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]",
michael@0 263 this, StateString(mState), aFlags, aCallback));
michael@0 264
michael@0 265 bool readonly = aFlags & nsICacheStorage::OPEN_READONLY;
michael@0 266 bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
michael@0 267 bool priority = aFlags & nsICacheStorage::OPEN_PRIORITY;
michael@0 268 bool multithread = aFlags & nsICacheStorage::CHECK_MULTITHREADED;
michael@0 269
michael@0 270 MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
michael@0 271 MOZ_ASSERT(!(truncate && mState > LOADING), "Must not call truncate on already loaded entry");
michael@0 272
michael@0 273 Callback callback(this, aCallback, readonly, multithread);
michael@0 274
michael@0 275 mozilla::MutexAutoLock lock(mLock);
michael@0 276
michael@0 277 RememberCallback(callback);
michael@0 278
michael@0 279 // Load() opens the lock
michael@0 280 if (Load(truncate, priority)) {
michael@0 281 // Loading is in progress...
michael@0 282 return;
michael@0 283 }
michael@0 284
michael@0 285 InvokeCallbacks();
michael@0 286 }
michael@0 287
michael@0 288 bool CacheEntry::Load(bool aTruncate, bool aPriority)
michael@0 289 {
michael@0 290 LOG(("CacheEntry::Load [this=%p, trunc=%d]", this, aTruncate));
michael@0 291
michael@0 292 mLock.AssertCurrentThreadOwns();
michael@0 293
michael@0 294 if (mState > LOADING) {
michael@0 295 LOG((" already loaded"));
michael@0 296 return false;
michael@0 297 }
michael@0 298
michael@0 299 if (mState == LOADING) {
michael@0 300 LOG((" already loading"));
michael@0 301 return true;
michael@0 302 }
michael@0 303
michael@0 304 mState = LOADING;
michael@0 305
michael@0 306 MOZ_ASSERT(!mFile);
michael@0 307
michael@0 308 bool directLoad = aTruncate || !mUseDisk;
michael@0 309 if (directLoad)
michael@0 310 mFileStatus = NS_OK;
michael@0 311 else
michael@0 312 mLoadStart = TimeStamp::Now();
michael@0 313
michael@0 314 mFile = new CacheFile();
michael@0 315
michael@0 316 BackgroundOp(Ops::REGISTER);
michael@0 317
michael@0 318 {
michael@0 319 mozilla::MutexAutoUnlock unlock(mLock);
michael@0 320
michael@0 321 nsresult rv;
michael@0 322
michael@0 323 nsAutoCString fileKey;
michael@0 324 rv = HashingKeyWithStorage(fileKey);
michael@0 325
michael@0 326 LOG((" performing load, file=%p", mFile.get()));
michael@0 327 if (NS_SUCCEEDED(rv)) {
michael@0 328 rv = mFile->Init(fileKey,
michael@0 329 aTruncate,
michael@0 330 !mUseDisk,
michael@0 331 aPriority,
michael@0 332 directLoad ? nullptr : this);
michael@0 333 }
michael@0 334
michael@0 335 if (NS_FAILED(rv)) {
michael@0 336 mFileStatus = rv;
michael@0 337 AsyncDoom(nullptr);
michael@0 338 return false;
michael@0 339 }
michael@0 340 }
michael@0 341
michael@0 342 if (directLoad) {
michael@0 343 // Just fake the load has already been done as "new".
michael@0 344 mState = EMPTY;
michael@0 345 }
michael@0 346
michael@0 347 return mState == LOADING;
michael@0 348 }
michael@0 349
michael@0 350 NS_IMETHODIMP CacheEntry::OnFileReady(nsresult aResult, bool aIsNew)
michael@0 351 {
michael@0 352 LOG(("CacheEntry::OnFileReady [this=%p, rv=0x%08x, new=%d]",
michael@0 353 this, aResult, aIsNew));
michael@0 354
michael@0 355 MOZ_ASSERT(!mLoadStart.IsNull());
michael@0 356
michael@0 357 if (NS_SUCCEEDED(aResult)) {
michael@0 358 if (aIsNew) {
michael@0 359 mozilla::Telemetry::AccumulateTimeDelta(
michael@0 360 mozilla::Telemetry::NETWORK_CACHE_V2_MISS_TIME_MS,
michael@0 361 mLoadStart);
michael@0 362 }
michael@0 363 else {
michael@0 364 mozilla::Telemetry::AccumulateTimeDelta(
michael@0 365 mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS,
michael@0 366 mLoadStart);
michael@0 367 }
michael@0 368 }
michael@0 369
michael@0 370 // OnFileReady, that is the only code that can transit from LOADING
michael@0 371 // to any follow-on state, can only be invoked ones on an entry,
michael@0 372 // thus no need to lock. Until this moment there is no consumer that
michael@0 373 // could manipulate the entry state.
michael@0 374 mozilla::MutexAutoLock lock(mLock);
michael@0 375
michael@0 376 MOZ_ASSERT(mState == LOADING);
michael@0 377
michael@0 378 mState = (aIsNew || NS_FAILED(aResult))
michael@0 379 ? EMPTY
michael@0 380 : READY;
michael@0 381
michael@0 382 mFileStatus = aResult;
michael@0 383
michael@0 384 if (mState == READY) {
michael@0 385 mHasData = true;
michael@0 386
michael@0 387 uint32_t frecency;
michael@0 388 mFile->GetFrecency(&frecency);
michael@0 389 // mFrecency is held in a double to increase computance precision.
michael@0 390 // It is ok to persist frecency only as a uint32 with some math involved.
michael@0 391 mFrecency = INT2FRECENCY(frecency);
michael@0 392 }
michael@0 393
michael@0 394 InvokeCallbacks();
michael@0 395 return NS_OK;
michael@0 396 }
michael@0 397
michael@0 398 NS_IMETHODIMP CacheEntry::OnFileDoomed(nsresult aResult)
michael@0 399 {
michael@0 400 if (mDoomCallback) {
michael@0 401 nsRefPtr<DoomCallbackRunnable> event =
michael@0 402 new DoomCallbackRunnable(this, aResult);
michael@0 403 NS_DispatchToMainThread(event);
michael@0 404 }
michael@0 405
michael@0 406 return NS_OK;
michael@0 407 }
michael@0 408
michael@0 409 already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
michael@0 410 nsICacheEntryOpenCallback* aCallback)
michael@0 411 {
michael@0 412 LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
michael@0 413
michael@0 414 mLock.AssertCurrentThreadOwns();
michael@0 415
michael@0 416 // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
michael@0 417 mPreventCallbacks = true;
michael@0 418
michael@0 419 nsRefPtr<CacheEntryHandle> handle;
michael@0 420 nsRefPtr<CacheEntry> newEntry;
michael@0 421 {
michael@0 422 mozilla::MutexAutoUnlock unlock(mLock);
michael@0 423
michael@0 424 // The following call dooms this entry (calls DoomAlreadyRemoved on us)
michael@0 425 nsresult rv = CacheStorageService::Self()->AddStorageEntry(
michael@0 426 GetStorageID(), GetURI(), GetEnhanceID(),
michael@0 427 mUseDisk && !aMemoryOnly,
michael@0 428 true, // always create
michael@0 429 true, // truncate existing (this one)
michael@0 430 getter_AddRefs(handle));
michael@0 431
michael@0 432 if (NS_SUCCEEDED(rv)) {
michael@0 433 newEntry = handle->Entry();
michael@0 434 LOG((" exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
michael@0 435 newEntry->AsyncOpen(aCallback, nsICacheStorage::OPEN_TRUNCATE);
michael@0 436 } else {
michael@0 437 LOG((" exchanged of entry %p failed, rv=0x%08x", this, rv));
michael@0 438 AsyncDoom(nullptr);
michael@0 439 }
michael@0 440 }
michael@0 441
michael@0 442 mPreventCallbacks = false;
michael@0 443
michael@0 444 if (!newEntry)
michael@0 445 return nullptr;
michael@0 446
michael@0 447 newEntry->TransferCallbacks(*this);
michael@0 448 mCallbacks.Clear();
michael@0 449
michael@0 450 return handle.forget();
michael@0 451 }
michael@0 452
michael@0 453 void CacheEntry::TransferCallbacks(CacheEntry & aFromEntry)
michael@0 454 {
michael@0 455 mozilla::MutexAutoLock lock(mLock);
michael@0 456
michael@0 457 LOG(("CacheEntry::TransferCallbacks [entry=%p, from=%p]",
michael@0 458 this, &aFromEntry));
michael@0 459
michael@0 460 if (!mCallbacks.Length())
michael@0 461 mCallbacks.SwapElements(aFromEntry.mCallbacks);
michael@0 462 else
michael@0 463 mCallbacks.AppendElements(aFromEntry.mCallbacks);
michael@0 464
michael@0 465 uint32_t callbacksLength = mCallbacks.Length();
michael@0 466 if (callbacksLength) {
michael@0 467 // Carry the entry reference (unfortunatelly, needs to be done manually...)
michael@0 468 for (uint32_t i = 0; i < callbacksLength; ++i)
michael@0 469 mCallbacks[i].ExchangeEntry(this);
michael@0 470
michael@0 471 BackgroundOp(Ops::CALLBACKS, true);
michael@0 472 }
michael@0 473 }
michael@0 474
michael@0 475 void CacheEntry::RememberCallback(Callback const& aCallback)
michael@0 476 {
michael@0 477 LOG(("CacheEntry::RememberCallback [this=%p, cb=%p]", this, aCallback.mCallback.get()));
michael@0 478
michael@0 479 mLock.AssertCurrentThreadOwns();
michael@0 480
michael@0 481 mCallbacks.AppendElement(aCallback);
michael@0 482 }
michael@0 483
michael@0 484 void CacheEntry::InvokeCallbacksLock()
michael@0 485 {
michael@0 486 mozilla::MutexAutoLock lock(mLock);
michael@0 487 InvokeCallbacks();
michael@0 488 }
michael@0 489
michael@0 490 void CacheEntry::InvokeCallbacks()
michael@0 491 {
michael@0 492 mLock.AssertCurrentThreadOwns();
michael@0 493
michael@0 494 LOG(("CacheEntry::InvokeCallbacks BEGIN [this=%p]", this));
michael@0 495
michael@0 496 // Invoke first all r/w callbacks, then all r/o callbacks.
michael@0 497 if (InvokeCallbacks(false))
michael@0 498 InvokeCallbacks(true);
michael@0 499
michael@0 500 LOG(("CacheEntry::InvokeCallbacks END [this=%p]", this));
michael@0 501 }
michael@0 502
michael@0 503 bool CacheEntry::InvokeCallbacks(bool aReadOnly)
michael@0 504 {
michael@0 505 mLock.AssertCurrentThreadOwns();
michael@0 506
michael@0 507 uint32_t i = 0;
michael@0 508 while (i < mCallbacks.Length()) {
michael@0 509 if (mPreventCallbacks) {
michael@0 510 LOG((" callbacks prevented!"));
michael@0 511 return false;
michael@0 512 }
michael@0 513
michael@0 514 if (!mIsDoomed && (mState == WRITING || mState == REVALIDATING)) {
michael@0 515 LOG((" entry is being written/revalidated"));
michael@0 516 return false;
michael@0 517 }
michael@0 518
michael@0 519 if (mCallbacks[i].mReadOnly != aReadOnly) {
michael@0 520 // Callback is not r/w or r/o, go to another one in line
michael@0 521 ++i;
michael@0 522 continue;
michael@0 523 }
michael@0 524
michael@0 525 bool onCheckThread;
michael@0 526 nsresult rv = mCallbacks[i].OnCheckThread(&onCheckThread);
michael@0 527
michael@0 528 if (NS_SUCCEEDED(rv) && !onCheckThread) {
michael@0 529 // Redispatch to the target thread
michael@0 530 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
michael@0 531 NS_NewRunnableMethod(this, &CacheEntry::InvokeCallbacksLock);
michael@0 532
michael@0 533 rv = mCallbacks[i].mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
michael@0 534 if (NS_SUCCEEDED(rv)) {
michael@0 535 LOG((" re-dispatching to target thread"));
michael@0 536 return false;
michael@0 537 }
michael@0 538 }
michael@0 539
michael@0 540 Callback callback = mCallbacks[i];
michael@0 541 mCallbacks.RemoveElementAt(i);
michael@0 542
michael@0 543 if (NS_SUCCEEDED(rv) && !InvokeCallback(callback)) {
michael@0 544 // Callback didn't fire, put it back and go to another one in line.
michael@0 545 // Only reason InvokeCallback returns false is that onCacheEntryCheck
michael@0 546 // returns RECHECK_AFTER_WRITE_FINISHED. If we would stop the loop, other
michael@0 547 // readers or potential writers would be unnecessarily kept from being
michael@0 548 // invoked.
michael@0 549 mCallbacks.InsertElementAt(i, callback);
michael@0 550 ++i;
michael@0 551 }
michael@0 552 }
michael@0 553
michael@0 554 return true;
michael@0 555 }
michael@0 556
michael@0 557 bool CacheEntry::InvokeCallback(Callback & aCallback)
michael@0 558 {
michael@0 559 LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]",
michael@0 560 this, StateString(mState), aCallback.mCallback.get()));
michael@0 561
michael@0 562 mLock.AssertCurrentThreadOwns();
michael@0 563
michael@0 564 // When this entry is doomed we want to notify the callback any time
michael@0 565 if (!mIsDoomed) {
michael@0 566 // When we are here, the entry must be loaded from disk
michael@0 567 MOZ_ASSERT(mState > LOADING);
michael@0 568
michael@0 569 if (mState == WRITING || mState == REVALIDATING) {
michael@0 570 // Prevent invoking other callbacks since one of them is now writing
michael@0 571 // or revalidating this entry. No consumers should get this entry
michael@0 572 // until metadata are filled with values downloaded from the server
michael@0 573 // or the entry revalidated and output stream has been opened.
michael@0 574 LOG((" entry is being written/revalidated, callback bypassed"));
michael@0 575 return false;
michael@0 576 }
michael@0 577
michael@0 578 // mRecheckAfterWrite flag already set means the callback has already passed
michael@0 579 // the onCacheEntryCheck call. Until the current write is not finished this
michael@0 580 // callback will be bypassed.
michael@0 581 if (!aCallback.mRecheckAfterWrite) {
michael@0 582
michael@0 583 if (!aCallback.mReadOnly) {
michael@0 584 if (mState == EMPTY) {
michael@0 585 // Advance to writing state, we expect to invoke the callback and let
michael@0 586 // it fill content of this entry. Must set and check the state here
michael@0 587 // to prevent more then one
michael@0 588 mState = WRITING;
michael@0 589 LOG((" advancing to WRITING state"));
michael@0 590 }
michael@0 591
michael@0 592 if (!aCallback.mCallback) {
michael@0 593 // We can be given no callback only in case of recreate, it is ok
michael@0 594 // to advance to WRITING state since the caller of recreate is expected
michael@0 595 // to write this entry now.
michael@0 596 return true;
michael@0 597 }
michael@0 598 }
michael@0 599
michael@0 600 if (mState == READY) {
michael@0 601 // Metadata present, validate the entry
michael@0 602 uint32_t checkResult;
michael@0 603 {
michael@0 604 // mayhemer: TODO check and solve any potential races of concurent OnCacheEntryCheck
michael@0 605 mozilla::MutexAutoUnlock unlock(mLock);
michael@0 606
michael@0 607 nsresult rv = aCallback.mCallback->OnCacheEntryCheck(
michael@0 608 this, nullptr, &checkResult);
michael@0 609 LOG((" OnCacheEntryCheck: rv=0x%08x, result=%d", rv, checkResult));
michael@0 610
michael@0 611 if (NS_FAILED(rv))
michael@0 612 checkResult = ENTRY_NOT_WANTED;
michael@0 613 }
michael@0 614
michael@0 615 switch (checkResult) {
michael@0 616 case ENTRY_WANTED:
michael@0 617 // Nothing more to do here, the consumer is responsible to handle
michael@0 618 // the result of OnCacheEntryCheck it self.
michael@0 619 // Proceed to callback...
michael@0 620 break;
michael@0 621
michael@0 622 case RECHECK_AFTER_WRITE_FINISHED:
michael@0 623 LOG((" consumer will check on the entry again after write is done"));
michael@0 624 // The consumer wants the entry to complete first.
michael@0 625 aCallback.mRecheckAfterWrite = true;
michael@0 626 break;
michael@0 627
michael@0 628 case ENTRY_NEEDS_REVALIDATION:
michael@0 629 LOG((" will be holding callbacks until entry is revalidated"));
michael@0 630 // State is READY now and from that state entry cannot transit to any other
michael@0 631 // state then REVALIDATING for which cocurrency is not an issue. Potentially
michael@0 632 // no need to lock here.
michael@0 633 mState = REVALIDATING;
michael@0 634 break;
michael@0 635
michael@0 636 case ENTRY_NOT_WANTED:
michael@0 637 LOG((" consumer not interested in the entry"));
michael@0 638 // Do not give this entry to the consumer, it is not interested in us.
michael@0 639 aCallback.mNotWanted = true;
michael@0 640 break;
michael@0 641 }
michael@0 642 }
michael@0 643 }
michael@0 644 }
michael@0 645
michael@0 646 if (aCallback.mCallback) {
michael@0 647 if (!mIsDoomed && aCallback.mRecheckAfterWrite) {
michael@0 648 // If we don't have data and the callback wants a complete entry,
michael@0 649 // don't invoke now.
michael@0 650 bool bypass = !mHasData;
michael@0 651 if (!bypass) {
michael@0 652 int64_t _unused;
michael@0 653 bypass = !mFile->DataSize(&_unused);
michael@0 654 }
michael@0 655
michael@0 656 if (bypass) {
michael@0 657 LOG((" bypassing, entry data still being written"));
michael@0 658 return false;
michael@0 659 }
michael@0 660
michael@0 661 // Entry is complete now, do the check+avail call again
michael@0 662 aCallback.mRecheckAfterWrite = false;
michael@0 663 return InvokeCallback(aCallback);
michael@0 664 }
michael@0 665
michael@0 666 mozilla::MutexAutoUnlock unlock(mLock);
michael@0 667 InvokeAvailableCallback(aCallback);
michael@0 668 }
michael@0 669
michael@0 670 return true;
michael@0 671 }
michael@0 672
michael@0 673 void CacheEntry::InvokeAvailableCallback(Callback const & aCallback)
michael@0 674 {
michael@0 675 LOG(("CacheEntry::InvokeAvailableCallback [this=%p, state=%s, cb=%p, r/o=%d, n/w=%d]",
michael@0 676 this, StateString(mState), aCallback.mCallback.get(), aCallback.mReadOnly, aCallback.mNotWanted));
michael@0 677
michael@0 678 nsresult rv;
michael@0 679
michael@0 680 uint32_t const state = mState;
michael@0 681
michael@0 682 // When we are here, the entry must be loaded from disk
michael@0 683 MOZ_ASSERT(state > LOADING || mIsDoomed);
michael@0 684
michael@0 685 bool onAvailThread;
michael@0 686 rv = aCallback.OnAvailThread(&onAvailThread);
michael@0 687 if (NS_FAILED(rv)) {
michael@0 688 LOG((" target thread dead?"));
michael@0 689 return;
michael@0 690 }
michael@0 691
michael@0 692 if (!onAvailThread) {
michael@0 693 // Dispatch to the right thread
michael@0 694 nsRefPtr<AvailableCallbackRunnable> event =
michael@0 695 new AvailableCallbackRunnable(this, aCallback);
michael@0 696
michael@0 697 rv = aCallback.mTargetThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
michael@0 698 LOG((" redispatched, (rv = 0x%08x)", rv));
michael@0 699 return;
michael@0 700 }
michael@0 701
michael@0 702 if (mIsDoomed || aCallback.mNotWanted) {
michael@0 703 LOG((" doomed or not wanted, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
michael@0 704 aCallback.mCallback->OnCacheEntryAvailable(
michael@0 705 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
michael@0 706 return;
michael@0 707 }
michael@0 708
michael@0 709 if (state == READY) {
michael@0 710 LOG((" ready/has-meta, notifying OCEA with entry and NS_OK"));
michael@0 711 {
michael@0 712 mozilla::MutexAutoLock lock(mLock);
michael@0 713 BackgroundOp(Ops::FRECENCYUPDATE);
michael@0 714 }
michael@0 715
michael@0 716 nsRefPtr<CacheEntryHandle> handle = NewHandle();
michael@0 717 aCallback.mCallback->OnCacheEntryAvailable(
michael@0 718 handle, false, nullptr, NS_OK);
michael@0 719 return;
michael@0 720 }
michael@0 721
michael@0 722 if (aCallback.mReadOnly) {
michael@0 723 LOG((" r/o and not ready, notifying OCEA with NS_ERROR_CACHE_KEY_NOT_FOUND"));
michael@0 724 aCallback.mCallback->OnCacheEntryAvailable(
michael@0 725 nullptr, false, nullptr, NS_ERROR_CACHE_KEY_NOT_FOUND);
michael@0 726 return;
michael@0 727 }
michael@0 728
michael@0 729 // This is a new or potentially non-valid entry and needs to be fetched first.
michael@0 730 // The CacheEntryHandle blocks other consumers until the channel
michael@0 731 // either releases the entry or marks metadata as filled or whole entry valid,
michael@0 732 // i.e. until MetaDataReady() or SetValid() on the entry is called respectively.
michael@0 733
michael@0 734 // Consumer will be responsible to fill or validate the entry metadata and data.
michael@0 735
michael@0 736 nsRefPtr<CacheEntryHandle> handle = NewWriteHandle();
michael@0 737 rv = aCallback.mCallback->OnCacheEntryAvailable(
michael@0 738 handle, state == WRITING, nullptr, NS_OK);
michael@0 739
michael@0 740 if (NS_FAILED(rv)) {
michael@0 741 LOG((" writing/revalidating failed (0x%08x)", rv));
michael@0 742
michael@0 743 // Consumer given a new entry failed to take care of the entry.
michael@0 744 OnHandleClosed(handle);
michael@0 745 return;
michael@0 746 }
michael@0 747
michael@0 748 LOG((" writing/revalidating"));
michael@0 749 }
michael@0 750
michael@0 751 CacheEntryHandle* CacheEntry::NewHandle()
michael@0 752 {
michael@0 753 return new CacheEntryHandle(this);
michael@0 754 }
michael@0 755
michael@0 756 CacheEntryHandle* CacheEntry::NewWriteHandle()
michael@0 757 {
michael@0 758 mozilla::MutexAutoLock lock(mLock);
michael@0 759
michael@0 760 BackgroundOp(Ops::FRECENCYUPDATE);
michael@0 761 return (mWriter = new CacheEntryHandle(this));
michael@0 762 }
michael@0 763
michael@0 764 void CacheEntry::OnHandleClosed(CacheEntryHandle const* aHandle)
michael@0 765 {
michael@0 766 LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this, StateString(mState), aHandle));
michael@0 767
michael@0 768 nsCOMPtr<nsIOutputStream> outputStream;
michael@0 769
michael@0 770 {
michael@0 771 mozilla::MutexAutoLock lock(mLock);
michael@0 772
michael@0 773 if (mWriter != aHandle) {
michael@0 774 LOG((" not the writer"));
michael@0 775 return;
michael@0 776 }
michael@0 777
michael@0 778 if (mOutputStream) {
michael@0 779 // No one took our internal output stream, so there are no data
michael@0 780 // and output stream has to be open symultaneously with input stream
michael@0 781 // on this entry again.
michael@0 782 mHasData = false;
michael@0 783 }
michael@0 784
michael@0 785 outputStream.swap(mOutputStream);
michael@0 786 mWriter = nullptr;
michael@0 787
michael@0 788 if (mState == WRITING) {
michael@0 789 LOG((" reverting to state EMPTY - write failed"));
michael@0 790 mState = EMPTY;
michael@0 791 }
michael@0 792 else if (mState == REVALIDATING) {
michael@0 793 LOG((" reverting to state READY - reval failed"));
michael@0 794 mState = READY;
michael@0 795 }
michael@0 796
michael@0 797 InvokeCallbacks();
michael@0 798 }
michael@0 799
michael@0 800 if (outputStream) {
michael@0 801 LOG((" abandoning phantom output stream"));
michael@0 802 outputStream->Close();
michael@0 803 }
michael@0 804 }
michael@0 805
michael@0 806 void CacheEntry::OnOutputClosed()
michael@0 807 {
michael@0 808 // Called when the file's output stream is closed. Invoke any callbacks
michael@0 809 // waiting for complete entry.
michael@0 810
michael@0 811 mozilla::MutexAutoLock lock(mLock);
michael@0 812 InvokeCallbacks();
michael@0 813 }
michael@0 814
michael@0 815 bool CacheEntry::IsUsingDiskLocked() const
michael@0 816 {
michael@0 817 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 818
michael@0 819 return IsUsingDisk();
michael@0 820 }
michael@0 821
michael@0 822 bool CacheEntry::SetUsingDisk(bool aUsingDisk)
michael@0 823 {
michael@0 824 // Called by the service when this entry is reopen to reflect
michael@0 825 // demanded storage target.
michael@0 826
michael@0 827 if (mState >= READY) {
michael@0 828 // Don't modify after this entry has been filled.
michael@0 829 return false;
michael@0 830 }
michael@0 831
michael@0 832 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 833
michael@0 834 bool changed = mUseDisk != aUsingDisk;
michael@0 835 mUseDisk = aUsingDisk;
michael@0 836 return changed;
michael@0 837 }
michael@0 838
michael@0 839 bool CacheEntry::IsReferenced() const
michael@0 840 {
michael@0 841 CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
michael@0 842
michael@0 843 // Increasing this counter from 0 to non-null and this check both happen only
michael@0 844 // under the service lock.
michael@0 845 return mHandlesCount > 0;
michael@0 846 }
michael@0 847
michael@0 848 bool CacheEntry::IsFileDoomed()
michael@0 849 {
michael@0 850 mozilla::MutexAutoLock lock(mLock);
michael@0 851
michael@0 852 if (NS_SUCCEEDED(mFileStatus)) {
michael@0 853 return mFile->IsDoomed();
michael@0 854 }
michael@0 855
michael@0 856 return false;
michael@0 857 }
michael@0 858
michael@0 859 uint32_t CacheEntry::GetMetadataMemoryConsumption()
michael@0 860 {
michael@0 861 NS_ENSURE_SUCCESS(mFileStatus, 0);
michael@0 862
michael@0 863 uint32_t size;
michael@0 864 if (NS_FAILED(mFile->ElementsSize(&size)))
michael@0 865 return 0;
michael@0 866
michael@0 867 return size;
michael@0 868 }
michael@0 869
michael@0 870 // nsICacheEntry
michael@0 871
michael@0 872 NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk)
michael@0 873 {
michael@0 874 // No need to sync when only reading.
michael@0 875 // When consumer needs to be consistent with state of the memory storage entries
michael@0 876 // table, then let it use GetUseDisk getter that must be called under the service lock.
michael@0 877 *aPersistToDisk = mUseDisk;
michael@0 878 return NS_OK;
michael@0 879 }
michael@0 880
michael@0 881 NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
michael@0 882 {
michael@0 883 return mURI->GetAsciiSpec(aKey);
michael@0 884 }
michael@0 885
michael@0 886 NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
michael@0 887 {
michael@0 888 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 889
michael@0 890 return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(aFetchCount));
michael@0 891 }
michael@0 892
michael@0 893 NS_IMETHODIMP CacheEntry::GetLastFetched(uint32_t *aLastFetched)
michael@0 894 {
michael@0 895 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 896
michael@0 897 return mFile->GetLastFetched(aLastFetched);
michael@0 898 }
michael@0 899
michael@0 900 NS_IMETHODIMP CacheEntry::GetLastModified(uint32_t *aLastModified)
michael@0 901 {
michael@0 902 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 903
michael@0 904 return mFile->GetLastModified(aLastModified);
michael@0 905 }
michael@0 906
michael@0 907 NS_IMETHODIMP CacheEntry::GetExpirationTime(uint32_t *aExpirationTime)
michael@0 908 {
michael@0 909 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 910
michael@0 911 return mFile->GetExpirationTime(aExpirationTime);
michael@0 912 }
michael@0 913
michael@0 914 NS_IMETHODIMP CacheEntry::SetExpirationTime(uint32_t aExpirationTime)
michael@0 915 {
michael@0 916 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 917
michael@0 918 nsresult rv = mFile->SetExpirationTime(aExpirationTime);
michael@0 919 NS_ENSURE_SUCCESS(rv, rv);
michael@0 920
michael@0 921 // Aligned assignment, thus atomic.
michael@0 922 mSortingExpirationTime = aExpirationTime;
michael@0 923 return NS_OK;
michael@0 924 }
michael@0 925
michael@0 926 NS_IMETHODIMP CacheEntry::OpenInputStream(int64_t offset, nsIInputStream * *_retval)
michael@0 927 {
michael@0 928 LOG(("CacheEntry::OpenInputStream [this=%p]", this));
michael@0 929
michael@0 930 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 931
michael@0 932 nsresult rv;
michael@0 933
michael@0 934 nsCOMPtr<nsIInputStream> stream;
michael@0 935 rv = mFile->OpenInputStream(getter_AddRefs(stream));
michael@0 936 NS_ENSURE_SUCCESS(rv, rv);
michael@0 937
michael@0 938 nsCOMPtr<nsISeekableStream> seekable =
michael@0 939 do_QueryInterface(stream, &rv);
michael@0 940 NS_ENSURE_SUCCESS(rv, rv);
michael@0 941
michael@0 942 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
michael@0 943 NS_ENSURE_SUCCESS(rv, rv);
michael@0 944
michael@0 945 mozilla::MutexAutoLock lock(mLock);
michael@0 946
michael@0 947 if (!mHasData) {
michael@0 948 // So far output stream on this new entry not opened, do it now.
michael@0 949 LOG((" creating phantom output stream"));
michael@0 950 rv = OpenOutputStreamInternal(0, getter_AddRefs(mOutputStream));
michael@0 951 NS_ENSURE_SUCCESS(rv, rv);
michael@0 952 }
michael@0 953
michael@0 954 stream.forget(_retval);
michael@0 955 return NS_OK;
michael@0 956 }
michael@0 957
michael@0 958 NS_IMETHODIMP CacheEntry::OpenOutputStream(int64_t offset, nsIOutputStream * *_retval)
michael@0 959 {
michael@0 960 LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
michael@0 961
michael@0 962 nsresult rv;
michael@0 963
michael@0 964 mozilla::MutexAutoLock lock(mLock);
michael@0 965
michael@0 966 MOZ_ASSERT(mState > EMPTY);
michael@0 967
michael@0 968 if (mOutputStream && !mIsDoomed) {
michael@0 969 LOG((" giving phantom output stream"));
michael@0 970 mOutputStream.forget(_retval);
michael@0 971 }
michael@0 972 else {
michael@0 973 rv = OpenOutputStreamInternal(offset, _retval);
michael@0 974 if (NS_FAILED(rv)) return rv;
michael@0 975 }
michael@0 976
michael@0 977 // Entry considered ready when writer opens output stream.
michael@0 978 if (mState < READY)
michael@0 979 mState = READY;
michael@0 980
michael@0 981 // Invoke any pending readers now.
michael@0 982 InvokeCallbacks();
michael@0 983
michael@0 984 return NS_OK;
michael@0 985 }
michael@0 986
michael@0 987 nsresult CacheEntry::OpenOutputStreamInternal(int64_t offset, nsIOutputStream * *_retval)
michael@0 988 {
michael@0 989 LOG(("CacheEntry::OpenOutputStreamInternal [this=%p]", this));
michael@0 990
michael@0 991 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 992
michael@0 993 mLock.AssertCurrentThreadOwns();
michael@0 994
michael@0 995 if (mIsDoomed) {
michael@0 996 LOG((" doomed..."));
michael@0 997 return NS_ERROR_NOT_AVAILABLE;
michael@0 998 }
michael@0 999
michael@0 1000 MOZ_ASSERT(mState > LOADING);
michael@0 1001
michael@0 1002 nsresult rv;
michael@0 1003
michael@0 1004 // No need to sync on mUseDisk here, we don't need to be consistent
michael@0 1005 // with content of the memory storage entries hash table.
michael@0 1006 if (!mUseDisk) {
michael@0 1007 rv = mFile->SetMemoryOnly();
michael@0 1008 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1009 }
michael@0 1010
michael@0 1011 nsRefPtr<CacheOutputCloseListener> listener =
michael@0 1012 new CacheOutputCloseListener(this);
michael@0 1013
michael@0 1014 nsCOMPtr<nsIOutputStream> stream;
michael@0 1015 rv = mFile->OpenOutputStream(listener, getter_AddRefs(stream));
michael@0 1016 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1017
michael@0 1018 nsCOMPtr<nsISeekableStream> seekable =
michael@0 1019 do_QueryInterface(stream, &rv);
michael@0 1020 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1021
michael@0 1022 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
michael@0 1023 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1024
michael@0 1025 // Prevent opening output stream again.
michael@0 1026 mHasData = true;
michael@0 1027
michael@0 1028 stream.swap(*_retval);
michael@0 1029 return NS_OK;
michael@0 1030 }
michael@0 1031
michael@0 1032 NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
michael@0 1033 {
michael@0 1034 *aPredictedDataSize = mPredictedDataSize;
michael@0 1035 return NS_OK;
michael@0 1036 }
michael@0 1037 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
michael@0 1038 {
michael@0 1039 mPredictedDataSize = aPredictedDataSize;
michael@0 1040
michael@0 1041 if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
michael@0 1042 LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
michael@0 1043 AsyncDoom(nullptr);
michael@0 1044
michael@0 1045 return NS_ERROR_FILE_TOO_BIG;
michael@0 1046 }
michael@0 1047
michael@0 1048 return NS_OK;
michael@0 1049 }
michael@0 1050
michael@0 1051 NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
michael@0 1052 {
michael@0 1053 {
michael@0 1054 mozilla::MutexAutoLock lock(mLock);
michael@0 1055 if (mSecurityInfoLoaded) {
michael@0 1056 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
michael@0 1057 return NS_OK;
michael@0 1058 }
michael@0 1059 }
michael@0 1060
michael@0 1061 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 1062
michael@0 1063 nsXPIDLCString info;
michael@0 1064 nsCOMPtr<nsISupports> secInfo;
michael@0 1065 nsresult rv;
michael@0 1066
michael@0 1067 rv = mFile->GetElement("security-info", getter_Copies(info));
michael@0 1068 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1069
michael@0 1070 if (info) {
michael@0 1071 rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
michael@0 1072 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1073 }
michael@0 1074
michael@0 1075 {
michael@0 1076 mozilla::MutexAutoLock lock(mLock);
michael@0 1077
michael@0 1078 mSecurityInfo.swap(secInfo);
michael@0 1079 mSecurityInfoLoaded = true;
michael@0 1080
michael@0 1081 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
michael@0 1082 }
michael@0 1083
michael@0 1084 return NS_OK;
michael@0 1085 }
michael@0 1086 NS_IMETHODIMP CacheEntry::SetSecurityInfo(nsISupports *aSecurityInfo)
michael@0 1087 {
michael@0 1088 nsresult rv;
michael@0 1089
michael@0 1090 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
michael@0 1091
michael@0 1092 {
michael@0 1093 mozilla::MutexAutoLock lock(mLock);
michael@0 1094
michael@0 1095 mSecurityInfo = aSecurityInfo;
michael@0 1096 mSecurityInfoLoaded = true;
michael@0 1097 }
michael@0 1098
michael@0 1099 nsCOMPtr<nsISerializable> serializable =
michael@0 1100 do_QueryInterface(aSecurityInfo);
michael@0 1101 if (aSecurityInfo && !serializable)
michael@0 1102 return NS_ERROR_UNEXPECTED;
michael@0 1103
michael@0 1104 nsCString info;
michael@0 1105 if (serializable) {
michael@0 1106 rv = NS_SerializeToString(serializable, info);
michael@0 1107 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1108 }
michael@0 1109
michael@0 1110 rv = mFile->SetElement("security-info", info.Length() ? info.get() : nullptr);
michael@0 1111 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1112
michael@0 1113 return NS_OK;
michael@0 1114 }
michael@0 1115
michael@0 1116 NS_IMETHODIMP CacheEntry::GetStorageDataSize(uint32_t *aStorageDataSize)
michael@0 1117 {
michael@0 1118 NS_ENSURE_ARG(aStorageDataSize);
michael@0 1119
michael@0 1120 int64_t dataSize;
michael@0 1121 nsresult rv = GetDataSize(&dataSize);
michael@0 1122 if (NS_FAILED(rv))
michael@0 1123 return rv;
michael@0 1124
michael@0 1125 *aStorageDataSize = (uint32_t)std::min(int64_t(uint32_t(-1)), dataSize);
michael@0 1126
michael@0 1127 return NS_OK;
michael@0 1128 }
michael@0 1129
michael@0 1130 NS_IMETHODIMP CacheEntry::AsyncDoom(nsICacheEntryDoomCallback *aCallback)
michael@0 1131 {
michael@0 1132 LOG(("CacheEntry::AsyncDoom [this=%p]", this));
michael@0 1133
michael@0 1134 {
michael@0 1135 mozilla::MutexAutoLock lock(mLock);
michael@0 1136
michael@0 1137 if (mIsDoomed || mDoomCallback)
michael@0 1138 return NS_ERROR_IN_PROGRESS; // to aggregate have DOOMING state
michael@0 1139
michael@0 1140 mIsDoomed = true;
michael@0 1141 mDoomCallback = aCallback;
michael@0 1142 }
michael@0 1143
michael@0 1144 // This immediately removes the entry from the master hashtable and also
michael@0 1145 // immediately dooms the file. This way we make sure that any consumer
michael@0 1146 // after this point asking for the same entry won't get
michael@0 1147 // a) this entry
michael@0 1148 // b) a new entry with the same file
michael@0 1149 PurgeAndDoom();
michael@0 1150
michael@0 1151 return NS_OK;
michael@0 1152 }
michael@0 1153
michael@0 1154 NS_IMETHODIMP CacheEntry::GetMetaDataElement(const char * aKey, char * *aRetval)
michael@0 1155 {
michael@0 1156 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 1157
michael@0 1158 return mFile->GetElement(aKey, aRetval);
michael@0 1159 }
michael@0 1160
michael@0 1161 NS_IMETHODIMP CacheEntry::SetMetaDataElement(const char * aKey, const char * aValue)
michael@0 1162 {
michael@0 1163 NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
michael@0 1164
michael@0 1165 return mFile->SetElement(aKey, aValue);
michael@0 1166 }
michael@0 1167
michael@0 1168 NS_IMETHODIMP CacheEntry::MetaDataReady()
michael@0 1169 {
michael@0 1170 mozilla::MutexAutoLock lock(mLock);
michael@0 1171
michael@0 1172 LOG(("CacheEntry::MetaDataReady [this=%p, state=%s]", this, StateString(mState)));
michael@0 1173
michael@0 1174 MOZ_ASSERT(mState > EMPTY);
michael@0 1175
michael@0 1176 if (mState == WRITING)
michael@0 1177 mState = READY;
michael@0 1178
michael@0 1179 InvokeCallbacks();
michael@0 1180
michael@0 1181 return NS_OK;
michael@0 1182 }
michael@0 1183
michael@0 1184 NS_IMETHODIMP CacheEntry::SetValid()
michael@0 1185 {
michael@0 1186 LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
michael@0 1187
michael@0 1188 nsCOMPtr<nsIOutputStream> outputStream;
michael@0 1189
michael@0 1190 {
michael@0 1191 mozilla::MutexAutoLock lock(mLock);
michael@0 1192
michael@0 1193 MOZ_ASSERT(mState > EMPTY);
michael@0 1194
michael@0 1195 mState = READY;
michael@0 1196 mHasData = true;
michael@0 1197
michael@0 1198 InvokeCallbacks();
michael@0 1199
michael@0 1200 outputStream.swap(mOutputStream);
michael@0 1201 }
michael@0 1202
michael@0 1203 if (outputStream) {
michael@0 1204 LOG((" abandoning phantom output stream"));
michael@0 1205 outputStream->Close();
michael@0 1206 }
michael@0 1207
michael@0 1208 return NS_OK;
michael@0 1209 }
michael@0 1210
michael@0 1211 NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly,
michael@0 1212 nsICacheEntry **_retval)
michael@0 1213 {
michael@0 1214 LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
michael@0 1215
michael@0 1216 mozilla::MutexAutoLock lock(mLock);
michael@0 1217
michael@0 1218 nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
michael@0 1219 if (handle) {
michael@0 1220 handle.forget(_retval);
michael@0 1221 return NS_OK;
michael@0 1222 }
michael@0 1223
michael@0 1224 BackgroundOp(Ops::CALLBACKS, true);
michael@0 1225 return NS_OK;
michael@0 1226 }
michael@0 1227
michael@0 1228 NS_IMETHODIMP CacheEntry::GetDataSize(int64_t *aDataSize)
michael@0 1229 {
michael@0 1230 LOG(("CacheEntry::GetDataSize [this=%p]", this));
michael@0 1231 *aDataSize = 0;
michael@0 1232
michael@0 1233 {
michael@0 1234 mozilla::MutexAutoLock lock(mLock);
michael@0 1235
michael@0 1236 if (!mHasData) {
michael@0 1237 LOG((" write in progress (no data)"));
michael@0 1238 return NS_ERROR_IN_PROGRESS;
michael@0 1239 }
michael@0 1240 }
michael@0 1241
michael@0 1242 NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
michael@0 1243
michael@0 1244 // mayhemer: TODO Problem with compression?
michael@0 1245 if (!mFile->DataSize(aDataSize)) {
michael@0 1246 LOG((" write in progress (stream active)"));
michael@0 1247 return NS_ERROR_IN_PROGRESS;
michael@0 1248 }
michael@0 1249
michael@0 1250 LOG((" size=%lld", *aDataSize));
michael@0 1251 return NS_OK;
michael@0 1252 }
michael@0 1253
michael@0 1254 NS_IMETHODIMP CacheEntry::MarkValid()
michael@0 1255 {
michael@0 1256 // NOT IMPLEMENTED ACTUALLY
michael@0 1257 return NS_OK;
michael@0 1258 }
michael@0 1259
michael@0 1260 NS_IMETHODIMP CacheEntry::MaybeMarkValid()
michael@0 1261 {
michael@0 1262 // NOT IMPLEMENTED ACTUALLY
michael@0 1263 return NS_OK;
michael@0 1264 }
michael@0 1265
michael@0 1266 NS_IMETHODIMP CacheEntry::HasWriteAccess(bool aWriteAllowed, bool *aWriteAccess)
michael@0 1267 {
michael@0 1268 *aWriteAccess = aWriteAllowed;
michael@0 1269 return NS_OK;
michael@0 1270 }
michael@0 1271
michael@0 1272 NS_IMETHODIMP CacheEntry::Close()
michael@0 1273 {
michael@0 1274 // NOT IMPLEMENTED ACTUALLY
michael@0 1275 return NS_OK;
michael@0 1276 }
michael@0 1277
michael@0 1278 // nsIRunnable
michael@0 1279
michael@0 1280 NS_IMETHODIMP CacheEntry::Run()
michael@0 1281 {
michael@0 1282 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1283
michael@0 1284 mozilla::MutexAutoLock lock(mLock);
michael@0 1285
michael@0 1286 BackgroundOp(mBackgroundOperations.Grab());
michael@0 1287 return NS_OK;
michael@0 1288 }
michael@0 1289
michael@0 1290 // Management methods
michael@0 1291
michael@0 1292 double CacheEntry::GetFrecency() const
michael@0 1293 {
michael@0 1294 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1295 return mFrecency;
michael@0 1296 }
michael@0 1297
michael@0 1298 uint32_t CacheEntry::GetExpirationTime() const
michael@0 1299 {
michael@0 1300 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1301 return mSortingExpirationTime;
michael@0 1302 }
michael@0 1303
michael@0 1304 bool CacheEntry::IsRegistered() const
michael@0 1305 {
michael@0 1306 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1307 return mRegistration == REGISTERED;
michael@0 1308 }
michael@0 1309
michael@0 1310 bool CacheEntry::CanRegister() const
michael@0 1311 {
michael@0 1312 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1313 return mRegistration == NEVERREGISTERED;
michael@0 1314 }
michael@0 1315
michael@0 1316 void CacheEntry::SetRegistered(bool aRegistered)
michael@0 1317 {
michael@0 1318 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1319
michael@0 1320 if (aRegistered) {
michael@0 1321 MOZ_ASSERT(mRegistration == NEVERREGISTERED);
michael@0 1322 mRegistration = REGISTERED;
michael@0 1323 }
michael@0 1324 else {
michael@0 1325 MOZ_ASSERT(mRegistration == REGISTERED);
michael@0 1326 mRegistration = DEREGISTERED;
michael@0 1327 }
michael@0 1328 }
michael@0 1329
michael@0 1330 bool CacheEntry::Purge(uint32_t aWhat)
michael@0 1331 {
michael@0 1332 LOG(("CacheEntry::Purge [this=%p, what=%d]", this, aWhat));
michael@0 1333
michael@0 1334 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1335
michael@0 1336 switch (aWhat) {
michael@0 1337 case PURGE_DATA_ONLY_DISK_BACKED:
michael@0 1338 case PURGE_WHOLE_ONLY_DISK_BACKED:
michael@0 1339 // This is an in-memory only entry, don't purge it
michael@0 1340 if (!mUseDisk) {
michael@0 1341 LOG((" not using disk"));
michael@0 1342 return false;
michael@0 1343 }
michael@0 1344 }
michael@0 1345
michael@0 1346 if (mState == WRITING || mState == LOADING || mFrecency == 0) {
michael@0 1347 // In-progress (write or load) entries should (at least for consistency and from
michael@0 1348 // the logical point of view) stay in memory.
michael@0 1349 // Zero-frecency entries are those which have never been given to any consumer, those
michael@0 1350 // are actually very fresh and should not go just because frecency had not been set
michael@0 1351 // so far.
michael@0 1352 LOG((" state=%s, frecency=%1.10f", StateString(mState), mFrecency));
michael@0 1353 return false;
michael@0 1354 }
michael@0 1355
michael@0 1356 if (NS_SUCCEEDED(mFileStatus) && mFile->IsWriteInProgress()) {
michael@0 1357 // The file is used when there are open streams or chunks/metadata still waiting for
michael@0 1358 // write. In this case, this entry cannot be purged, otherwise reopenned entry
michael@0 1359 // would may not even find the data on disk - CacheFile is not shared and cannot be
michael@0 1360 // left orphan when its job is not done, hence keep the whole entry.
michael@0 1361 LOG((" file still under use"));
michael@0 1362 return false;
michael@0 1363 }
michael@0 1364
michael@0 1365 switch (aWhat) {
michael@0 1366 case PURGE_WHOLE_ONLY_DISK_BACKED:
michael@0 1367 case PURGE_WHOLE:
michael@0 1368 {
michael@0 1369 if (!CacheStorageService::Self()->RemoveEntry(this, true)) {
michael@0 1370 LOG((" not purging, still referenced"));
michael@0 1371 return false;
michael@0 1372 }
michael@0 1373
michael@0 1374 CacheStorageService::Self()->UnregisterEntry(this);
michael@0 1375
michael@0 1376 // Entry removed it self from control arrays, return true
michael@0 1377 return true;
michael@0 1378 }
michael@0 1379
michael@0 1380 case PURGE_DATA_ONLY_DISK_BACKED:
michael@0 1381 {
michael@0 1382 NS_ENSURE_SUCCESS(mFileStatus, false);
michael@0 1383
michael@0 1384 mFile->ThrowMemoryCachedData();
michael@0 1385
michael@0 1386 // Entry has been left in control arrays, return false (not purged)
michael@0 1387 return false;
michael@0 1388 }
michael@0 1389 }
michael@0 1390
michael@0 1391 LOG((" ?"));
michael@0 1392 return false;
michael@0 1393 }
michael@0 1394
michael@0 1395 void CacheEntry::PurgeAndDoom()
michael@0 1396 {
michael@0 1397 LOG(("CacheEntry::PurgeAndDoom [this=%p]", this));
michael@0 1398
michael@0 1399 CacheStorageService::Self()->RemoveEntry(this);
michael@0 1400 DoomAlreadyRemoved();
michael@0 1401 }
michael@0 1402
michael@0 1403 void CacheEntry::DoomAlreadyRemoved()
michael@0 1404 {
michael@0 1405 LOG(("CacheEntry::DoomAlreadyRemoved [this=%p]", this));
michael@0 1406
michael@0 1407 mozilla::MutexAutoLock lock(mLock);
michael@0 1408
michael@0 1409 mIsDoomed = true;
michael@0 1410
michael@0 1411 // This schedules dooming of the file, dooming is ensured to happen
michael@0 1412 // sooner than demand to open the same file made after this point
michael@0 1413 // so that we don't get this file for any newer opened entry(s).
michael@0 1414 DoomFile();
michael@0 1415
michael@0 1416 // Must force post here since may be indirectly called from
michael@0 1417 // InvokeCallbacks of this entry and we don't want reentrancy here.
michael@0 1418 BackgroundOp(Ops::CALLBACKS, true);
michael@0 1419 // Process immediately when on the management thread.
michael@0 1420 BackgroundOp(Ops::UNREGISTER);
michael@0 1421 }
michael@0 1422
michael@0 1423 void CacheEntry::DoomFile()
michael@0 1424 {
michael@0 1425 nsresult rv = NS_ERROR_NOT_AVAILABLE;
michael@0 1426
michael@0 1427 if (NS_SUCCEEDED(mFileStatus)) {
michael@0 1428 // Always calls the callback asynchronously.
michael@0 1429 rv = mFile->Doom(mDoomCallback ? this : nullptr);
michael@0 1430 if (NS_SUCCEEDED(rv)) {
michael@0 1431 LOG((" file doomed"));
michael@0 1432 return;
michael@0 1433 }
michael@0 1434
michael@0 1435 if (NS_ERROR_FILE_NOT_FOUND == rv) {
michael@0 1436 // File is set to be just memory-only, notify the callbacks
michael@0 1437 // and pretend dooming has succeeded. From point of view of
michael@0 1438 // the entry it actually did - the data is gone and cannot be
michael@0 1439 // reused.
michael@0 1440 rv = NS_OK;
michael@0 1441 }
michael@0 1442 }
michael@0 1443
michael@0 1444 // Always posts to the main thread.
michael@0 1445 OnFileDoomed(rv);
michael@0 1446 }
michael@0 1447
michael@0 1448 void CacheEntry::BackgroundOp(uint32_t aOperations, bool aForceAsync)
michael@0 1449 {
michael@0 1450 mLock.AssertCurrentThreadOwns();
michael@0 1451
michael@0 1452 if (!CacheStorageService::IsOnManagementThread() || aForceAsync) {
michael@0 1453 if (mBackgroundOperations.Set(aOperations))
michael@0 1454 CacheStorageService::Self()->Dispatch(this);
michael@0 1455
michael@0 1456 LOG(("CacheEntry::BackgroundOp this=%p dipatch of %x", this, aOperations));
michael@0 1457 return;
michael@0 1458 }
michael@0 1459
michael@0 1460 mozilla::MutexAutoUnlock unlock(mLock);
michael@0 1461
michael@0 1462 MOZ_ASSERT(CacheStorageService::IsOnManagementThread());
michael@0 1463
michael@0 1464 if (aOperations & Ops::FRECENCYUPDATE) {
michael@0 1465 #ifndef M_LN2
michael@0 1466 #define M_LN2 0.69314718055994530942
michael@0 1467 #endif
michael@0 1468
michael@0 1469 // Half-life is dynamic, in seconds.
michael@0 1470 static double half_life = CacheObserver::HalfLifeSeconds();
michael@0 1471 // Must convert from seconds to milliseconds since PR_Now() gives usecs.
michael@0 1472 static double const decay = (M_LN2 / half_life) / static_cast<double>(PR_USEC_PER_SEC);
michael@0 1473
michael@0 1474 double now_decay = static_cast<double>(PR_Now()) * decay;
michael@0 1475
michael@0 1476 if (mFrecency == 0) {
michael@0 1477 mFrecency = now_decay;
michael@0 1478 }
michael@0 1479 else {
michael@0 1480 // TODO: when C++11 enabled, use std::log1p(n) which is equal to log(n + 1) but
michael@0 1481 // more precise.
michael@0 1482 mFrecency = log(exp(mFrecency - now_decay) + 1) + now_decay;
michael@0 1483 }
michael@0 1484 LOG(("CacheEntry FRECENCYUPDATE [this=%p, frecency=%1.10f]", this, mFrecency));
michael@0 1485
michael@0 1486 // Because CacheFile::Set*() are not thread-safe to use (uses WeakReference that
michael@0 1487 // is not thread-safe) we must post to the main thread...
michael@0 1488 nsRefPtr<nsRunnableMethod<CacheEntry> > event =
michael@0 1489 NS_NewRunnableMethod(this, &CacheEntry::StoreFrecency);
michael@0 1490 NS_DispatchToMainThread(event);
michael@0 1491 }
michael@0 1492
michael@0 1493 if (aOperations & Ops::REGISTER) {
michael@0 1494 LOG(("CacheEntry REGISTER [this=%p]", this));
michael@0 1495
michael@0 1496 CacheStorageService::Self()->RegisterEntry(this);
michael@0 1497 }
michael@0 1498
michael@0 1499 if (aOperations & Ops::UNREGISTER) {
michael@0 1500 LOG(("CacheEntry UNREGISTER [this=%p]", this));
michael@0 1501
michael@0 1502 CacheStorageService::Self()->UnregisterEntry(this);
michael@0 1503 }
michael@0 1504
michael@0 1505 if (aOperations & Ops::CALLBACKS) {
michael@0 1506 LOG(("CacheEntry CALLBACKS (invoke) [this=%p]", this));
michael@0 1507
michael@0 1508 mozilla::MutexAutoLock lock(mLock);
michael@0 1509 InvokeCallbacks();
michael@0 1510 }
michael@0 1511 }
michael@0 1512
michael@0 1513 void CacheEntry::StoreFrecency()
michael@0 1514 {
michael@0 1515 // No need for thread safety over mFrecency, it will be rewriten
michael@0 1516 // correctly on following invocation if broken by concurrency.
michael@0 1517 MOZ_ASSERT(NS_IsMainThread());
michael@0 1518 mFile->SetFrecency(FRECENCY2INT(mFrecency));
michael@0 1519 }
michael@0 1520
michael@0 1521 // CacheOutputCloseListener
michael@0 1522
michael@0 1523 CacheOutputCloseListener::CacheOutputCloseListener(CacheEntry* aEntry)
michael@0 1524 : mEntry(aEntry)
michael@0 1525 {
michael@0 1526 MOZ_COUNT_CTOR(CacheOutputCloseListener);
michael@0 1527 }
michael@0 1528
michael@0 1529 CacheOutputCloseListener::~CacheOutputCloseListener()
michael@0 1530 {
michael@0 1531 MOZ_COUNT_DTOR(CacheOutputCloseListener);
michael@0 1532 }
michael@0 1533
michael@0 1534 void CacheOutputCloseListener::OnOutputClosed()
michael@0 1535 {
michael@0 1536 // We need this class and to redispatch since this callback is invoked
michael@0 1537 // under the file's lock and to do the job we need to enter the entry's
michael@0 1538 // lock too. That would lead to potential deadlocks.
michael@0 1539 NS_DispatchToCurrentThread(this);
michael@0 1540 }
michael@0 1541
michael@0 1542 NS_IMETHODIMP CacheOutputCloseListener::Run()
michael@0 1543 {
michael@0 1544 mEntry->OnOutputClosed();
michael@0 1545 return NS_OK;
michael@0 1546 }
michael@0 1547
michael@0 1548 // Memory reporting
michael@0 1549
michael@0 1550 size_t CacheEntry::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
michael@0 1551 {
michael@0 1552 size_t n = 0;
michael@0 1553 nsCOMPtr<nsISizeOf> sizeOf;
michael@0 1554
michael@0 1555 n += mCallbacks.SizeOfExcludingThis(mallocSizeOf);
michael@0 1556 if (mFile) {
michael@0 1557 n += mFile->SizeOfIncludingThis(mallocSizeOf);
michael@0 1558 }
michael@0 1559
michael@0 1560 sizeOf = do_QueryInterface(mURI);
michael@0 1561 if (sizeOf) {
michael@0 1562 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
michael@0 1563 }
michael@0 1564
michael@0 1565 n += mEnhanceID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
michael@0 1566 n += mStorageID.SizeOfExcludingThisIfUnshared(mallocSizeOf);
michael@0 1567
michael@0 1568 // mDoomCallback is an arbitrary class that is probably reported elsewhere.
michael@0 1569 // mOutputStream is reported in mFile.
michael@0 1570 // mWriter is one of many handles we create, but (intentionally) not keep
michael@0 1571 // any reference to, so those unfortunatelly cannot be reported. Handles are
michael@0 1572 // small, though.
michael@0 1573 // mSecurityInfo doesn't impl nsISizeOf.
michael@0 1574
michael@0 1575 return n;
michael@0 1576 }
michael@0 1577
michael@0 1578 size_t CacheEntry::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
michael@0 1579 {
michael@0 1580 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
michael@0 1581 }
michael@0 1582
michael@0 1583 } // net
michael@0 1584 } // mozilla

mercurial