Wed, 31 Dec 2014 06:55:50 +0100
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 |