Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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/. */
5 #include "CacheLog.h"
6 #include "CacheFileIOManager.h"
8 #include "../cache/nsCacheUtils.h"
9 #include "CacheHashUtils.h"
10 #include "CacheStorageService.h"
11 #include "CacheIndex.h"
12 #include "CacheFileUtils.h"
13 #include "nsThreadUtils.h"
14 #include "CacheFile.h"
15 #include "CacheObserver.h"
16 #include "nsIFile.h"
17 #include "CacheFileContextEvictor.h"
18 #include "nsITimer.h"
19 #include "nsISimpleEnumerator.h"
20 #include "nsIDirectoryEnumerator.h"
21 #include "nsIObserverService.h"
22 #include "nsISizeOf.h"
23 #include "mozilla/Telemetry.h"
24 #include "mozilla/DebugOnly.h"
25 #include "mozilla/Services.h"
26 #include "nsDirectoryServiceUtils.h"
27 #include "nsAppDirectoryServiceDefs.h"
28 #include "private/pprio.h"
29 #include "mozilla/VisualEventTracer.h"
30 #include "mozilla/Preferences.h"
32 // include files for ftruncate (or equivalent)
33 #if defined(XP_UNIX)
34 #include <unistd.h>
35 #elif defined(XP_WIN)
36 #include <windows.h>
37 #undef CreateFile
38 #undef CREATE_NEW
39 #else
40 // XXX add necessary include file for ftruncate (or equivalent)
41 #endif
44 namespace mozilla {
45 namespace net {
47 #define kOpenHandlesLimit 64
48 #define kMetadataWriteDelay 5000
49 #define kRemoveTrashStartDelay 60000 // in milliseconds
50 #define kSmartSizeUpdateInterval 60000 // in milliseconds
52 #ifdef ANDROID
53 const uint32_t kMaxCacheSizeKB = 200*1024; // 200 MB
54 #else
55 const uint32_t kMaxCacheSizeKB = 350*1024; // 350 MB
56 #endif
58 bool
59 CacheFileHandle::DispatchRelease()
60 {
61 if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
62 return false;
63 }
65 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
66 if (!ioTarget) {
67 return false;
68 }
70 nsRefPtr<nsRunnableMethod<CacheFileHandle, MozExternalRefCountType, false> > event =
71 NS_NewNonOwningRunnableMethod(this, &CacheFileHandle::Release);
72 nsresult rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
73 if (NS_FAILED(rv)) {
74 return false;
75 }
77 return true;
78 }
80 NS_IMPL_ADDREF(CacheFileHandle)
81 NS_IMETHODIMP_(MozExternalRefCountType)
82 CacheFileHandle::Release()
83 {
84 nsrefcnt count = mRefCnt - 1;
85 if (DispatchRelease()) {
86 // Redispatched to the IO thread.
87 return count;
88 }
90 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
92 LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get()));
93 NS_PRECONDITION(0 != mRefCnt, "dup release");
94 count = --mRefCnt;
95 NS_LOG_RELEASE(this, count, "CacheFileHandle");
97 if (0 == count) {
98 mRefCnt = 1;
99 delete (this);
100 return 0;
101 }
103 return count;
104 }
106 NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
107 NS_INTERFACE_MAP_ENTRY(nsISupports)
108 NS_INTERFACE_MAP_END_THREADSAFE
110 CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority)
111 : mHash(aHash)
112 , mIsDoomed(false)
113 , mPriority(aPriority)
114 , mClosed(false)
115 , mInvalid(false)
116 , mFileExists(false)
117 , mFileSize(-1)
118 , mFD(nullptr)
119 {
120 LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]"
121 , this, LOGSHA1(aHash)));
122 }
124 CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority)
125 : mHash(nullptr)
126 , mIsDoomed(false)
127 , mPriority(aPriority)
128 , mClosed(false)
129 , mInvalid(false)
130 , mFileExists(false)
131 , mFileSize(-1)
132 , mFD(nullptr)
133 , mKey(aKey)
134 {
135 LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
136 PromiseFlatCString(aKey).get()));
137 }
139 CacheFileHandle::~CacheFileHandle()
140 {
141 LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
143 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
145 nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
146 if (ioMan) {
147 ioMan->CloseHandleInternal(this);
148 }
149 }
151 void
152 CacheFileHandle::Log()
153 {
154 nsAutoCString leafName;
155 if (mFile) {
156 mFile->GetNativeLeafName(leafName);
157 }
159 if (!mHash) {
160 // special file
161 LOG(("CacheFileHandle::Log() [this=%p, hash=nullptr, isDoomed=%d, "
162 "priority=%d, closed=%d, invalid=%d, "
163 "fileExists=%d, fileSize=%lld, leafName=%s, key=%s]",
164 this, mIsDoomed, mPriority, mClosed, mInvalid,
165 mFileExists, mFileSize, leafName.get(), mKey.get()));
166 } else {
167 LOG(("CacheFileHandle::Log() [this=%p, hash=%08x%08x%08x%08x%08x, "
168 "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
169 "fileExists=%d, fileSize=%lld, leafName=%s, key=%s]",
170 this, LOGSHA1(mHash), mIsDoomed, mPriority, mClosed,
171 mInvalid, mFileExists, mFileSize, leafName.get(), mKey.get()));
172 }
173 }
175 uint32_t
176 CacheFileHandle::FileSizeInK() const
177 {
178 MOZ_ASSERT(mFileSize != -1);
179 uint64_t size64 = mFileSize;
181 size64 += 0x3FF;
182 size64 >>= 10;
184 uint32_t size;
185 if (size64 >> 32) {
186 NS_WARNING("CacheFileHandle::FileSizeInK() - FileSize is too large, "
187 "truncating to PR_UINT32_MAX");
188 size = PR_UINT32_MAX;
189 } else {
190 size = static_cast<uint32_t>(size64);
191 }
193 return size;
194 }
196 // Memory reporting
198 size_t
199 CacheFileHandle::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
200 {
201 size_t n = 0;
202 nsCOMPtr<nsISizeOf> sizeOf;
204 sizeOf = do_QueryInterface(mFile);
205 if (sizeOf) {
206 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
207 }
209 n += mallocSizeOf(mFD);
210 n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
211 return n;
212 }
214 size_t
215 CacheFileHandle::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
216 {
217 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
218 }
220 /******************************************************************************
221 * CacheFileHandles::HandleHashKey
222 *****************************************************************************/
224 void
225 CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle)
226 {
227 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
229 mHandles.InsertElementAt(0, aHandle);
230 }
232 void
233 CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle)
234 {
235 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
237 DebugOnly<bool> found;
238 found = mHandles.RemoveElement(aHandle);
239 MOZ_ASSERT(found);
240 }
242 already_AddRefed<CacheFileHandle>
243 CacheFileHandles::HandleHashKey::GetNewestHandle()
244 {
245 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
247 nsRefPtr<CacheFileHandle> handle;
248 if (mHandles.Length()) {
249 handle = mHandles[0];
250 }
252 return handle.forget();
253 }
255 void
256 CacheFileHandles::HandleHashKey::GetHandles(nsTArray<nsRefPtr<CacheFileHandle> > &aResult)
257 {
258 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
260 for (uint32_t i = 0; i < mHandles.Length(); ++i) {
261 CacheFileHandle* handle = mHandles[i];
262 aResult.AppendElement(handle);
263 }
264 }
266 #ifdef DEBUG
268 void
269 CacheFileHandles::HandleHashKey::AssertHandlesState()
270 {
271 for (uint32_t i = 0; i < mHandles.Length(); ++i) {
272 CacheFileHandle* handle = mHandles[i];
273 MOZ_ASSERT(handle->IsDoomed());
274 }
275 }
277 #endif
279 size_t
280 CacheFileHandles::HandleHashKey::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
281 {
282 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
284 size_t n = 0;
285 n += mallocSizeOf(mHash);
286 for (uint32_t i = 0; i < mHandles.Length(); ++i) {
287 n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf);
288 }
290 return n;
291 }
293 /******************************************************************************
294 * CacheFileHandles
295 *****************************************************************************/
297 CacheFileHandles::CacheFileHandles()
298 {
299 LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
300 MOZ_COUNT_CTOR(CacheFileHandles);
301 }
303 CacheFileHandles::~CacheFileHandles()
304 {
305 LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
306 MOZ_COUNT_DTOR(CacheFileHandles);
307 }
309 nsresult
310 CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash,
311 bool aReturnDoomed,
312 CacheFileHandle **_retval)
313 {
314 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
315 MOZ_ASSERT(aHash);
317 #ifdef DEBUG_HANDLES
318 LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
319 LOGSHA1(aHash)));
320 #endif
322 // find hash entry for key
323 HandleHashKey *entry = mTable.GetEntry(*aHash);
324 if (!entry) {
325 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
326 "no handle entries found", LOGSHA1(aHash)));
327 return NS_ERROR_NOT_AVAILABLE;
328 }
330 #ifdef DEBUG_HANDLES
331 Log(entry);
332 #endif
334 // Check if the entry is doomed
335 nsRefPtr<CacheFileHandle> handle = entry->GetNewestHandle();
336 if (!handle) {
337 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
338 "no handle found %p, entry %p", LOGSHA1(aHash), handle.get(), entry));
339 return NS_ERROR_NOT_AVAILABLE;
340 }
342 if (handle->IsDoomed()) {
343 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
344 "found doomed handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry));
346 // If the consumer doesn't want doomed handles, exit with NOT_AVAIL.
347 if (!aReturnDoomed) {
348 return NS_ERROR_NOT_AVAILABLE;
349 }
350 } else {
351 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
352 "found handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry));
353 }
355 handle.forget(_retval);
356 return NS_OK;
357 }
360 nsresult
361 CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
362 bool aPriority,
363 CacheFileHandle **_retval)
364 {
365 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
366 MOZ_ASSERT(aHash);
368 #ifdef DEBUG_HANDLES
369 LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
370 #endif
372 // find hash entry for key
373 HandleHashKey *entry = mTable.PutEntry(*aHash);
375 #ifdef DEBUG_HANDLES
376 Log(entry);
377 #endif
379 #ifdef DEBUG
380 entry->AssertHandlesState();
381 #endif
383 nsRefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority);
384 entry->AddHandle(handle);
386 LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
387 "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry));
389 handle.forget(_retval);
390 return NS_OK;
391 }
393 void
394 CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle)
395 {
396 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
397 MOZ_ASSERT(aHandle);
399 if (!aHandle) {
400 return;
401 }
403 #ifdef DEBUG_HANDLES
404 LOG(("CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]"
405 , aHandle, LOGSHA1(aHandle->Hash())));
406 #endif
408 // find hash entry for key
409 HandleHashKey *entry = mTable.GetEntry(*aHandle->Hash());
410 if (!entry) {
411 MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
412 "Should find entry when removing a handle before shutdown");
414 LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
415 "no entries found", LOGSHA1(aHandle->Hash())));
416 return;
417 }
419 #ifdef DEBUG_HANDLES
420 Log(entry);
421 #endif
423 LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
424 "removing handle %p", LOGSHA1(entry->Hash()), aHandle));
425 entry->RemoveHandle(aHandle);
427 if (entry->IsEmpty()) {
428 LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
429 "list is empty, removing entry %p", LOGSHA1(entry->Hash()), entry));
430 mTable.RemoveEntry(*entry->Hash());
431 }
432 }
434 static PLDHashOperator
435 GetAllHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure)
436 {
437 nsTArray<nsRefPtr<CacheFileHandle> > *array =
438 static_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(aClosure);
440 aEntry->GetHandles(*array);
441 return PL_DHASH_NEXT;
442 }
444 void
445 CacheFileHandles::GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
446 {
447 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
448 mTable.EnumerateEntries(&GetAllHandlesEnum, _retval);
449 }
451 static PLDHashOperator
452 GetActiveHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure)
453 {
454 nsTArray<nsRefPtr<CacheFileHandle> > *array =
455 static_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(aClosure);
457 nsRefPtr<CacheFileHandle> handle = aEntry->GetNewestHandle();
458 MOZ_ASSERT(handle);
460 if (!handle->IsDoomed()) {
461 array->AppendElement(handle);
462 }
464 return PL_DHASH_NEXT;
465 }
467 void
468 CacheFileHandles::GetActiveHandles(
469 nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
470 {
471 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
472 mTable.EnumerateEntries(&GetActiveHandlesEnum, _retval);
473 }
475 void
476 CacheFileHandles::ClearAll()
477 {
478 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
479 mTable.Clear();
480 }
482 uint32_t
483 CacheFileHandles::HandleCount()
484 {
485 return mTable.Count();
486 }
488 #ifdef DEBUG_HANDLES
489 void
490 CacheFileHandles::Log(CacheFileHandlesEntry *entry)
491 {
492 LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry));
494 nsTArray<nsRefPtr<CacheFileHandle> > array;
495 aEntry->GetHandles(array);
497 for (uint32_t i = 0; i < array.Length(); ++i) {
498 CacheFileHandle *handle = array[i];
499 handle->Log();
500 }
502 LOG(("CacheFileHandles::Log() END [entry=%p]", entry));
503 }
504 #endif
506 // Memory reporting
508 namespace { // anon
510 size_t
511 CollectHandlesMemory(CacheFileHandles::HandleHashKey* key,
512 mozilla::MallocSizeOf mallocSizeOf,
513 void *arg)
514 {
515 return key->SizeOfExcludingThis(mallocSizeOf);
516 }
518 } // anon
520 size_t
521 CacheFileHandles::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
522 {
523 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
525 return mTable.SizeOfExcludingThis(&CollectHandlesMemory, mallocSizeOf);
526 }
528 // Events
530 class ShutdownEvent : public nsRunnable {
531 public:
532 ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar)
533 : mLock(aLock)
534 , mCondVar(aCondVar)
535 {
536 MOZ_COUNT_CTOR(ShutdownEvent);
537 }
539 ~ShutdownEvent()
540 {
541 MOZ_COUNT_DTOR(ShutdownEvent);
542 }
544 NS_IMETHOD Run()
545 {
546 MutexAutoLock lock(*mLock);
548 CacheFileIOManager::gInstance->ShutdownInternal();
550 mCondVar->Notify();
551 return NS_OK;
552 }
554 protected:
555 mozilla::Mutex *mLock;
556 mozilla::CondVar *mCondVar;
557 };
559 class OpenFileEvent : public nsRunnable {
560 public:
561 OpenFileEvent(const nsACString &aKey,
562 uint32_t aFlags, bool aResultOnAnyThread,
563 CacheFileIOListener *aCallback)
564 : mFlags(aFlags)
565 , mResultOnAnyThread(aResultOnAnyThread)
566 , mCallback(aCallback)
567 , mRV(NS_ERROR_FAILURE)
568 , mKey(aKey)
569 {
570 MOZ_COUNT_CTOR(OpenFileEvent);
572 if (!aResultOnAnyThread) {
573 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
574 MOZ_ASSERT(mTarget);
575 }
577 mIOMan = CacheFileIOManager::gInstance;
579 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aKey.BeginReading());
580 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-background");
581 }
583 ~OpenFileEvent()
584 {
585 MOZ_COUNT_DTOR(OpenFileEvent);
586 }
588 NS_IMETHOD Run()
589 {
590 if (mResultOnAnyThread || mTarget) {
591 mRV = NS_OK;
593 if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
594 SHA1Sum sum;
595 sum.update(mKey.BeginReading(), mKey.Length());
596 sum.finish(mHash);
597 }
599 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this),
600 "net::cache::open-background");
601 if (NS_SUCCEEDED(mRV)) {
602 if (!mIOMan) {
603 mRV = NS_ERROR_NOT_INITIALIZED;
604 } else {
605 if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
606 mRV = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
607 getter_AddRefs(mHandle));
608 } else {
609 mRV = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
610 getter_AddRefs(mHandle));
611 }
612 mIOMan = nullptr;
613 if (mHandle) {
614 MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get());
615 if (mHandle->Key().IsEmpty()) {
616 mHandle->Key() = mKey;
617 }
618 }
619 }
620 }
621 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-background");
623 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-result");
625 if (mTarget) {
626 nsCOMPtr<nsIEventTarget> target;
627 mTarget.swap(target);
628 return target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
629 }
630 }
632 if (!mTarget) {
633 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-result");
634 mCallback->OnFileOpened(mHandle, mRV);
635 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-result");
636 }
638 return NS_OK;
639 }
641 protected:
642 SHA1Sum::Hash mHash;
643 uint32_t mFlags;
644 bool mResultOnAnyThread;
645 nsCOMPtr<CacheFileIOListener> mCallback;
646 nsCOMPtr<nsIEventTarget> mTarget;
647 nsRefPtr<CacheFileIOManager> mIOMan;
648 nsRefPtr<CacheFileHandle> mHandle;
649 nsresult mRV;
650 nsCString mKey;
651 };
653 class ReadEvent : public nsRunnable {
654 public:
655 ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
656 int32_t aCount, bool aResultOnAnyThread, CacheFileIOListener *aCallback)
657 : mHandle(aHandle)
658 , mOffset(aOffset)
659 , mBuf(aBuf)
660 , mCount(aCount)
661 , mResultOnAnyThread(aResultOnAnyThread)
662 , mCallback(aCallback)
663 , mRV(NS_ERROR_FAILURE)
664 {
665 MOZ_COUNT_CTOR(ReadEvent);
667 if (!aResultOnAnyThread) {
668 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
669 }
671 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
672 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-background");
673 }
675 ~ReadEvent()
676 {
677 MOZ_COUNT_DTOR(ReadEvent);
678 }
680 NS_IMETHOD Run()
681 {
682 if (mResultOnAnyThread || mTarget) {
683 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-background");
684 if (mHandle->IsClosed()) {
685 mRV = NS_ERROR_NOT_INITIALIZED;
686 } else {
687 mRV = CacheFileIOManager::gInstance->ReadInternal(
688 mHandle, mOffset, mBuf, mCount);
689 }
690 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-background");
692 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-result");
694 if (mTarget) {
695 nsCOMPtr<nsIEventTarget> target;
696 mTarget.swap(target);
697 return target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
698 }
699 }
701 if (!mTarget && mCallback) {
702 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-result");
703 mCallback->OnDataRead(mHandle, mBuf, mRV);
704 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-result");
705 }
707 return NS_OK;
708 }
710 protected:
711 nsRefPtr<CacheFileHandle> mHandle;
712 int64_t mOffset;
713 char *mBuf;
714 int32_t mCount;
715 bool mResultOnAnyThread;
716 nsCOMPtr<CacheFileIOListener> mCallback;
717 nsCOMPtr<nsIEventTarget> mTarget;
718 nsresult mRV;
719 };
721 class WriteEvent : public nsRunnable {
722 public:
723 WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf,
724 int32_t aCount, bool aValidate, CacheFileIOListener *aCallback)
725 : mHandle(aHandle)
726 , mOffset(aOffset)
727 , mBuf(aBuf)
728 , mCount(aCount)
729 , mValidate(aValidate)
730 , mCallback(aCallback)
731 , mRV(NS_ERROR_FAILURE)
732 {
733 MOZ_COUNT_CTOR(WriteEvent);
734 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
736 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
737 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-background");
738 }
740 ~WriteEvent()
741 {
742 MOZ_COUNT_DTOR(WriteEvent);
744 if (!mCallback && mBuf) {
745 free(const_cast<char *>(mBuf));
746 }
747 }
749 NS_IMETHOD Run()
750 {
751 if (mTarget) {
752 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-background");
753 if (mHandle->IsClosed()) {
754 mRV = NS_ERROR_NOT_INITIALIZED;
755 } else {
756 mRV = CacheFileIOManager::gInstance->WriteInternal(
757 mHandle, mOffset, mBuf, mCount, mValidate);
758 }
759 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-background");
761 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-result");
762 nsCOMPtr<nsIEventTarget> target;
763 mTarget.swap(target);
764 target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
765 } else {
766 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-result");
767 if (mCallback) {
768 mCallback->OnDataWritten(mHandle, mBuf, mRV);
769 } else {
770 free(const_cast<char *>(mBuf));
771 mBuf = nullptr;
772 }
773 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-result");
774 }
775 return NS_OK;
776 }
778 protected:
779 nsRefPtr<CacheFileHandle> mHandle;
780 int64_t mOffset;
781 const char *mBuf;
782 int32_t mCount;
783 bool mValidate;
784 nsCOMPtr<CacheFileIOListener> mCallback;
785 nsCOMPtr<nsIEventTarget> mTarget;
786 nsresult mRV;
787 };
789 class DoomFileEvent : public nsRunnable {
790 public:
791 DoomFileEvent(CacheFileHandle *aHandle,
792 CacheFileIOListener *aCallback)
793 : mCallback(aCallback)
794 , mHandle(aHandle)
795 , mRV(NS_ERROR_FAILURE)
796 {
797 MOZ_COUNT_CTOR(DoomFileEvent);
798 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
800 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
801 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
802 }
804 ~DoomFileEvent()
805 {
806 MOZ_COUNT_DTOR(DoomFileEvent);
807 }
809 NS_IMETHOD Run()
810 {
811 if (mTarget) {
812 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
813 if (mHandle->IsClosed()) {
814 mRV = NS_ERROR_NOT_INITIALIZED;
815 } else {
816 mRV = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
817 }
818 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
820 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
821 nsCOMPtr<nsIEventTarget> target;
822 mTarget.swap(target);
823 target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
824 } else {
825 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
826 if (mCallback) {
827 mCallback->OnFileDoomed(mHandle, mRV);
828 }
829 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
830 }
831 return NS_OK;
832 }
834 protected:
835 nsCOMPtr<CacheFileIOListener> mCallback;
836 nsCOMPtr<nsIEventTarget> mTarget;
837 nsRefPtr<CacheFileHandle> mHandle;
838 nsresult mRV;
839 };
841 class DoomFileByKeyEvent : public nsRunnable {
842 public:
843 DoomFileByKeyEvent(const nsACString &aKey,
844 CacheFileIOListener *aCallback)
845 : mCallback(aCallback)
846 , mRV(NS_ERROR_FAILURE)
847 {
848 MOZ_COUNT_CTOR(DoomFileByKeyEvent);
850 SHA1Sum sum;
851 sum.update(aKey.BeginReading(), aKey.Length());
852 sum.finish(mHash);
854 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
855 mIOMan = CacheFileIOManager::gInstance;
856 MOZ_ASSERT(mTarget);
857 }
859 ~DoomFileByKeyEvent()
860 {
861 MOZ_COUNT_DTOR(DoomFileByKeyEvent);
862 }
864 NS_IMETHOD Run()
865 {
866 if (mTarget) {
867 if (!mIOMan) {
868 mRV = NS_ERROR_NOT_INITIALIZED;
869 } else {
870 mRV = mIOMan->DoomFileByKeyInternal(&mHash);
871 mIOMan = nullptr;
872 }
874 nsCOMPtr<nsIEventTarget> target;
875 mTarget.swap(target);
876 target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
877 } else {
878 if (mCallback) {
879 mCallback->OnFileDoomed(nullptr, mRV);
880 }
881 }
882 return NS_OK;
883 }
885 protected:
886 SHA1Sum::Hash mHash;
887 nsCOMPtr<CacheFileIOListener> mCallback;
888 nsCOMPtr<nsIEventTarget> mTarget;
889 nsRefPtr<CacheFileIOManager> mIOMan;
890 nsresult mRV;
891 };
893 class ReleaseNSPRHandleEvent : public nsRunnable {
894 public:
895 ReleaseNSPRHandleEvent(CacheFileHandle *aHandle)
896 : mHandle(aHandle)
897 {
898 MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
899 }
901 ~ReleaseNSPRHandleEvent()
902 {
903 MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent);
904 }
906 NS_IMETHOD Run()
907 {
908 if (mHandle->mFD && !mHandle->IsClosed()) {
909 CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
910 }
912 return NS_OK;
913 }
915 protected:
916 nsRefPtr<CacheFileHandle> mHandle;
917 };
919 class TruncateSeekSetEOFEvent : public nsRunnable {
920 public:
921 TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos,
922 int64_t aEOFPos, CacheFileIOListener *aCallback)
923 : mHandle(aHandle)
924 , mTruncatePos(aTruncatePos)
925 , mEOFPos(aEOFPos)
926 , mCallback(aCallback)
927 , mRV(NS_ERROR_FAILURE)
928 {
929 MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent);
930 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
931 }
933 ~TruncateSeekSetEOFEvent()
934 {
935 MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent);
936 }
938 NS_IMETHOD Run()
939 {
940 if (mTarget) {
941 if (mHandle->IsClosed()) {
942 mRV = NS_ERROR_NOT_INITIALIZED;
943 } else {
944 mRV = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
945 mHandle, mTruncatePos, mEOFPos);
946 }
948 nsCOMPtr<nsIEventTarget> target;
949 mTarget.swap(target);
950 target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
951 } else {
952 if (mCallback) {
953 mCallback->OnEOFSet(mHandle, mRV);
954 }
955 }
956 return NS_OK;
957 }
959 protected:
960 nsRefPtr<CacheFileHandle> mHandle;
961 int64_t mTruncatePos;
962 int64_t mEOFPos;
963 nsCOMPtr<CacheFileIOListener> mCallback;
964 nsCOMPtr<nsIEventTarget> mTarget;
965 nsresult mRV;
966 };
968 class RenameFileEvent : public nsRunnable {
969 public:
970 RenameFileEvent(CacheFileHandle *aHandle, const nsACString &aNewName,
971 CacheFileIOListener *aCallback)
972 : mHandle(aHandle)
973 , mNewName(aNewName)
974 , mCallback(aCallback)
975 , mRV(NS_ERROR_FAILURE)
976 {
977 MOZ_COUNT_CTOR(RenameFileEvent);
978 mTarget = static_cast<nsIEventTarget*>(NS_GetCurrentThread());
979 }
981 ~RenameFileEvent()
982 {
983 MOZ_COUNT_DTOR(RenameFileEvent);
984 }
986 NS_IMETHOD Run()
987 {
988 if (mTarget) {
989 if (mHandle->IsClosed()) {
990 mRV = NS_ERROR_NOT_INITIALIZED;
991 } else {
992 mRV = CacheFileIOManager::gInstance->RenameFileInternal(mHandle,
993 mNewName);
994 }
996 nsCOMPtr<nsIEventTarget> target;
997 mTarget.swap(target);
998 target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
999 } else {
1000 if (mCallback) {
1001 mCallback->OnFileRenamed(mHandle, mRV);
1002 }
1003 }
1004 return NS_OK;
1005 }
1007 protected:
1008 nsRefPtr<CacheFileHandle> mHandle;
1009 nsCString mNewName;
1010 nsCOMPtr<CacheFileIOListener> mCallback;
1011 nsCOMPtr<nsIEventTarget> mTarget;
1012 nsresult mRV;
1013 };
1015 class InitIndexEntryEvent : public nsRunnable {
1016 public:
1017 InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId,
1018 bool aAnonymous, bool aInBrowser)
1019 : mHandle(aHandle)
1020 , mAppId(aAppId)
1021 , mAnonymous(aAnonymous)
1022 , mInBrowser(aInBrowser)
1023 {
1024 MOZ_COUNT_CTOR(InitIndexEntryEvent);
1025 }
1027 ~InitIndexEntryEvent()
1028 {
1029 MOZ_COUNT_DTOR(InitIndexEntryEvent);
1030 }
1032 NS_IMETHOD Run()
1033 {
1034 if (mHandle->IsClosed() || mHandle->IsDoomed()) {
1035 return NS_OK;
1036 }
1038 CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser);
1040 // We cannot set the filesize before we init the entry. If we're opening
1041 // an existing entry file, frecency and expiration time will be set after
1042 // parsing the entry file, but we must set the filesize here since nobody is
1043 // going to set it if there is no write to the file.
1044 uint32_t sizeInK = mHandle->FileSizeInK();
1045 CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, &sizeInK);
1047 return NS_OK;
1048 }
1050 protected:
1051 nsRefPtr<CacheFileHandle> mHandle;
1052 uint32_t mAppId;
1053 bool mAnonymous;
1054 bool mInBrowser;
1055 };
1057 class UpdateIndexEntryEvent : public nsRunnable {
1058 public:
1059 UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency,
1060 const uint32_t *aExpirationTime)
1061 : mHandle(aHandle)
1062 , mHasFrecency(false)
1063 , mHasExpirationTime(false)
1064 {
1065 MOZ_COUNT_CTOR(UpdateIndexEntryEvent);
1066 if (aFrecency) {
1067 mHasFrecency = true;
1068 mFrecency = *aFrecency;
1069 }
1070 if (aExpirationTime) {
1071 mHasExpirationTime = true;
1072 mExpirationTime = *aExpirationTime;
1073 }
1074 }
1076 ~UpdateIndexEntryEvent()
1077 {
1078 MOZ_COUNT_DTOR(UpdateIndexEntryEvent);
1079 }
1081 NS_IMETHOD Run()
1082 {
1083 if (mHandle->IsClosed() || mHandle->IsDoomed()) {
1084 return NS_OK;
1085 }
1087 CacheIndex::UpdateEntry(mHandle->Hash(),
1088 mHasFrecency ? &mFrecency : nullptr,
1089 mHasExpirationTime ? &mExpirationTime : nullptr,
1090 nullptr);
1091 return NS_OK;
1092 }
1094 protected:
1095 nsRefPtr<CacheFileHandle> mHandle;
1096 bool mHasFrecency;
1097 bool mHasExpirationTime;
1098 uint32_t mFrecency;
1099 uint32_t mExpirationTime;
1100 };
1102 class MetadataWriteScheduleEvent : public nsRunnable
1103 {
1104 public:
1105 enum EMode {
1106 SCHEDULE,
1107 UNSCHEDULE,
1108 SHUTDOWN
1109 } mMode;
1111 nsRefPtr<CacheFile> mFile;
1112 nsRefPtr<CacheFileIOManager> mIOMan;
1114 MetadataWriteScheduleEvent(CacheFileIOManager * aManager,
1115 CacheFile * aFile,
1116 EMode aMode)
1117 : mMode(aMode)
1118 , mFile(aFile)
1119 , mIOMan(aManager)
1120 { }
1122 virtual ~MetadataWriteScheduleEvent() { }
1124 NS_IMETHOD Run()
1125 {
1126 nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
1127 if (!ioMan) {
1128 NS_WARNING("CacheFileIOManager already gone in MetadataWriteScheduleEvent::Run()");
1129 return NS_OK;
1130 }
1132 switch (mMode)
1133 {
1134 case SCHEDULE:
1135 ioMan->ScheduleMetadataWriteInternal(mFile);
1136 break;
1137 case UNSCHEDULE:
1138 ioMan->UnscheduleMetadataWriteInternal(mFile);
1139 break;
1140 case SHUTDOWN:
1141 ioMan->ShutdownMetadataWriteSchedulingInternal();
1142 break;
1143 }
1144 return NS_OK;
1145 }
1146 };
1148 CacheFileIOManager * CacheFileIOManager::gInstance = nullptr;
1150 NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback)
1152 CacheFileIOManager::CacheFileIOManager()
1153 : mShuttingDown(false)
1154 , mTreeCreated(false)
1155 , mOverLimitEvicting(false)
1156 , mRemovingTrashDirs(false)
1157 {
1158 LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
1159 MOZ_COUNT_CTOR(CacheFileIOManager);
1160 MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
1161 }
1163 CacheFileIOManager::~CacheFileIOManager()
1164 {
1165 LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
1166 MOZ_COUNT_DTOR(CacheFileIOManager);
1167 }
1169 // static
1170 nsresult
1171 CacheFileIOManager::Init()
1172 {
1173 LOG(("CacheFileIOManager::Init()"));
1175 MOZ_ASSERT(NS_IsMainThread());
1177 if (gInstance) {
1178 return NS_ERROR_ALREADY_INITIALIZED;
1179 }
1181 nsRefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
1183 nsresult rv = ioMan->InitInternal();
1184 NS_ENSURE_SUCCESS(rv, rv);
1186 ioMan.swap(gInstance);
1187 return NS_OK;
1188 }
1190 nsresult
1191 CacheFileIOManager::InitInternal()
1192 {
1193 nsresult rv;
1195 mIOThread = new CacheIOThread();
1197 rv = mIOThread->Init();
1198 MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
1199 NS_ENSURE_SUCCESS(rv, rv);
1201 mStartTime = TimeStamp::NowLoRes();
1203 return NS_OK;
1204 }
1206 // static
1207 nsresult
1208 CacheFileIOManager::Shutdown()
1209 {
1210 LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance));
1212 MOZ_ASSERT(NS_IsMainThread());
1214 if (!gInstance) {
1215 return NS_ERROR_NOT_INITIALIZED;
1216 }
1218 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2> shutdownTimer;
1220 CacheIndex::PreShutdown();
1222 ShutdownMetadataWriteScheduling();
1224 {
1225 mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock");
1226 mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar");
1228 MutexAutoLock autoLock(lock);
1229 nsRefPtr<ShutdownEvent> ev = new ShutdownEvent(&lock, &condVar);
1230 DebugOnly<nsresult> rv;
1231 rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
1232 MOZ_ASSERT(NS_SUCCEEDED(rv));
1233 condVar.Wait();
1234 }
1236 MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
1237 MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
1239 if (gInstance->mIOThread) {
1240 gInstance->mIOThread->Shutdown();
1241 }
1243 CacheIndex::Shutdown();
1245 if (CacheObserver::ClearCacheOnShutdown()) {
1246 gInstance->SyncRemoveAllCacheFiles();
1247 }
1249 nsRefPtr<CacheFileIOManager> ioMan;
1250 ioMan.swap(gInstance);
1252 return NS_OK;
1253 }
1255 nsresult
1256 CacheFileIOManager::ShutdownInternal()
1257 {
1258 LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
1260 MOZ_ASSERT(mIOThread->IsCurrentThread());
1262 // No new handles can be created after this flag is set
1263 mShuttingDown = true;
1265 // close all handles and delete all associated files
1266 nsTArray<nsRefPtr<CacheFileHandle> > handles;
1267 mHandles.GetAllHandles(&handles);
1268 handles.AppendElements(mSpecialHandles);
1270 for (uint32_t i=0 ; i<handles.Length() ; i++) {
1271 CacheFileHandle *h = handles[i];
1272 h->mClosed = true;
1274 h->Log();
1276 // Close file handle
1277 if (h->mFD) {
1278 ReleaseNSPRHandleInternal(h);
1279 }
1281 // Remove file if entry is doomed or invalid
1282 if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) {
1283 LOG(("CacheFileIOManager::ShutdownInternal() - Removing file from disk"));
1284 h->mFile->Remove(false);
1285 }
1287 if (!h->IsSpecialFile() && !h->mIsDoomed &&
1288 (h->mInvalid || !h->mFileExists)) {
1289 CacheIndex::RemoveEntry(h->Hash());
1290 }
1292 // Remove the handle from mHandles/mSpecialHandles
1293 if (h->IsSpecialFile()) {
1294 mSpecialHandles.RemoveElement(h);
1295 } else {
1296 mHandles.RemoveHandle(h);
1297 }
1298 }
1300 // Assert the table is empty. When we are here, no new handles can be added
1301 // and handles will no longer remove them self from this table and we don't
1302 // want to keep invalid handles here. Also, there is no lookup after this
1303 // point to happen.
1304 MOZ_ASSERT(mHandles.HandleCount() == 0);
1306 // Release trash directory enumerator
1307 if (mTrashDirEnumerator) {
1308 mTrashDirEnumerator->Close();
1309 mTrashDirEnumerator = nullptr;
1310 }
1312 return NS_OK;
1313 }
1315 // static
1316 nsresult
1317 CacheFileIOManager::OnProfile()
1318 {
1319 LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance));
1321 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1322 if (!ioMan) {
1323 // CacheFileIOManager::Init() failed, probably could not create the IO
1324 // thread, just go with it...
1325 return NS_ERROR_NOT_INITIALIZED;
1326 }
1328 nsresult rv;
1330 nsCOMPtr<nsIFile> directory;
1332 CacheObserver::ParentDirOverride(getter_AddRefs(directory));
1334 #if defined(MOZ_WIDGET_ANDROID)
1335 char* cachePath = getenv("CACHE_DIRECTORY");
1336 if (!directory && cachePath && *cachePath) {
1337 rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
1338 true, getter_AddRefs(directory));
1339 }
1340 #endif
1342 if (!directory) {
1343 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
1344 getter_AddRefs(directory));
1345 }
1347 if (!directory) {
1348 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1349 getter_AddRefs(directory));
1350 }
1352 if (directory) {
1353 rv = directory->Append(NS_LITERAL_STRING("cache2"));
1354 NS_ENSURE_SUCCESS(rv, rv);
1355 }
1357 // All functions return a clone.
1358 ioMan->mCacheDirectory.swap(directory);
1360 if (ioMan->mCacheDirectory) {
1361 CacheIndex::Init(ioMan->mCacheDirectory);
1362 }
1364 return NS_OK;
1365 }
1367 // static
1368 already_AddRefed<nsIEventTarget>
1369 CacheFileIOManager::IOTarget()
1370 {
1371 nsCOMPtr<nsIEventTarget> target;
1372 if (gInstance && gInstance->mIOThread) {
1373 target = gInstance->mIOThread->Target();
1374 }
1376 return target.forget();
1377 }
1379 // static
1380 already_AddRefed<CacheIOThread>
1381 CacheFileIOManager::IOThread()
1382 {
1383 nsRefPtr<CacheIOThread> thread;
1384 if (gInstance) {
1385 thread = gInstance->mIOThread;
1386 }
1388 return thread.forget();
1389 }
1391 // static
1392 bool
1393 CacheFileIOManager::IsOnIOThread()
1394 {
1395 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1396 if (ioMan && ioMan->mIOThread) {
1397 return ioMan->mIOThread->IsCurrentThread();
1398 }
1400 return false;
1401 }
1403 // static
1404 bool
1405 CacheFileIOManager::IsOnIOThreadOrCeased()
1406 {
1407 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1408 if (ioMan && ioMan->mIOThread) {
1409 return ioMan->mIOThread->IsCurrentThread();
1410 }
1412 // Ceased...
1413 return true;
1414 }
1416 // static
1417 bool
1418 CacheFileIOManager::IsShutdown()
1419 {
1420 if (!gInstance) {
1421 return true;
1422 }
1423 return gInstance->mShuttingDown;
1424 }
1426 // static
1427 nsresult
1428 CacheFileIOManager::ScheduleMetadataWrite(CacheFile * aFile)
1429 {
1430 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1431 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1433 NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
1435 nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1436 ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
1437 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1438 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1439 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1440 }
1442 nsresult
1443 CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile * aFile)
1444 {
1445 MOZ_ASSERT(IsOnIOThreadOrCeased());
1447 nsresult rv;
1449 if (!mMetadataWritesTimer) {
1450 mMetadataWritesTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1451 NS_ENSURE_SUCCESS(rv, rv);
1453 rv = mMetadataWritesTimer->InitWithCallback(
1454 this, kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT);
1455 NS_ENSURE_SUCCESS(rv, rv);
1456 }
1458 if (mScheduledMetadataWrites.IndexOf(aFile) !=
1459 mScheduledMetadataWrites.NoIndex) {
1460 return NS_OK;
1461 }
1463 mScheduledMetadataWrites.AppendElement(aFile);
1465 return NS_OK;
1466 }
1468 // static
1469 nsresult
1470 CacheFileIOManager::UnscheduleMetadataWrite(CacheFile * aFile)
1471 {
1472 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1473 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1475 NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
1477 nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1478 ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
1479 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1480 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1481 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1482 }
1484 nsresult
1485 CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile * aFile)
1486 {
1487 MOZ_ASSERT(IsOnIOThreadOrCeased());
1489 mScheduledMetadataWrites.RemoveElement(aFile);
1491 if (mScheduledMetadataWrites.Length() == 0 &&
1492 mMetadataWritesTimer) {
1493 mMetadataWritesTimer->Cancel();
1494 mMetadataWritesTimer = nullptr;
1495 }
1497 return NS_OK;
1498 }
1500 // static
1501 nsresult
1502 CacheFileIOManager::ShutdownMetadataWriteScheduling()
1503 {
1504 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1505 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1507 nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1508 ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN);
1509 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1510 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1511 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1512 }
1514 nsresult
1515 CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal()
1516 {
1517 MOZ_ASSERT(IsOnIOThreadOrCeased());
1519 nsTArray<nsRefPtr<CacheFile> > files;
1520 files.SwapElements(mScheduledMetadataWrites);
1521 for (uint32_t i = 0; i < files.Length(); ++i) {
1522 CacheFile * file = files[i];
1523 file->WriteMetadataIfNeeded();
1524 }
1526 if (mMetadataWritesTimer) {
1527 mMetadataWritesTimer->Cancel();
1528 mMetadataWritesTimer = nullptr;
1529 }
1531 return NS_OK;
1532 }
1534 NS_IMETHODIMP
1535 CacheFileIOManager::Notify(nsITimer * aTimer)
1536 {
1537 MOZ_ASSERT(IsOnIOThreadOrCeased());
1538 MOZ_ASSERT(mMetadataWritesTimer == aTimer);
1540 mMetadataWritesTimer = nullptr;
1542 nsTArray<nsRefPtr<CacheFile> > files;
1543 files.SwapElements(mScheduledMetadataWrites);
1544 for (uint32_t i = 0; i < files.Length(); ++i) {
1545 CacheFile * file = files[i];
1546 file->WriteMetadataIfNeeded();
1547 }
1549 return NS_OK;
1550 }
1552 // static
1553 nsresult
1554 CacheFileIOManager::OpenFile(const nsACString &aKey,
1555 uint32_t aFlags, bool aResultOnAnyThread,
1556 CacheFileIOListener *aCallback)
1557 {
1558 LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
1559 PromiseFlatCString(aKey).get(), aFlags, aCallback));
1561 nsresult rv;
1562 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1564 if (!ioMan) {
1565 return NS_ERROR_NOT_INITIALIZED;
1566 }
1568 bool priority = aFlags & CacheFileIOManager::PRIORITY;
1569 nsRefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aResultOnAnyThread, aCallback);
1570 rv = ioMan->mIOThread->Dispatch(ev, priority
1571 ? CacheIOThread::OPEN_PRIORITY
1572 : CacheIOThread::OPEN);
1573 NS_ENSURE_SUCCESS(rv, rv);
1575 return NS_OK;
1576 }
1578 nsresult
1579 CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
1580 const nsACString &aKey,
1581 uint32_t aFlags,
1582 CacheFileHandle **_retval)
1583 {
1584 LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
1585 "key=%s, flags=%d]", LOGSHA1(aHash), PromiseFlatCString(aKey).get(),
1586 aFlags));
1588 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1590 nsresult rv;
1592 if (mShuttingDown) {
1593 return NS_ERROR_NOT_INITIALIZED;
1594 }
1596 if (!mTreeCreated) {
1597 rv = CreateCacheTree();
1598 if (NS_FAILED(rv)) return rv;
1599 }
1601 nsCOMPtr<nsIFile> file;
1602 rv = GetFile(aHash, getter_AddRefs(file));
1603 NS_ENSURE_SUCCESS(rv, rv);
1605 nsRefPtr<CacheFileHandle> handle;
1606 mHandles.GetHandle(aHash, false, getter_AddRefs(handle));
1608 if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
1609 if (handle) {
1610 rv = DoomFileInternal(handle);
1611 NS_ENSURE_SUCCESS(rv, rv);
1612 handle = nullptr;
1613 }
1615 rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
1616 NS_ENSURE_SUCCESS(rv, rv);
1618 bool exists;
1619 rv = file->Exists(&exists);
1620 NS_ENSURE_SUCCESS(rv, rv);
1622 if (exists) {
1623 CacheIndex::RemoveEntry(aHash);
1625 LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file from "
1626 "disk"));
1627 rv = file->Remove(false);
1628 if (NS_FAILED(rv)) {
1629 NS_WARNING("Cannot remove old entry from the disk");
1630 LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
1631 ". [rv=0x%08x]", rv));
1632 }
1633 }
1635 CacheIndex::AddEntry(aHash);
1636 handle->mFile.swap(file);
1637 handle->mFileSize = 0;
1638 }
1640 if (handle) {
1641 handle.swap(*_retval);
1642 return NS_OK;
1643 }
1645 bool exists;
1646 rv = file->Exists(&exists);
1647 NS_ENSURE_SUCCESS(rv, rv);
1649 if (exists && mContextEvictor) {
1650 if (mContextEvictor->ContextsCount() == 0) {
1651 mContextEvictor = nullptr;
1652 } else {
1653 bool wasEvicted = false;
1654 mContextEvictor->WasEvicted(aKey, file, &wasEvicted);
1655 if (wasEvicted) {
1656 LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the "
1657 "entry was evicted by EvictByContext()"));
1658 exists = false;
1659 file->Remove(false);
1660 CacheIndex::RemoveEntry(aHash);
1661 }
1662 }
1663 }
1665 if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
1666 return NS_ERROR_NOT_AVAILABLE;
1667 }
1669 rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
1670 NS_ENSURE_SUCCESS(rv, rv);
1672 if (exists) {
1673 rv = file->GetFileSize(&handle->mFileSize);
1674 NS_ENSURE_SUCCESS(rv, rv);
1676 handle->mFileExists = true;
1678 CacheIndex::EnsureEntryExists(aHash);
1679 } else {
1680 handle->mFileSize = 0;
1682 CacheIndex::AddEntry(aHash);
1683 }
1685 handle->mFile.swap(file);
1686 handle.swap(*_retval);
1687 return NS_OK;
1688 }
1690 nsresult
1691 CacheFileIOManager::OpenSpecialFileInternal(const nsACString &aKey,
1692 uint32_t aFlags,
1693 CacheFileHandle **_retval)
1694 {
1695 LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
1696 PromiseFlatCString(aKey).get(), aFlags));
1698 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1700 nsresult rv;
1702 if (mShuttingDown) {
1703 return NS_ERROR_NOT_INITIALIZED;
1704 }
1706 if (!mTreeCreated) {
1707 rv = CreateCacheTree();
1708 if (NS_FAILED(rv)) return rv;
1709 }
1711 nsCOMPtr<nsIFile> file;
1712 rv = GetSpecialFile(aKey, getter_AddRefs(file));
1713 NS_ENSURE_SUCCESS(rv, rv);
1715 nsRefPtr<CacheFileHandle> handle;
1716 for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) {
1717 if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) {
1718 handle = mSpecialHandles[i];
1719 break;
1720 }
1721 }
1723 if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
1724 if (handle) {
1725 rv = DoomFileInternal(handle);
1726 NS_ENSURE_SUCCESS(rv, rv);
1727 handle = nullptr;
1728 }
1730 handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
1731 mSpecialHandles.AppendElement(handle);
1733 bool exists;
1734 rv = file->Exists(&exists);
1735 NS_ENSURE_SUCCESS(rv, rv);
1737 if (exists) {
1738 LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
1739 "disk"));
1740 rv = file->Remove(false);
1741 if (NS_FAILED(rv)) {
1742 NS_WARNING("Cannot remove old entry from the disk");
1743 LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
1744 "failed. [rv=0x%08x]", rv));
1745 }
1746 }
1748 handle->mFile.swap(file);
1749 handle->mFileSize = 0;
1750 }
1752 if (handle) {
1753 handle.swap(*_retval);
1754 return NS_OK;
1755 }
1757 bool exists;
1758 rv = file->Exists(&exists);
1759 NS_ENSURE_SUCCESS(rv, rv);
1761 if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
1762 return NS_ERROR_NOT_AVAILABLE;
1763 }
1765 handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
1766 mSpecialHandles.AppendElement(handle);
1768 if (exists) {
1769 rv = file->GetFileSize(&handle->mFileSize);
1770 NS_ENSURE_SUCCESS(rv, rv);
1772 handle->mFileExists = true;
1773 } else {
1774 handle->mFileSize = 0;
1775 }
1777 handle->mFile.swap(file);
1778 handle.swap(*_retval);
1779 return NS_OK;
1780 }
1782 nsresult
1783 CacheFileIOManager::CloseHandleInternal(CacheFileHandle *aHandle)
1784 {
1785 LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle));
1786 aHandle->Log();
1788 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
1790 // Close file handle
1791 if (aHandle->mFD) {
1792 ReleaseNSPRHandleInternal(aHandle);
1793 }
1795 // Delete the file if the entry was doomed or invalid
1796 if (aHandle->mIsDoomed || aHandle->mInvalid) {
1797 LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from "
1798 "disk"));
1799 aHandle->mFile->Remove(false);
1800 }
1802 if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
1803 (aHandle->mInvalid || !aHandle->mFileExists)) {
1804 CacheIndex::RemoveEntry(aHandle->Hash());
1805 }
1807 // Don't remove handles after shutdown
1808 if (!mShuttingDown) {
1809 if (aHandle->IsSpecialFile()) {
1810 mSpecialHandles.RemoveElement(aHandle);
1811 } else {
1812 mHandles.RemoveHandle(aHandle);
1813 }
1814 }
1816 return NS_OK;
1817 }
1819 // static
1820 nsresult
1821 CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset,
1822 char *aBuf, int32_t aCount, bool aResultOnAnyThread,
1823 CacheFileIOListener *aCallback)
1824 {
1825 LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, "
1826 "listener=%p]", aHandle, aOffset, aCount, aCallback));
1828 nsresult rv;
1829 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1831 if (aHandle->IsClosed() || !ioMan) {
1832 return NS_ERROR_NOT_INITIALIZED;
1833 }
1835 nsRefPtr<ReadEvent> ev = new ReadEvent(aHandle, aOffset, aBuf, aCount,
1836 aResultOnAnyThread, aCallback);
1837 rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
1838 ? CacheIOThread::READ_PRIORITY
1839 : CacheIOThread::READ);
1840 NS_ENSURE_SUCCESS(rv, rv);
1842 return NS_OK;
1843 }
1845 nsresult
1846 CacheFileIOManager::ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
1847 char *aBuf, int32_t aCount)
1848 {
1849 LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%lld, count=%d]",
1850 aHandle, aOffset, aCount));
1852 nsresult rv;
1854 if (!aHandle->mFileExists) {
1855 NS_WARNING("Trying to read from non-existent file");
1856 return NS_ERROR_NOT_AVAILABLE;
1857 }
1859 if (!aHandle->mFD) {
1860 rv = OpenNSPRHandle(aHandle);
1861 NS_ENSURE_SUCCESS(rv, rv);
1862 } else {
1863 NSPRHandleUsed(aHandle);
1864 }
1866 // Check again, OpenNSPRHandle could figure out the file was gone.
1867 if (!aHandle->mFileExists) {
1868 NS_WARNING("Trying to read from non-existent file");
1869 return NS_ERROR_NOT_AVAILABLE;
1870 }
1872 int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
1873 if (offset == -1) {
1874 return NS_ERROR_FAILURE;
1875 }
1877 int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
1878 if (bytesRead != aCount) {
1879 return NS_ERROR_FAILURE;
1880 }
1882 return NS_OK;
1883 }
1885 // static
1886 nsresult
1887 CacheFileIOManager::Write(CacheFileHandle *aHandle, int64_t aOffset,
1888 const char *aBuf, int32_t aCount, bool aValidate,
1889 CacheFileIOListener *aCallback)
1890 {
1891 LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, "
1892 "validate=%d, listener=%p]", aHandle, aOffset, aCount, aValidate,
1893 aCallback));
1895 nsresult rv;
1896 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1898 if (aHandle->IsClosed() || !ioMan) {
1899 return NS_ERROR_NOT_INITIALIZED;
1900 }
1902 nsRefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
1903 aValidate, aCallback);
1904 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
1905 NS_ENSURE_SUCCESS(rv, rv);
1907 return NS_OK;
1908 }
1910 nsresult
1911 CacheFileIOManager::WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
1912 const char *aBuf, int32_t aCount,
1913 bool aValidate)
1914 {
1915 LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%lld, count=%d, "
1916 "validate=%d]", aHandle, aOffset, aCount, aValidate));
1918 nsresult rv;
1920 if (!aHandle->mFileExists) {
1921 rv = CreateFile(aHandle);
1922 NS_ENSURE_SUCCESS(rv, rv);
1923 }
1925 if (!aHandle->mFD) {
1926 rv = OpenNSPRHandle(aHandle);
1927 NS_ENSURE_SUCCESS(rv, rv);
1928 } else {
1929 NSPRHandleUsed(aHandle);
1930 }
1932 // Check again, OpenNSPRHandle could figure out the file was gone.
1933 if (!aHandle->mFileExists) {
1934 return NS_ERROR_NOT_AVAILABLE;
1935 }
1937 // Write invalidates the entry by default
1938 aHandle->mInvalid = true;
1940 int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
1941 if (offset == -1) {
1942 return NS_ERROR_FAILURE;
1943 }
1945 int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
1947 if (bytesWritten != -1 && aHandle->mFileSize < aOffset+bytesWritten) {
1948 aHandle->mFileSize = aOffset+bytesWritten;
1950 if (!aHandle->IsDoomed() && !aHandle->IsSpecialFile()) {
1951 uint32_t size = aHandle->FileSizeInK();
1952 CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, &size);
1953 EvictIfOverLimitInternal();
1954 }
1955 }
1957 if (bytesWritten != aCount) {
1958 return NS_ERROR_FAILURE;
1959 }
1961 // Write was successful and this write validates the entry (i.e. metadata)
1962 if (aValidate) {
1963 aHandle->mInvalid = false;
1964 }
1966 return NS_OK;
1967 }
1969 // static
1970 nsresult
1971 CacheFileIOManager::DoomFile(CacheFileHandle *aHandle,
1972 CacheFileIOListener *aCallback)
1973 {
1974 LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]",
1975 aHandle, aCallback));
1977 nsresult rv;
1978 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1980 if (aHandle->IsClosed() || !ioMan) {
1981 return NS_ERROR_NOT_INITIALIZED;
1982 }
1984 nsRefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
1985 rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
1986 ? CacheIOThread::OPEN_PRIORITY
1987 : CacheIOThread::OPEN);
1988 NS_ENSURE_SUCCESS(rv, rv);
1990 return NS_OK;
1991 }
1993 nsresult
1994 CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
1995 {
1996 LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
1997 aHandle->Log();
1999 nsresult rv;
2001 if (aHandle->IsDoomed()) {
2002 return NS_OK;
2003 }
2005 if (aHandle->mFileExists) {
2006 // we need to move the current file to the doomed directory
2007 if (aHandle->mFD) {
2008 ReleaseNSPRHandleInternal(aHandle);
2009 }
2011 // find unused filename
2012 nsCOMPtr<nsIFile> file;
2013 rv = GetDoomedFile(getter_AddRefs(file));
2014 NS_ENSURE_SUCCESS(rv, rv);
2016 nsCOMPtr<nsIFile> parentDir;
2017 rv = file->GetParent(getter_AddRefs(parentDir));
2018 NS_ENSURE_SUCCESS(rv, rv);
2020 nsAutoCString leafName;
2021 rv = file->GetNativeLeafName(leafName);
2022 NS_ENSURE_SUCCESS(rv, rv);
2024 rv = aHandle->mFile->MoveToNative(parentDir, leafName);
2025 if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
2026 LOG((" file already removed under our hands"));
2027 aHandle->mFileExists = false;
2028 rv = NS_OK;
2029 } else {
2030 NS_ENSURE_SUCCESS(rv, rv);
2031 aHandle->mFile.swap(file);
2032 }
2033 }
2035 if (!aHandle->IsSpecialFile()) {
2036 CacheIndex::RemoveEntry(aHandle->Hash());
2037 }
2039 aHandle->mIsDoomed = true;
2041 if (!aHandle->IsSpecialFile()) {
2042 nsRefPtr<CacheStorageService> storageService = CacheStorageService::Self();
2043 if (storageService) {
2044 nsAutoCString idExtension, url;
2045 nsCOMPtr<nsILoadContextInfo> info =
2046 CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url);
2047 MOZ_ASSERT(info);
2048 if (info) {
2049 storageService->CacheFileDoomed(info, idExtension, url);
2050 }
2051 }
2052 }
2054 return NS_OK;
2055 }
2057 // static
2058 nsresult
2059 CacheFileIOManager::DoomFileByKey(const nsACString &aKey,
2060 CacheFileIOListener *aCallback)
2061 {
2062 LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
2063 PromiseFlatCString(aKey).get(), aCallback));
2065 nsresult rv;
2066 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2068 if (!ioMan) {
2069 return NS_ERROR_NOT_INITIALIZED;
2070 }
2072 nsRefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
2073 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2074 NS_ENSURE_SUCCESS(rv, rv);
2076 return NS_OK;
2077 }
2079 nsresult
2080 CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash)
2081 {
2082 LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]"
2083 , LOGSHA1(aHash)));
2085 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2087 nsresult rv;
2089 if (mShuttingDown) {
2090 return NS_ERROR_NOT_INITIALIZED;
2091 }
2093 if (!mCacheDirectory) {
2094 return NS_ERROR_FILE_INVALID_PATH;
2095 }
2097 // Find active handle
2098 nsRefPtr<CacheFileHandle> handle;
2099 mHandles.GetHandle(aHash, true, getter_AddRefs(handle));
2101 if (handle) {
2102 handle->Log();
2104 if (handle->IsDoomed()) {
2105 return NS_OK;
2106 }
2108 return DoomFileInternal(handle);
2109 }
2111 // There is no handle for this file, delete the file if exists
2112 nsCOMPtr<nsIFile> file;
2113 rv = GetFile(aHash, getter_AddRefs(file));
2114 NS_ENSURE_SUCCESS(rv, rv);
2116 bool exists;
2117 rv = file->Exists(&exists);
2118 NS_ENSURE_SUCCESS(rv, rv);
2120 if (!exists) {
2121 return NS_ERROR_NOT_AVAILABLE;
2122 }
2124 LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from "
2125 "disk"));
2126 rv = file->Remove(false);
2127 if (NS_FAILED(rv)) {
2128 NS_WARNING("Cannot remove old entry from the disk");
2129 LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
2130 "[rv=0x%08x]", rv));
2131 }
2133 CacheIndex::RemoveEntry(aHash);
2135 return NS_OK;
2136 }
2138 // static
2139 nsresult
2140 CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle)
2141 {
2142 LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
2144 nsresult rv;
2145 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2147 if (aHandle->IsClosed() || !ioMan) {
2148 return NS_ERROR_NOT_INITIALIZED;
2149 }
2151 nsRefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
2152 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
2153 NS_ENSURE_SUCCESS(rv, rv);
2155 return NS_OK;
2156 }
2158 nsresult
2159 CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle)
2160 {
2161 LOG(("CacheFileIOManager::ReleaseNSPRHandleInternal() [handle=%p]", aHandle));
2163 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2164 MOZ_ASSERT(aHandle->mFD);
2166 DebugOnly<bool> found;
2167 found = mHandlesByLastUsed.RemoveElement(aHandle);
2168 MOZ_ASSERT(found);
2170 PR_Close(aHandle->mFD);
2171 aHandle->mFD = nullptr;
2173 return NS_OK;
2174 }
2176 // static
2177 nsresult
2178 CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle,
2179 int64_t aTruncatePos, int64_t aEOFPos,
2180 CacheFileIOListener *aCallback)
2181 {
2182 LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, "
2183 "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback));
2185 nsresult rv;
2186 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2188 if (aHandle->IsClosed() || !ioMan) {
2189 return NS_ERROR_NOT_INITIALIZED;
2190 }
2192 nsRefPtr<TruncateSeekSetEOFEvent> ev = new TruncateSeekSetEOFEvent(
2193 aHandle, aTruncatePos, aEOFPos,
2194 aCallback);
2195 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
2196 NS_ENSURE_SUCCESS(rv, rv);
2198 return NS_OK;
2199 }
2201 // static
2202 void CacheFileIOManager::GetCacheDirectory(nsIFile** result)
2203 {
2204 *result = nullptr;
2206 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2207 if (!ioMan) {
2208 return;
2209 }
2211 nsCOMPtr<nsIFile> file = ioMan->mCacheDirectory;
2212 file.forget(result);
2213 }
2215 static nsresult
2216 TruncFile(PRFileDesc *aFD, uint32_t aEOF)
2217 {
2218 #if defined(XP_UNIX)
2219 if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
2220 NS_ERROR("ftruncate failed");
2221 return NS_ERROR_FAILURE;
2222 }
2223 #elif defined(XP_WIN)
2224 int32_t cnt = PR_Seek(aFD, aEOF, PR_SEEK_SET);
2225 if (cnt == -1) {
2226 return NS_ERROR_FAILURE;
2227 }
2228 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(aFD))) {
2229 NS_ERROR("SetEndOfFile failed");
2230 return NS_ERROR_FAILURE;
2231 }
2232 #else
2233 MOZ_ASSERT(false, "Not implemented!");
2234 #endif
2236 return NS_OK;
2237 }
2239 nsresult
2240 CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
2241 int64_t aTruncatePos,
2242 int64_t aEOFPos)
2243 {
2244 LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
2245 "truncatePos=%lld, EOFPos=%lld]", aHandle, aTruncatePos, aEOFPos));
2247 nsresult rv;
2249 if (!aHandle->mFileExists) {
2250 rv = CreateFile(aHandle);
2251 NS_ENSURE_SUCCESS(rv, rv);
2252 }
2254 if (!aHandle->mFD) {
2255 rv = OpenNSPRHandle(aHandle);
2256 NS_ENSURE_SUCCESS(rv, rv);
2257 } else {
2258 NSPRHandleUsed(aHandle);
2259 }
2261 // Check again, OpenNSPRHandle could figure out the file was gone.
2262 if (!aHandle->mFileExists) {
2263 return NS_ERROR_NOT_AVAILABLE;
2264 }
2266 // This operation always invalidates the entry
2267 aHandle->mInvalid = true;
2269 rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aTruncatePos));
2270 NS_ENSURE_SUCCESS(rv, rv);
2272 rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aEOFPos));
2273 NS_ENSURE_SUCCESS(rv, rv);
2275 return NS_OK;
2276 }
2278 // static
2279 nsresult
2280 CacheFileIOManager::RenameFile(CacheFileHandle *aHandle,
2281 const nsACString &aNewName,
2282 CacheFileIOListener *aCallback)
2283 {
2284 LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
2285 aHandle, PromiseFlatCString(aNewName).get(), aCallback));
2287 nsresult rv;
2288 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2290 if (aHandle->IsClosed() || !ioMan) {
2291 return NS_ERROR_NOT_INITIALIZED;
2292 }
2294 if (!aHandle->IsSpecialFile()) {
2295 return NS_ERROR_UNEXPECTED;
2296 }
2298 nsRefPtr<RenameFileEvent> ev = new RenameFileEvent(aHandle, aNewName,
2299 aCallback);
2300 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
2301 NS_ENSURE_SUCCESS(rv, rv);
2303 return NS_OK;
2304 }
2306 nsresult
2307 CacheFileIOManager::RenameFileInternal(CacheFileHandle *aHandle,
2308 const nsACString &aNewName)
2309 {
2310 LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",
2311 aHandle, PromiseFlatCString(aNewName).get()));
2313 nsresult rv;
2315 MOZ_ASSERT(aHandle->IsSpecialFile());
2317 if (aHandle->IsDoomed()) {
2318 return NS_ERROR_NOT_AVAILABLE;
2319 }
2321 // Doom old handle if it exists and is not doomed
2322 for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) {
2323 if (!mSpecialHandles[i]->IsDoomed() &&
2324 mSpecialHandles[i]->Key() == aNewName) {
2325 MOZ_ASSERT(aHandle != mSpecialHandles[i]);
2326 rv = DoomFileInternal(mSpecialHandles[i]);
2327 NS_ENSURE_SUCCESS(rv, rv);
2328 break;
2329 }
2330 }
2332 nsCOMPtr<nsIFile> file;
2333 rv = GetSpecialFile(aNewName, getter_AddRefs(file));
2334 NS_ENSURE_SUCCESS(rv, rv);
2336 bool exists;
2337 rv = file->Exists(&exists);
2338 NS_ENSURE_SUCCESS(rv, rv);
2340 if (exists) {
2341 LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file from "
2342 "disk"));
2343 rv = file->Remove(false);
2344 if (NS_FAILED(rv)) {
2345 NS_WARNING("Cannot remove file from the disk");
2346 LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file failed"
2347 ". [rv=0x%08x]", rv));
2348 }
2349 }
2351 if (!aHandle->FileExists()) {
2352 aHandle->mKey = aNewName;
2353 return NS_OK;
2354 }
2356 if (aHandle->mFD) {
2357 ReleaseNSPRHandleInternal(aHandle);
2358 }
2360 rv = aHandle->mFile->MoveToNative(nullptr, aNewName);
2361 NS_ENSURE_SUCCESS(rv, rv);
2363 aHandle->mKey = aNewName;
2364 return NS_OK;
2365 }
2367 // static
2368 nsresult
2369 CacheFileIOManager::EvictIfOverLimit()
2370 {
2371 LOG(("CacheFileIOManager::EvictIfOverLimit()"));
2373 nsresult rv;
2374 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2376 if (!ioMan) {
2377 return NS_ERROR_NOT_INITIALIZED;
2378 }
2380 nsCOMPtr<nsIRunnable> ev;
2381 ev = NS_NewRunnableMethod(ioMan,
2382 &CacheFileIOManager::EvictIfOverLimitInternal);
2384 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2385 NS_ENSURE_SUCCESS(rv, rv);
2387 return NS_OK;
2388 }
2390 nsresult
2391 CacheFileIOManager::EvictIfOverLimitInternal()
2392 {
2393 LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
2395 nsresult rv;
2397 MOZ_ASSERT(mIOThread->IsCurrentThread());
2399 if (mShuttingDown) {
2400 return NS_ERROR_NOT_INITIALIZED;
2401 }
2403 if (mOverLimitEvicting) {
2404 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
2405 "running."));
2406 return NS_OK;
2407 }
2409 UpdateSmartCacheSize();
2411 uint32_t cacheUsage;
2412 rv = CacheIndex::GetCacheSize(&cacheUsage);
2413 NS_ENSURE_SUCCESS(rv, rv);
2415 uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
2416 if (cacheUsage <= cacheLimit) {
2417 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size under "
2418 "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
2419 return NS_OK;
2420 }
2422 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
2423 "limit. Starting overlimit eviction. [cacheSize=%u, limit=%u]",
2424 cacheUsage, cacheLimit));
2426 nsCOMPtr<nsIRunnable> ev;
2427 ev = NS_NewRunnableMethod(this,
2428 &CacheFileIOManager::OverLimitEvictionInternal);
2430 rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2431 NS_ENSURE_SUCCESS(rv, rv);
2433 mOverLimitEvicting = true;
2434 return NS_OK;
2435 }
2437 nsresult
2438 CacheFileIOManager::OverLimitEvictionInternal()
2439 {
2440 LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
2442 nsresult rv;
2444 MOZ_ASSERT(mIOThread->IsCurrentThread());
2446 // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
2447 // here and set it to true again once we dispatch another event that will
2448 // continue with the eviction. The reason why we do so is that we can fail
2449 // early anywhere in this method and the variable will contain a correct
2450 // value. Otherwise we would need to set it to false on every failing place.
2451 mOverLimitEvicting = false;
2453 if (mShuttingDown) {
2454 return NS_ERROR_NOT_INITIALIZED;
2455 }
2457 UpdateSmartCacheSize();
2459 while (true) {
2460 uint32_t cacheUsage;
2461 rv = CacheIndex::GetCacheSize(&cacheUsage);
2462 NS_ENSURE_SUCCESS(rv, rv);
2464 uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
2465 if (cacheUsage <= cacheLimit) {
2466 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size under "
2467 "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
2468 return NS_OK;
2469 }
2471 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
2472 "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
2474 if (CacheIOThread::YieldAndRerun()) {
2475 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
2476 "for higher level events."));
2477 mOverLimitEvicting = true;
2478 return NS_OK;
2479 }
2481 SHA1Sum::Hash hash;
2482 uint32_t cnt;
2483 static uint32_t consecutiveFailures = 0;
2484 rv = CacheIndex::GetEntryForEviction(&hash, &cnt);
2485 NS_ENSURE_SUCCESS(rv, rv);
2487 rv = DoomFileByKeyInternal(&hash);
2488 if (NS_SUCCEEDED(rv)) {
2489 consecutiveFailures = 0;
2490 } else if (rv == NS_ERROR_NOT_AVAILABLE) {
2491 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - "
2492 "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv));
2493 // TODO index is outdated, start update
2495 // Make sure index won't return the same entry again
2496 CacheIndex::RemoveEntry(&hash);
2497 consecutiveFailures = 0;
2498 } else {
2499 // This shouldn't normally happen, but the eviction must not fail
2500 // completely if we ever encounter this problem.
2501 NS_WARNING("CacheFileIOManager::OverLimitEvictionInternal() - Unexpected "
2502 "failure of DoomFileByKeyInternal()");
2504 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - "
2505 "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv));
2507 // Normally, CacheIndex::UpdateEntry() is called only to update newly
2508 // created/opened entries which are always fresh and UpdateEntry() expects
2509 // and checks this flag. The way we use UpdateEntry() here is a kind of
2510 // hack and we must make sure the flag is set by calling
2511 // EnsureEntryExists().
2512 rv = CacheIndex::EnsureEntryExists(&hash);
2513 NS_ENSURE_SUCCESS(rv, rv);
2515 // Move the entry at the end of both lists to make sure we won't end up
2516 // failing on one entry forever.
2517 uint32_t frecency = 0;
2518 uint32_t expTime = nsICacheEntry::NO_EXPIRATION_TIME;
2519 rv = CacheIndex::UpdateEntry(&hash, &frecency, &expTime, nullptr);
2520 NS_ENSURE_SUCCESS(rv, rv);
2522 consecutiveFailures++;
2523 if (consecutiveFailures >= cnt) {
2524 // This doesn't necessarily mean that we've tried to doom every entry
2525 // but we've reached a sane number of tries. It is likely that another
2526 // eviction will start soon. And as said earlier, this normally doesn't
2527 // happen at all.
2528 return NS_OK;
2529 }
2530 }
2531 }
2533 NS_NOTREACHED("We should never get here");
2534 return NS_OK;
2535 }
2537 // static
2538 nsresult
2539 CacheFileIOManager::EvictAll()
2540 {
2541 LOG(("CacheFileIOManager::EvictAll()"));
2543 nsresult rv;
2544 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2546 if (!ioMan) {
2547 return NS_ERROR_NOT_INITIALIZED;
2548 }
2550 nsCOMPtr<nsIRunnable> ev;
2551 ev = NS_NewRunnableMethod(ioMan, &CacheFileIOManager::EvictAllInternal);
2553 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2554 if (NS_WARN_IF(NS_FAILED(rv))) {
2555 return rv;
2556 }
2558 return NS_OK;
2559 }
2561 namespace {
2563 class EvictionNotifierRunnable : public nsRunnable
2564 {
2565 public:
2566 NS_DECL_NSIRUNNABLE
2567 };
2569 NS_IMETHODIMP
2570 EvictionNotifierRunnable::Run()
2571 {
2572 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
2573 if (obsSvc) {
2574 obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr);
2575 }
2576 return NS_OK;
2577 }
2579 } // anonymous namespace
2581 nsresult
2582 CacheFileIOManager::EvictAllInternal()
2583 {
2584 LOG(("CacheFileIOManager::EvictAllInternal()"));
2586 nsresult rv;
2588 MOZ_ASSERT(mIOThread->IsCurrentThread());
2590 nsRefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
2592 if (!mCacheDirectory) {
2593 // This is a kind of hack. Somebody called EvictAll() without a profile.
2594 // This happens in xpcshell tests that use cache without profile. We need
2595 // to notify observers in this case since the tests are waiting for it.
2596 NS_DispatchToMainThread(r);
2597 return NS_ERROR_FILE_INVALID_PATH;
2598 }
2600 if (mShuttingDown) {
2601 return NS_ERROR_NOT_INITIALIZED;
2602 }
2604 if (!mTreeCreated) {
2605 rv = CreateCacheTree();
2606 if (NS_FAILED(rv)) {
2607 return rv;
2608 }
2609 }
2611 // Doom all active handles
2612 nsTArray<nsRefPtr<CacheFileHandle> > handles;
2613 mHandles.GetActiveHandles(&handles);
2615 for (uint32_t i = 0; i < handles.Length(); ++i) {
2616 rv = DoomFileInternal(handles[i]);
2617 if (NS_WARN_IF(NS_FAILED(rv))) {
2618 LOG(("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
2619 "[handle=%p]", handles[i].get()));
2620 }
2621 }
2623 nsCOMPtr<nsIFile> file;
2624 rv = mCacheDirectory->Clone(getter_AddRefs(file));
2625 if (NS_WARN_IF(NS_FAILED(rv))) {
2626 return rv;
2627 }
2629 rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
2630 if (NS_WARN_IF(NS_FAILED(rv))) {
2631 return rv;
2632 }
2634 // Trash current entries directory
2635 rv = TrashDirectory(file);
2636 if (NS_WARN_IF(NS_FAILED(rv))) {
2637 return rv;
2638 }
2640 // Files are now inaccessible in entries directory, notify observers.
2641 NS_DispatchToMainThread(r);
2643 // Create a new empty entries directory
2644 rv = CheckAndCreateDir(mCacheDirectory, kEntriesDir, false);
2645 if (NS_WARN_IF(NS_FAILED(rv))) {
2646 return rv;
2647 }
2649 CacheIndex::RemoveAll();
2651 return NS_OK;
2652 }
2654 // static
2655 nsresult
2656 CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
2657 {
2658 LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
2659 aLoadContextInfo));
2661 nsresult rv;
2662 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2664 if (!ioMan) {
2665 return NS_ERROR_NOT_INITIALIZED;
2666 }
2668 nsCOMPtr<nsIRunnable> ev;
2669 ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
2670 (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
2672 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2673 if (NS_WARN_IF(NS_FAILED(rv))) {
2674 return rv;
2675 }
2677 return NS_OK;
2678 }
2680 nsresult
2681 CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
2682 {
2683 LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
2684 "anonymous=%u, inBrowser=%u, appId=%u]", aLoadContextInfo,
2685 aLoadContextInfo->IsAnonymous(), aLoadContextInfo->IsInBrowserElement(),
2686 aLoadContextInfo->AppId()));
2688 nsresult rv;
2690 MOZ_ASSERT(mIOThread->IsCurrentThread());
2692 MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
2693 if (aLoadContextInfo->IsPrivate()) {
2694 return NS_ERROR_INVALID_ARG;
2695 }
2697 if (!mCacheDirectory) {
2698 return NS_ERROR_FILE_INVALID_PATH;
2699 }
2701 if (mShuttingDown) {
2702 return NS_ERROR_NOT_INITIALIZED;
2703 }
2705 if (!mTreeCreated) {
2706 rv = CreateCacheTree();
2707 if (NS_FAILED(rv)) {
2708 return rv;
2709 }
2710 }
2712 // Doom all active handles that matches the load context
2713 nsTArray<nsRefPtr<CacheFileHandle> > handles;
2714 mHandles.GetActiveHandles(&handles);
2716 for (uint32_t i = 0; i < handles.Length(); ++i) {
2717 bool equals;
2718 rv = CacheFileUtils::KeyMatchesLoadContextInfo(handles[i]->Key(),
2719 aLoadContextInfo,
2720 &equals);
2721 if (NS_FAILED(rv)) {
2722 LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
2723 "handle! [handle=%p, key=%s]", handles[i].get(),
2724 handles[i]->Key().get()));
2725 MOZ_CRASH("Unexpected error!");
2726 }
2728 if (equals) {
2729 rv = DoomFileInternal(handles[i]);
2730 if (NS_WARN_IF(NS_FAILED(rv))) {
2731 LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
2732 " [handle=%p]", handles[i].get()));
2733 }
2734 }
2735 }
2737 if (!mContextEvictor) {
2738 mContextEvictor = new CacheFileContextEvictor();
2739 mContextEvictor->Init(mCacheDirectory);
2740 }
2742 mContextEvictor->AddContext(aLoadContextInfo);
2744 return NS_OK;
2745 }
2747 // static
2748 nsresult
2749 CacheFileIOManager::CacheIndexStateChanged()
2750 {
2751 LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
2753 nsresult rv;
2755 // CacheFileIOManager lives longer than CacheIndex so gInstance must be
2756 // non-null here.
2757 MOZ_ASSERT(gInstance);
2759 // We have to re-distatch even if we are on IO thread to prevent reentering
2760 // the lock in CacheIndex
2761 nsCOMPtr<nsIRunnable> ev;
2762 ev = NS_NewRunnableMethod(
2763 gInstance, &CacheFileIOManager::CacheIndexStateChangedInternal);
2765 nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
2766 MOZ_ASSERT(ioTarget);
2768 rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
2769 if (NS_WARN_IF(NS_FAILED(rv))) {
2770 return rv;
2771 }
2773 return NS_OK;
2774 }
2776 nsresult
2777 CacheFileIOManager::CacheIndexStateChangedInternal()
2778 {
2779 if (mShuttingDown) {
2780 // ignore notification during shutdown
2781 return NS_OK;
2782 }
2784 if (!mContextEvictor) {
2785 return NS_OK;
2786 }
2788 mContextEvictor->CacheIndexStateChanged();
2789 return NS_OK;
2790 }
2792 nsresult
2793 CacheFileIOManager::TrashDirectory(nsIFile *aFile)
2794 {
2795 #ifdef PR_LOGGING
2796 nsAutoCString path;
2797 aFile->GetNativePath(path);
2798 #endif
2799 LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", path.get()));
2801 nsresult rv;
2803 MOZ_ASSERT(mIOThread->IsCurrentThread());
2804 MOZ_ASSERT(mCacheDirectory);
2806 // When the directory is empty, it is cheaper to remove it directly instead of
2807 // using the trash mechanism.
2808 bool isEmpty;
2809 rv = IsEmptyDirectory(aFile, &isEmpty);
2810 NS_ENSURE_SUCCESS(rv, rv);
2812 if (isEmpty) {
2813 rv = aFile->Remove(false);
2814 LOG(("CacheFileIOManager::TrashDirectory() - Directory removed [rv=0x%08x]",
2815 rv));
2816 return rv;
2817 }
2819 #ifdef DEBUG
2820 nsCOMPtr<nsIFile> dirCheck;
2821 rv = aFile->GetParent(getter_AddRefs(dirCheck));
2822 NS_ENSURE_SUCCESS(rv, rv);
2824 bool equals = false;
2825 rv = dirCheck->Equals(mCacheDirectory, &equals);
2826 NS_ENSURE_SUCCESS(rv, rv);
2828 MOZ_ASSERT(equals);
2829 #endif
2831 nsCOMPtr<nsIFile> dir, trash;
2832 nsAutoCString leaf;
2834 rv = aFile->Clone(getter_AddRefs(dir));
2835 NS_ENSURE_SUCCESS(rv, rv);
2837 rv = aFile->Clone(getter_AddRefs(trash));
2838 NS_ENSURE_SUCCESS(rv, rv);
2840 srand(static_cast<unsigned>(PR_Now()));
2841 while (true) {
2842 leaf = kTrashDir;
2843 leaf.AppendInt(rand());
2844 rv = trash->SetNativeLeafName(leaf);
2845 NS_ENSURE_SUCCESS(rv, rv);
2847 bool exists;
2848 if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
2849 break;
2850 }
2851 }
2853 LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",
2854 leaf.get()));
2856 rv = dir->MoveToNative(nullptr, leaf);
2857 NS_ENSURE_SUCCESS(rv, rv);
2859 StartRemovingTrash();
2860 return NS_OK;
2861 }
2863 // static
2864 void
2865 CacheFileIOManager::OnTrashTimer(nsITimer *aTimer, void *aClosure)
2866 {
2867 LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer,
2868 aClosure));
2870 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2872 if (!ioMan) {
2873 return;
2874 }
2876 ioMan->mTrashTimer = nullptr;
2877 ioMan->StartRemovingTrash();
2878 }
2880 nsresult
2881 CacheFileIOManager::StartRemovingTrash()
2882 {
2883 LOG(("CacheFileIOManager::StartRemovingTrash()"));
2885 nsresult rv;
2887 MOZ_ASSERT(mIOThread->IsCurrentThread());
2889 if (mShuttingDown) {
2890 return NS_ERROR_NOT_INITIALIZED;
2891 }
2893 if (!mCacheDirectory) {
2894 return NS_ERROR_FILE_INVALID_PATH;
2895 }
2897 if (mTrashTimer) {
2898 LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));
2899 return NS_OK;
2900 }
2902 if (mRemovingTrashDirs) {
2903 LOG(("CacheFileIOManager::StartRemovingTrash() - Trash removing in "
2904 "progress."));
2905 return NS_OK;
2906 }
2908 uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
2909 if (elapsed < kRemoveTrashStartDelay) {
2910 nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
2911 NS_ENSURE_SUCCESS(rv, rv);
2913 nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
2914 MOZ_ASSERT(ioTarget);
2916 rv = timer->SetTarget(ioTarget);
2917 NS_ENSURE_SUCCESS(rv, rv);
2919 rv = timer->InitWithFuncCallback(CacheFileIOManager::OnTrashTimer, nullptr,
2920 kRemoveTrashStartDelay - elapsed,
2921 nsITimer::TYPE_ONE_SHOT);
2922 NS_ENSURE_SUCCESS(rv, rv);
2924 mTrashTimer.swap(timer);
2925 return NS_OK;
2926 }
2928 nsCOMPtr<nsIRunnable> ev;
2929 ev = NS_NewRunnableMethod(this,
2930 &CacheFileIOManager::RemoveTrashInternal);
2932 rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2933 NS_ENSURE_SUCCESS(rv, rv);
2935 mRemovingTrashDirs = true;
2936 return NS_OK;
2937 }
2939 nsresult
2940 CacheFileIOManager::RemoveTrashInternal()
2941 {
2942 LOG(("CacheFileIOManager::RemoveTrashInternal()"));
2944 nsresult rv;
2946 MOZ_ASSERT(mIOThread->IsCurrentThread());
2948 if (mShuttingDown) {
2949 return NS_ERROR_NOT_INITIALIZED;
2950 }
2952 MOZ_ASSERT(!mTrashTimer);
2953 MOZ_ASSERT(mRemovingTrashDirs);
2955 if (!mTreeCreated) {
2956 rv = CreateCacheTree();
2957 if (NS_FAILED(rv)) {
2958 return rv;
2959 }
2960 }
2962 // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
2963 // here and set it again once we dispatch a continuation event. By doing so,
2964 // we don't have to drop the flag on any possible early return.
2965 mRemovingTrashDirs = false;
2967 while (true) {
2968 if (CacheIOThread::YieldAndRerun()) {
2969 LOG(("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
2970 "higher level events."));
2971 mRemovingTrashDirs = true;
2972 return NS_OK;
2973 }
2975 // Find some trash directory
2976 if (!mTrashDir) {
2977 MOZ_ASSERT(!mTrashDirEnumerator);
2979 rv = FindTrashDirToRemove();
2980 if (rv == NS_ERROR_NOT_AVAILABLE) {
2981 LOG(("CacheFileIOManager::RemoveTrashInternal() - No trash directory "
2982 "found."));
2983 return NS_OK;
2984 }
2985 NS_ENSURE_SUCCESS(rv, rv);
2987 nsCOMPtr<nsISimpleEnumerator> enumerator;
2988 rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(enumerator));
2989 if (NS_SUCCEEDED(rv)) {
2990 mTrashDirEnumerator = do_QueryInterface(enumerator, &rv);
2991 NS_ENSURE_SUCCESS(rv, rv);
2992 }
2994 continue; // check elapsed time
2995 }
2997 // We null out mTrashDirEnumerator once we remove all files in the
2998 // directory, so remove the trash directory if we don't have enumerator.
2999 if (!mTrashDirEnumerator) {
3000 rv = mTrashDir->Remove(false);
3001 if (NS_FAILED(rv)) {
3002 // There is no reason why removing an empty directory should fail, but
3003 // if it does, we should continue and try to remove all other trash
3004 // directories.
3005 nsAutoCString leafName;
3006 mTrashDir->GetNativeLeafName(leafName);
3007 mFailedTrashDirs.AppendElement(leafName);
3008 LOG(("CacheFileIOManager::RemoveTrashInternal() - Cannot remove "
3009 "trashdir. [name=%s]", leafName.get()));
3010 }
3012 mTrashDir = nullptr;
3013 continue; // check elapsed time
3014 }
3016 nsCOMPtr<nsIFile> file;
3017 rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file));
3018 if (!file) {
3019 mTrashDirEnumerator->Close();
3020 mTrashDirEnumerator = nullptr;
3021 continue; // check elapsed time
3022 } else {
3023 bool isDir = false;
3024 file->IsDirectory(&isDir);
3025 if (isDir) {
3026 NS_WARNING("Found a directory in a trash directory! It will be removed "
3027 "recursively, but this can block IO thread for a while!");
3028 #ifdef PR_LOGGING
3029 nsAutoCString path;
3030 file->GetNativePath(path);
3031 #endif
3032 LOG(("CacheFileIOManager::RemoveTrashInternal() - Found a directory in a trash "
3033 "directory! It will be removed recursively, but this can block IO "
3034 "thread for a while! [file=%s]", path.get()));
3035 }
3036 file->Remove(isDir);
3037 }
3038 }
3040 NS_NOTREACHED("We should never get here");
3041 return NS_OK;
3042 }
3044 nsresult
3045 CacheFileIOManager::FindTrashDirToRemove()
3046 {
3047 LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
3049 nsresult rv;
3051 // We call this method on the main thread during shutdown when user wants to
3052 // remove all cache files.
3053 MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown);
3055 nsCOMPtr<nsISimpleEnumerator> iter;
3056 rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));
3057 NS_ENSURE_SUCCESS(rv, rv);
3059 bool more;
3060 nsCOMPtr<nsISupports> elem;
3062 while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
3063 rv = iter->GetNext(getter_AddRefs(elem));
3064 if (NS_FAILED(rv)) {
3065 continue;
3066 }
3068 nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
3069 if (!file) {
3070 continue;
3071 }
3073 bool isDir = false;
3074 file->IsDirectory(&isDir);
3075 if (!isDir) {
3076 continue;
3077 }
3079 nsAutoCString leafName;
3080 rv = file->GetNativeLeafName(leafName);
3081 if (NS_FAILED(rv)) {
3082 continue;
3083 }
3085 if (leafName.Length() < strlen(kTrashDir)) {
3086 continue;
3087 }
3089 if (!StringBeginsWith(leafName, NS_LITERAL_CSTRING(kTrashDir))) {
3090 continue;
3091 }
3093 if (mFailedTrashDirs.Contains(leafName)) {
3094 continue;
3095 }
3097 LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",
3098 leafName.get()));
3100 mTrashDir = file;
3101 return NS_OK;
3102 }
3104 // When we're here we've tried to delete all trash directories. Clear
3105 // mFailedTrashDirs so we will try to delete them again when we start removing
3106 // trash directories next time.
3107 mFailedTrashDirs.Clear();
3108 return NS_ERROR_NOT_AVAILABLE;
3109 }
3111 // static
3112 nsresult
3113 CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle,
3114 uint32_t aAppId,
3115 bool aAnonymous,
3116 bool aInBrowser)
3117 {
3118 LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d"
3119 ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser));
3121 nsresult rv;
3122 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
3124 if (aHandle->IsClosed() || !ioMan) {
3125 return NS_ERROR_NOT_INITIALIZED;
3126 }
3128 if (aHandle->IsSpecialFile()) {
3129 return NS_ERROR_UNEXPECTED;
3130 }
3132 nsRefPtr<InitIndexEntryEvent> ev =
3133 new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser);
3134 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
3135 NS_ENSURE_SUCCESS(rv, rv);
3137 return NS_OK;
3138 }
3140 // static
3141 nsresult
3142 CacheFileIOManager::UpdateIndexEntry(CacheFileHandle *aHandle,
3143 const uint32_t *aFrecency,
3144 const uint32_t *aExpirationTime)
3145 {
3146 LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
3147 "expirationTime=%s]", aHandle,
3148 aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
3149 aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : ""));
3151 nsresult rv;
3152 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
3154 if (aHandle->IsClosed() || !ioMan) {
3155 return NS_ERROR_NOT_INITIALIZED;
3156 }
3158 if (aHandle->IsSpecialFile()) {
3159 return NS_ERROR_UNEXPECTED;
3160 }
3162 nsRefPtr<UpdateIndexEntryEvent> ev =
3163 new UpdateIndexEntryEvent(aHandle, aFrecency, aExpirationTime);
3164 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
3165 NS_ENSURE_SUCCESS(rv, rv);
3167 return NS_OK;
3168 }
3170 nsresult
3171 CacheFileIOManager::CreateFile(CacheFileHandle *aHandle)
3172 {
3173 MOZ_ASSERT(!aHandle->mFD);
3174 MOZ_ASSERT(aHandle->mFile);
3176 nsresult rv;
3178 if (aHandle->IsDoomed()) {
3179 nsCOMPtr<nsIFile> file;
3181 rv = GetDoomedFile(getter_AddRefs(file));
3182 NS_ENSURE_SUCCESS(rv, rv);
3184 aHandle->mFile.swap(file);
3185 } else {
3186 bool exists;
3187 if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) {
3188 NS_WARNING("Found a file that should not exist!");
3189 }
3190 }
3192 rv = OpenNSPRHandle(aHandle, true);
3193 NS_ENSURE_SUCCESS(rv, rv);
3195 aHandle->mFileSize = 0;
3196 return NS_OK;
3197 }
3199 // static
3200 void
3201 CacheFileIOManager::HashToStr(const SHA1Sum::Hash *aHash, nsACString &_retval)
3202 {
3203 _retval.Assign("");
3204 const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
3205 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
3206 for (uint32_t i=0 ; i<sizeof(SHA1Sum::Hash) ; i++) {
3207 _retval.Append(hexChars[(*aHash)[i] >> 4]);
3208 _retval.Append(hexChars[(*aHash)[i] & 0xF]);
3209 }
3210 }
3212 // static
3213 nsresult
3214 CacheFileIOManager::StrToHash(const nsACString &aHash, SHA1Sum::Hash *_retval)
3215 {
3216 if (aHash.Length() != 2*sizeof(SHA1Sum::Hash)) {
3217 return NS_ERROR_INVALID_ARG;
3218 }
3220 for (uint32_t i=0 ; i<aHash.Length() ; i++) {
3221 uint8_t value;
3223 if (aHash[i] >= '0' && aHash[i] <= '9') {
3224 value = aHash[i] - '0';
3225 } else if (aHash[i] >= 'A' && aHash[i] <= 'F') {
3226 value = aHash[i] - 'A' + 10;
3227 } else if (aHash[i] >= 'a' && aHash[i] <= 'f') {
3228 value = aHash[i] - 'a' + 10;
3229 } else {
3230 return NS_ERROR_INVALID_ARG;
3231 }
3233 if (i%2 == 0) {
3234 (reinterpret_cast<uint8_t *>(_retval))[i/2] = value << 4;
3235 } else {
3236 (reinterpret_cast<uint8_t *>(_retval))[i/2] += value;
3237 }
3238 }
3240 return NS_OK;
3241 }
3243 nsresult
3244 CacheFileIOManager::GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval)
3245 {
3246 nsresult rv;
3247 nsCOMPtr<nsIFile> file;
3248 rv = mCacheDirectory->Clone(getter_AddRefs(file));
3249 NS_ENSURE_SUCCESS(rv, rv);
3251 rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
3252 NS_ENSURE_SUCCESS(rv, rv);
3254 nsAutoCString leafName;
3255 HashToStr(aHash, leafName);
3257 rv = file->AppendNative(leafName);
3258 NS_ENSURE_SUCCESS(rv, rv);
3260 file.swap(*_retval);
3261 return NS_OK;
3262 }
3264 nsresult
3265 CacheFileIOManager::GetSpecialFile(const nsACString &aKey, nsIFile **_retval)
3266 {
3267 nsresult rv;
3268 nsCOMPtr<nsIFile> file;
3269 rv = mCacheDirectory->Clone(getter_AddRefs(file));
3270 NS_ENSURE_SUCCESS(rv, rv);
3272 rv = file->AppendNative(aKey);
3273 NS_ENSURE_SUCCESS(rv, rv);
3275 file.swap(*_retval);
3276 return NS_OK;
3277 }
3279 nsresult
3280 CacheFileIOManager::GetDoomedFile(nsIFile **_retval)
3281 {
3282 nsresult rv;
3283 nsCOMPtr<nsIFile> file;
3284 rv = mCacheDirectory->Clone(getter_AddRefs(file));
3285 NS_ENSURE_SUCCESS(rv, rv);
3287 rv = file->AppendNative(NS_LITERAL_CSTRING(kDoomedDir));
3288 NS_ENSURE_SUCCESS(rv, rv);
3290 rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf"));
3291 NS_ENSURE_SUCCESS(rv, rv);
3293 srand(static_cast<unsigned>(PR_Now()));
3294 nsAutoCString leafName;
3295 uint32_t iter=0;
3296 while (true) {
3297 iter++;
3298 leafName.AppendInt(rand());
3299 rv = file->SetNativeLeafName(leafName);
3300 NS_ENSURE_SUCCESS(rv, rv);
3302 bool exists;
3303 if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) {
3304 break;
3305 }
3307 leafName.Truncate();
3308 }
3310 // Telemetry::Accumulate(Telemetry::DISK_CACHE_GETDOOMEDFILE_ITERATIONS, iter);
3312 file.swap(*_retval);
3313 return NS_OK;
3314 }
3316 nsresult
3317 CacheFileIOManager::IsEmptyDirectory(nsIFile *aFile, bool *_retval)
3318 {
3319 MOZ_ASSERT(mIOThread->IsCurrentThread());
3321 nsresult rv;
3323 nsCOMPtr<nsISimpleEnumerator> enumerator;
3324 rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator));
3325 NS_ENSURE_SUCCESS(rv, rv);
3327 bool hasMoreElements = false;
3328 rv = enumerator->HasMoreElements(&hasMoreElements);
3329 NS_ENSURE_SUCCESS(rv, rv);
3331 *_retval = !hasMoreElements;
3332 return NS_OK;
3333 }
3335 nsresult
3336 CacheFileIOManager::CheckAndCreateDir(nsIFile *aFile, const char *aDir,
3337 bool aEnsureEmptyDir)
3338 {
3339 nsresult rv;
3341 nsCOMPtr<nsIFile> file;
3342 if (!aDir) {
3343 file = aFile;
3344 } else {
3345 nsAutoCString dir(aDir);
3346 rv = aFile->Clone(getter_AddRefs(file));
3347 NS_ENSURE_SUCCESS(rv, rv);
3348 rv = file->AppendNative(dir);
3349 NS_ENSURE_SUCCESS(rv, rv);
3350 }
3352 bool exists = false;
3353 rv = file->Exists(&exists);
3354 if (NS_SUCCEEDED(rv) && exists) {
3355 bool isDirectory = false;
3356 rv = file->IsDirectory(&isDirectory);
3357 if (NS_FAILED(rv) || !isDirectory) {
3358 // Try to remove the file
3359 rv = file->Remove(false);
3360 if (NS_SUCCEEDED(rv)) {
3361 exists = false;
3362 }
3363 }
3364 NS_ENSURE_SUCCESS(rv, rv);
3365 }
3367 if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) {
3368 bool isEmpty;
3369 rv = IsEmptyDirectory(file, &isEmpty);
3370 NS_ENSURE_SUCCESS(rv, rv);
3372 if (!isEmpty) {
3373 rv = TrashDirectory(file);
3374 NS_ENSURE_SUCCESS(rv, rv);
3376 exists = false;
3377 }
3378 }
3380 if (NS_SUCCEEDED(rv) && !exists) {
3381 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
3382 }
3383 if (NS_FAILED(rv)) {
3384 NS_WARNING("Cannot create directory");
3385 return NS_ERROR_FAILURE;
3386 }
3388 return NS_OK;
3389 }
3391 nsresult
3392 CacheFileIOManager::CreateCacheTree()
3393 {
3394 MOZ_ASSERT(mIOThread->IsCurrentThread());
3395 MOZ_ASSERT(!mTreeCreated);
3397 if (!mCacheDirectory) {
3398 return NS_ERROR_FILE_INVALID_PATH;
3399 }
3401 nsresult rv;
3403 // ensure parent directory exists
3404 nsCOMPtr<nsIFile> parentDir;
3405 rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
3406 NS_ENSURE_SUCCESS(rv, rv);
3407 rv = CheckAndCreateDir(parentDir, nullptr, false);
3408 NS_ENSURE_SUCCESS(rv, rv);
3410 // ensure cache directory exists
3411 rv = CheckAndCreateDir(mCacheDirectory, nullptr, false);
3412 NS_ENSURE_SUCCESS(rv, rv);
3414 // ensure entries directory exists
3415 rv = CheckAndCreateDir(mCacheDirectory, kEntriesDir, false);
3416 NS_ENSURE_SUCCESS(rv, rv);
3418 // ensure doomed directory exists
3419 rv = CheckAndCreateDir(mCacheDirectory, kDoomedDir, true);
3420 NS_ENSURE_SUCCESS(rv, rv);
3422 mTreeCreated = true;
3424 if (!mContextEvictor) {
3425 nsRefPtr<CacheFileContextEvictor> contextEvictor;
3426 contextEvictor = new CacheFileContextEvictor();
3428 // Init() method will try to load unfinished contexts from the disk. Store
3429 // the evictor as a member only when there is some unfinished job.
3430 contextEvictor->Init(mCacheDirectory);
3431 if (contextEvictor->ContextsCount()) {
3432 contextEvictor.swap(mContextEvictor);
3433 }
3434 }
3436 StartRemovingTrash();
3438 return NS_OK;
3439 }
3441 nsresult
3442 CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
3443 {
3444 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3445 MOZ_ASSERT(!aHandle->mFD);
3446 MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
3447 MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
3448 MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
3449 (!aCreate && aHandle->mFileExists));
3451 nsresult rv;
3453 if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
3454 // close handle that hasn't been used for the longest time
3455 rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]);
3456 NS_ENSURE_SUCCESS(rv, rv);
3457 }
3459 if (aCreate) {
3460 rv = aHandle->mFile->OpenNSPRFileDesc(
3461 PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
3462 NS_ENSURE_SUCCESS(rv, rv);
3464 aHandle->mFileExists = true;
3465 } else {
3466 rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
3467 if (NS_ERROR_FILE_NOT_FOUND == rv) {
3468 LOG((" file doesn't exists"));
3469 aHandle->mFileExists = false;
3470 return DoomFileInternal(aHandle);
3471 }
3472 NS_ENSURE_SUCCESS(rv, rv);
3473 }
3475 mHandlesByLastUsed.AppendElement(aHandle);
3476 return NS_OK;
3477 }
3479 void
3480 CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle)
3481 {
3482 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3483 MOZ_ASSERT(aHandle->mFD);
3485 DebugOnly<bool> found;
3486 found = mHandlesByLastUsed.RemoveElement(aHandle);
3487 MOZ_ASSERT(found);
3489 mHandlesByLastUsed.AppendElement(aHandle);
3490 }
3492 nsresult
3493 CacheFileIOManager::SyncRemoveDir(nsIFile *aFile, const char *aDir)
3494 {
3495 nsresult rv;
3496 nsCOMPtr<nsIFile> file;
3498 if (!aDir) {
3499 file = aFile;
3500 } else {
3501 rv = aFile->Clone(getter_AddRefs(file));
3502 if (NS_WARN_IF(NS_FAILED(rv))) {
3503 return rv;
3504 }
3506 rv = file->AppendNative(nsDependentCString(aDir));
3507 if (NS_WARN_IF(NS_FAILED(rv))) {
3508 return rv;
3509 }
3510 }
3512 #ifdef PR_LOGGING
3513 nsAutoCString path;
3514 file->GetNativePath(path);
3515 #endif
3517 LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
3518 path.get()));
3520 rv = file->Remove(true);
3521 if (NS_WARN_IF(NS_FAILED(rv))) {
3522 LOG(("CacheFileIOManager::SyncRemoveDir() - Removing failed! [rv=0x%08x]",
3523 rv));
3524 }
3526 return rv;
3527 }
3529 void
3530 CacheFileIOManager::SyncRemoveAllCacheFiles()
3531 {
3532 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
3534 nsresult rv;
3536 SyncRemoveDir(mCacheDirectory, kEntriesDir);
3537 SyncRemoveDir(mCacheDirectory, kDoomedDir);
3539 // Clear any intermediate state of trash dir enumeration.
3540 mFailedTrashDirs.Clear();
3541 mTrashDir = nullptr;
3543 while (true) {
3544 // FindTrashDirToRemove() fills mTrashDir if there is any trash directory.
3545 rv = FindTrashDirToRemove();
3546 if (rv == NS_ERROR_NOT_AVAILABLE) {
3547 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory "
3548 "found."));
3549 break;
3550 }
3551 if (NS_WARN_IF(NS_FAILED(rv))) {
3552 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - "
3553 "FindTrashDirToRemove() returned an unexpected error. [rv=0x%08x]",
3554 rv));
3555 break;
3556 }
3558 rv = SyncRemoveDir(mTrashDir, nullptr);
3559 if (NS_FAILED(rv)) {
3560 nsAutoCString leafName;
3561 mTrashDir->GetNativeLeafName(leafName);
3562 mFailedTrashDirs.AppendElement(leafName);
3563 }
3564 }
3565 }
3567 // Returns default ("smart") size (in KB) of cache, given available disk space
3568 // (also in KB)
3569 static uint32_t
3570 SmartCacheSize(const uint32_t availKB)
3571 {
3572 uint32_t maxSize = kMaxCacheSizeKB;
3574 if (availKB > 100 * 1024 * 1024) {
3575 return maxSize; // skip computing if we're over 100 GB
3576 }
3578 // Grow/shrink in 10 MB units, deliberately, so that in the common case we
3579 // don't shrink cache and evict items every time we startup (it's important
3580 // that we don't slow down startup benchmarks).
3581 uint32_t sz10MBs = 0;
3582 uint32_t avail10MBs = availKB / (1024*10);
3584 // .5% of space above 25 GB
3585 if (avail10MBs > 2500) {
3586 sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
3587 avail10MBs = 2500;
3588 }
3589 // 1% of space between 7GB -> 25 GB
3590 if (avail10MBs > 700) {
3591 sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
3592 avail10MBs = 700;
3593 }
3594 // 5% of space between 500 MB -> 7 GB
3595 if (avail10MBs > 50) {
3596 sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
3597 avail10MBs = 50;
3598 }
3600 #ifdef ANDROID
3601 // On Android, smaller/older devices may have very little storage and
3602 // device owners may be sensitive to storage footprint: Use a smaller
3603 // percentage of available space and a smaller minimum.
3605 // 20% of space up to 500 MB (10 MB min)
3606 sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
3607 #else
3608 // 40% of space up to 500 MB (50 MB min)
3609 sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
3610 #endif
3612 return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
3613 }
3615 nsresult
3616 CacheFileIOManager::UpdateSmartCacheSize()
3617 {
3618 MOZ_ASSERT(mIOThread->IsCurrentThread());
3620 nsresult rv;
3622 if (!CacheObserver::UseNewCache()) {
3623 return NS_ERROR_NOT_AVAILABLE;
3624 }
3626 if (!CacheObserver::SmartCacheSizeEnabled()) {
3627 return NS_ERROR_NOT_AVAILABLE;
3628 }
3630 // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
3631 static const TimeDuration kUpdateLimit =
3632 TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);
3633 if (!mLastSmartSizeTime.IsNull() &&
3634 (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) {
3635 return NS_OK;
3636 }
3638 // Do not compute smart size when cache size is not reliable.
3639 bool isUpToDate = false;
3640 CacheIndex::IsUpToDate(&isUpToDate);
3641 if (!isUpToDate) {
3642 return NS_ERROR_NOT_AVAILABLE;
3643 }
3645 uint32_t cacheUsage;
3646 rv = CacheIndex::GetCacheSize(&cacheUsage);
3647 if (NS_WARN_IF(NS_FAILED(rv))) {
3648 LOG(("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! "
3649 "[rv=0x%08x]", rv));
3650 return rv;
3651 }
3653 int64_t avail;
3654 rv = mCacheDirectory->GetDiskSpaceAvailable(&avail);
3655 if (NS_WARN_IF(NS_FAILED(rv))) {
3656 // Do not change smart size.
3657 LOG(("CacheFileIOManager::UpdateSmartCacheSize() - GetDiskSpaceAvailable() "
3658 "failed! [rv=0x%08x]", rv));
3659 return rv;
3660 }
3662 mLastSmartSizeTime = TimeStamp::NowLoRes();
3664 uint32_t smartSize = SmartCacheSize(static_cast<uint32_t>(avail / 1024) +
3665 cacheUsage);
3667 if (smartSize == (CacheObserver::DiskCacheCapacity() >> 10)) {
3668 // Smart size has not changed.
3669 return NS_OK;
3670 }
3672 CacheObserver::SetDiskCacheCapacity(smartSize << 10);
3674 return NS_OK;
3675 }
3677 // Memory reporting
3679 namespace { // anon
3681 // A helper class that dispatches and waits for an event that gets result of
3682 // CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread
3683 // to safely get handles memory report.
3684 // We must do this, since the handle list is only accessed and managed w/o
3685 // locking on the I/O thread. That is by design.
3686 class SizeOfHandlesRunnable : public nsRunnable
3687 {
3688 public:
3689 SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf,
3690 CacheFileHandles const &handles,
3691 nsTArray<CacheFileHandle *> const &specialHandles)
3692 : mMonitor("SizeOfHandlesRunnable.mMonitor")
3693 , mMallocSizeOf(mallocSizeOf)
3694 , mHandles(handles)
3695 , mSpecialHandles(specialHandles)
3696 {
3697 }
3699 size_t Get(CacheIOThread* thread)
3700 {
3701 nsCOMPtr<nsIEventTarget> target = thread->Target();
3702 if (!target) {
3703 NS_ERROR("If we have the I/O thread we also must have the I/O target");
3704 return 0;
3705 }
3707 mozilla::MonitorAutoLock mon(mMonitor);
3708 nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
3709 if (NS_FAILED(rv)) {
3710 NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");
3711 return 0;
3712 }
3714 mon.Wait();
3715 return mSize;
3716 }
3718 NS_IMETHOD Run()
3719 {
3720 mozilla::MonitorAutoLock mon(mMonitor);
3721 // Excluding this since the object itself is a member of CacheFileIOManager
3722 // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.
3723 mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf);
3724 for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) {
3725 mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf);
3726 }
3728 mon.Notify();
3729 return NS_OK;
3730 }
3732 private:
3733 mozilla::Monitor mMonitor;
3734 mozilla::MallocSizeOf mMallocSizeOf;
3735 CacheFileHandles const &mHandles;
3736 nsTArray<CacheFileHandle *> const &mSpecialHandles;
3737 size_t mSize;
3738 };
3740 } // anon
3742 size_t
3743 CacheFileIOManager::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
3744 {
3745 size_t n = 0;
3746 nsCOMPtr<nsISizeOf> sizeOf;
3748 if (mIOThread) {
3749 n += mIOThread->SizeOfIncludingThis(mallocSizeOf);
3751 // mHandles and mSpecialHandles must be accessed only on the I/O thread,
3752 // must sync dispatch.
3753 nsRefPtr<SizeOfHandlesRunnable> sizeOfHandlesRunnable =
3754 new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles);
3755 n += sizeOfHandlesRunnable->Get(mIOThread);
3756 }
3758 // mHandlesByLastUsed just refers handles reported by mHandles.
3760 sizeOf = do_QueryInterface(mCacheDirectory);
3761 if (sizeOf)
3762 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3764 sizeOf = do_QueryInterface(mMetadataWritesTimer);
3765 if (sizeOf)
3766 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3768 sizeOf = do_QueryInterface(mTrashTimer);
3769 if (sizeOf)
3770 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3772 sizeOf = do_QueryInterface(mTrashDir);
3773 if (sizeOf)
3774 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3776 for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) {
3777 n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
3778 }
3780 return n;
3781 }
3783 // static
3784 size_t
3785 CacheFileIOManager::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
3786 {
3787 if (!gInstance)
3788 return 0;
3790 return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
3791 }
3793 // static
3794 size_t
3795 CacheFileIOManager::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
3796 {
3797 return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
3798 }
3800 } // net
3801 } // mozilla