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