Bumping manifests a=b2g-bump
[gecko.git] / netwerk / cache2 / CacheFileIOManager.cpp
blob353caf2b3d3f9c9f1e240aa3d6c48a65529e3d21
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 "nsICacheStorageVisitor.h"
23 #include "nsISizeOf.h"
24 #include "mozilla/Telemetry.h"
25 #include "mozilla/DebugOnly.h"
26 #include "mozilla/Services.h"
27 #include "nsDirectoryServiceUtils.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "private/pprio.h"
30 #include "mozilla/VisualEventTracer.h"
31 #include "mozilla/Preferences.h"
32 #include "nsNetUtil.h"
34 // include files for ftruncate (or equivalent)
35 #if defined(XP_UNIX)
36 #include <unistd.h>
37 #elif defined(XP_WIN)
38 #include <windows.h>
39 #undef CreateFile
40 #undef CREATE_NEW
41 #else
42 // XXX add necessary include file for ftruncate (or equivalent)
43 #endif
46 namespace mozilla {
47 namespace net {
49 #define kOpenHandlesLimit 64
50 #define kMetadataWriteDelay 5000
51 #define kRemoveTrashStartDelay 60000 // in milliseconds
52 #define kSmartSizeUpdateInterval 60000 // in milliseconds
54 #ifdef ANDROID
55 const uint32_t kMaxCacheSizeKB = 200*1024; // 200 MB
56 #else
57 const uint32_t kMaxCacheSizeKB = 350*1024; // 350 MB
58 #endif
60 bool
61 CacheFileHandle::DispatchRelease()
63 if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
64 return false;
67 nsCOMPtr<nsIEventTarget> ioTarget = CacheFileIOManager::IOTarget();
68 if (!ioTarget) {
69 return false;
72 nsRefPtr<nsRunnableMethod<CacheFileHandle, MozExternalRefCountType, false> > event =
73 NS_NewNonOwningRunnableMethod(this, &CacheFileHandle::Release);
74 nsresult rv = ioTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
75 if (NS_FAILED(rv)) {
76 return false;
79 return true;
82 NS_IMPL_ADDREF(CacheFileHandle)
83 NS_IMETHODIMP_(MozExternalRefCountType)
84 CacheFileHandle::Release()
86 nsrefcnt count = mRefCnt - 1;
87 if (DispatchRelease()) {
88 // Redispatched to the IO thread.
89 return count;
92 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
94 LOG(("CacheFileHandle::Release() [this=%p, refcnt=%d]", this, mRefCnt.get()));
95 NS_PRECONDITION(0 != mRefCnt, "dup release");
96 count = --mRefCnt;
97 NS_LOG_RELEASE(this, count, "CacheFileHandle");
99 if (0 == count) {
100 mRefCnt = 1;
101 delete (this);
102 return 0;
105 return count;
108 NS_INTERFACE_MAP_BEGIN(CacheFileHandle)
109 NS_INTERFACE_MAP_ENTRY(nsISupports)
110 NS_INTERFACE_MAP_END_THREADSAFE
112 CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash *aHash, bool aPriority)
113 : mHash(aHash)
114 , mIsDoomed(false)
115 , mPriority(aPriority)
116 , mClosed(false)
117 , mSpecialFile(false)
118 , mInvalid(false)
119 , mFileExists(false)
120 , mFileSize(-1)
121 , mFD(nullptr)
123 LOG(("CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]"
124 , this, LOGSHA1(aHash)));
127 CacheFileHandle::CacheFileHandle(const nsACString &aKey, bool aPriority)
128 : mHash(nullptr)
129 , mIsDoomed(false)
130 , mPriority(aPriority)
131 , mClosed(false)
132 , mSpecialFile(true)
133 , mInvalid(false)
134 , mFileExists(false)
135 , mFileSize(-1)
136 , mFD(nullptr)
137 , mKey(aKey)
139 LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
140 PromiseFlatCString(aKey).get()));
143 CacheFileHandle::~CacheFileHandle()
145 LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
147 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
149 nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
150 if (!IsClosed() && ioMan) {
151 ioMan->CloseHandleInternal(this);
155 void
156 CacheFileHandle::Log()
158 nsAutoCString leafName;
159 if (mFile) {
160 mFile->GetNativeLeafName(leafName);
163 if (mSpecialFile) {
164 LOG(("CacheFileHandle::Log() - special file [this=%p, isDoomed=%d, "
165 "priority=%d, closed=%d, invalid=%d, fileExists=%d, fileSize=%lld, "
166 "leafName=%s, key=%s]", this, mIsDoomed, mPriority, mClosed, mInvalid,
167 mFileExists, mFileSize, leafName.get(), mKey.get()));
168 } else {
169 LOG(("CacheFileHandle::Log() - entry file [this=%p, hash=%08x%08x%08x%08x"
170 "%08x, isDoomed=%d, priority=%d, closed=%d, invalid=%d, fileExists=%d,"
171 " fileSize=%lld, leafName=%s, key=%s]", this, LOGSHA1(mHash),
172 mIsDoomed, mPriority, mClosed, mInvalid, mFileExists, mFileSize,
173 leafName.get(), mKey.get()));
177 uint32_t
178 CacheFileHandle::FileSizeInK() const
180 MOZ_ASSERT(mFileSize != -1);
181 uint64_t size64 = mFileSize;
183 size64 += 0x3FF;
184 size64 >>= 10;
186 uint32_t size;
187 if (size64 >> 32) {
188 NS_WARNING("CacheFileHandle::FileSizeInK() - FileSize is too large, "
189 "truncating to PR_UINT32_MAX");
190 size = PR_UINT32_MAX;
191 } else {
192 size = static_cast<uint32_t>(size64);
195 return size;
198 // Memory reporting
200 size_t
201 CacheFileHandle::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
203 size_t n = 0;
204 nsCOMPtr<nsISizeOf> sizeOf;
206 sizeOf = do_QueryInterface(mFile);
207 if (sizeOf) {
208 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
211 n += mallocSizeOf(mFD);
212 n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
213 return n;
216 size_t
217 CacheFileHandle::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
219 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
222 /******************************************************************************
223 * CacheFileHandles::HandleHashKey
224 *****************************************************************************/
226 void
227 CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle* aHandle)
229 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
231 mHandles.InsertElementAt(0, aHandle);
234 void
235 CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle* aHandle)
237 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
239 DebugOnly<bool> found;
240 found = mHandles.RemoveElement(aHandle);
241 MOZ_ASSERT(found);
244 already_AddRefed<CacheFileHandle>
245 CacheFileHandles::HandleHashKey::GetNewestHandle()
247 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
249 nsRefPtr<CacheFileHandle> handle;
250 if (mHandles.Length()) {
251 handle = mHandles[0];
254 return handle.forget();
257 void
258 CacheFileHandles::HandleHashKey::GetHandles(nsTArray<nsRefPtr<CacheFileHandle> > &aResult)
260 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
262 for (uint32_t i = 0; i < mHandles.Length(); ++i) {
263 CacheFileHandle* handle = mHandles[i];
264 aResult.AppendElement(handle);
268 #ifdef DEBUG
270 void
271 CacheFileHandles::HandleHashKey::AssertHandlesState()
273 for (uint32_t i = 0; i < mHandles.Length(); ++i) {
274 CacheFileHandle* handle = mHandles[i];
275 MOZ_ASSERT(handle->IsDoomed());
279 #endif
281 size_t
282 CacheFileHandles::HandleHashKey::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
284 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
286 size_t n = 0;
287 n += mallocSizeOf(mHash);
288 for (uint32_t i = 0; i < mHandles.Length(); ++i) {
289 n += mHandles[i]->SizeOfIncludingThis(mallocSizeOf);
292 return n;
295 /******************************************************************************
296 * CacheFileHandles
297 *****************************************************************************/
299 CacheFileHandles::CacheFileHandles()
301 LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
302 MOZ_COUNT_CTOR(CacheFileHandles);
305 CacheFileHandles::~CacheFileHandles()
307 LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
308 MOZ_COUNT_DTOR(CacheFileHandles);
311 nsresult
312 CacheFileHandles::GetHandle(const SHA1Sum::Hash *aHash,
313 bool aReturnDoomed,
314 CacheFileHandle **_retval)
316 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
317 MOZ_ASSERT(aHash);
319 #ifdef DEBUG_HANDLES
320 LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
321 LOGSHA1(aHash)));
322 #endif
324 // find hash entry for key
325 HandleHashKey *entry = mTable.GetEntry(*aHash);
326 if (!entry) {
327 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
328 "no handle entries found", LOGSHA1(aHash)));
329 return NS_ERROR_NOT_AVAILABLE;
332 #ifdef DEBUG_HANDLES
333 Log(entry);
334 #endif
336 // Check if the entry is doomed
337 nsRefPtr<CacheFileHandle> handle = entry->GetNewestHandle();
338 if (!handle) {
339 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
340 "no handle found %p, entry %p", LOGSHA1(aHash), handle.get(), entry));
341 return NS_ERROR_NOT_AVAILABLE;
344 if (handle->IsDoomed()) {
345 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
346 "found doomed handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry));
348 // If the consumer doesn't want doomed handles, exit with NOT_AVAIL.
349 if (!aReturnDoomed) {
350 return NS_ERROR_NOT_AVAILABLE;
352 } else {
353 LOG(("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
354 "found handle %p, entry %p", LOGSHA1(aHash), handle.get(), entry));
357 handle.forget(_retval);
358 return NS_OK;
362 nsresult
363 CacheFileHandles::NewHandle(const SHA1Sum::Hash *aHash,
364 bool aPriority,
365 CacheFileHandle **_retval)
367 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
368 MOZ_ASSERT(aHash);
370 #ifdef DEBUG_HANDLES
371 LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]", LOGSHA1(aHash)));
372 #endif
374 // find hash entry for key
375 HandleHashKey *entry = mTable.PutEntry(*aHash);
377 #ifdef DEBUG_HANDLES
378 Log(entry);
379 #endif
381 #ifdef DEBUG
382 entry->AssertHandlesState();
383 #endif
385 nsRefPtr<CacheFileHandle> handle = new CacheFileHandle(entry->Hash(), aPriority);
386 entry->AddHandle(handle);
388 LOG(("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
389 "created new handle %p, entry=%p", LOGSHA1(aHash), handle.get(), entry));
391 handle.forget(_retval);
392 return NS_OK;
395 void
396 CacheFileHandles::RemoveHandle(CacheFileHandle *aHandle)
398 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
399 MOZ_ASSERT(aHandle);
401 if (!aHandle) {
402 return;
405 #ifdef DEBUG_HANDLES
406 LOG(("CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]"
407 , aHandle, LOGSHA1(aHandle->Hash())));
408 #endif
410 // find hash entry for key
411 HandleHashKey *entry = mTable.GetEntry(*aHandle->Hash());
412 if (!entry) {
413 MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
414 "Should find entry when removing a handle before shutdown");
416 LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
417 "no entries found", LOGSHA1(aHandle->Hash())));
418 return;
421 #ifdef DEBUG_HANDLES
422 Log(entry);
423 #endif
425 LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
426 "removing handle %p", LOGSHA1(entry->Hash()), aHandle));
427 entry->RemoveHandle(aHandle);
429 if (entry->IsEmpty()) {
430 LOG(("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
431 "list is empty, removing entry %p", LOGSHA1(entry->Hash()), entry));
432 mTable.RemoveEntry(*entry->Hash());
436 static PLDHashOperator
437 GetAllHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure)
439 nsTArray<nsRefPtr<CacheFileHandle> > *array =
440 static_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(aClosure);
442 aEntry->GetHandles(*array);
443 return PL_DHASH_NEXT;
446 void
447 CacheFileHandles::GetAllHandles(nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
449 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
450 mTable.EnumerateEntries(&GetAllHandlesEnum, _retval);
453 static PLDHashOperator
454 GetActiveHandlesEnum(CacheFileHandles::HandleHashKey* aEntry, void *aClosure)
456 nsTArray<nsRefPtr<CacheFileHandle> > *array =
457 static_cast<nsTArray<nsRefPtr<CacheFileHandle> > *>(aClosure);
459 nsRefPtr<CacheFileHandle> handle = aEntry->GetNewestHandle();
460 MOZ_ASSERT(handle);
462 if (!handle->IsDoomed()) {
463 array->AppendElement(handle);
466 return PL_DHASH_NEXT;
469 void
470 CacheFileHandles::GetActiveHandles(
471 nsTArray<nsRefPtr<CacheFileHandle> > *_retval)
473 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
474 mTable.EnumerateEntries(&GetActiveHandlesEnum, _retval);
477 void
478 CacheFileHandles::ClearAll()
480 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
481 mTable.Clear();
484 uint32_t
485 CacheFileHandles::HandleCount()
487 return mTable.Count();
490 #ifdef DEBUG_HANDLES
491 void
492 CacheFileHandles::Log(CacheFileHandlesEntry *entry)
494 LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry));
496 nsTArray<nsRefPtr<CacheFileHandle> > array;
497 aEntry->GetHandles(array);
499 for (uint32_t i = 0; i < array.Length(); ++i) {
500 CacheFileHandle *handle = array[i];
501 handle->Log();
504 LOG(("CacheFileHandles::Log() END [entry=%p]", entry));
506 #endif
508 // Memory reporting
510 size_t
511 CacheFileHandles::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
513 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
515 return mTable.SizeOfExcludingThis(mallocSizeOf);
518 // Events
520 class ShutdownEvent : public nsRunnable {
521 public:
522 ShutdownEvent(mozilla::Mutex *aLock, mozilla::CondVar *aCondVar)
523 : mLock(aLock)
524 , mCondVar(aCondVar)
526 MOZ_COUNT_CTOR(ShutdownEvent);
529 protected:
530 ~ShutdownEvent()
532 MOZ_COUNT_DTOR(ShutdownEvent);
535 public:
536 NS_IMETHOD Run()
538 MutexAutoLock lock(*mLock);
540 CacheFileIOManager::gInstance->ShutdownInternal();
542 mCondVar->Notify();
543 return NS_OK;
546 protected:
547 mozilla::Mutex *mLock;
548 mozilla::CondVar *mCondVar;
551 class OpenFileEvent : public nsRunnable {
552 public:
553 OpenFileEvent(const nsACString &aKey, uint32_t aFlags,
554 CacheFileIOListener *aCallback)
555 : mFlags(aFlags)
556 , mCallback(aCallback)
557 , mKey(aKey)
559 MOZ_COUNT_CTOR(OpenFileEvent);
560 mIOMan = CacheFileIOManager::gInstance;
562 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aKey.BeginReading());
563 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::open-background");
566 protected:
567 ~OpenFileEvent()
569 MOZ_COUNT_DTOR(OpenFileEvent);
572 public:
573 NS_IMETHOD Run()
575 nsresult rv = NS_OK;
577 if (!(mFlags & CacheFileIOManager::SPECIAL_FILE)) {
578 SHA1Sum sum;
579 sum.update(mKey.BeginReading(), mKey.Length());
580 sum.finish(mHash);
583 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-background");
584 if (!mIOMan) {
585 rv = NS_ERROR_NOT_INITIALIZED;
586 } else {
587 if (mFlags & CacheFileIOManager::SPECIAL_FILE) {
588 rv = mIOMan->OpenSpecialFileInternal(mKey, mFlags,
589 getter_AddRefs(mHandle));
590 } else {
591 rv = mIOMan->OpenFileInternal(&mHash, mKey, mFlags,
592 getter_AddRefs(mHandle));
594 mIOMan = nullptr;
595 if (mHandle) {
596 MOZ_EVENT_TRACER_NAME_OBJECT(mHandle.get(), mKey.get());
597 if (mHandle->Key().IsEmpty()) {
598 mHandle->Key() = mKey;
602 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-background");
604 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::open-result");
605 mCallback->OnFileOpened(mHandle, rv);
606 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::open-result");
608 return NS_OK;
611 protected:
612 SHA1Sum::Hash mHash;
613 uint32_t mFlags;
614 nsCOMPtr<CacheFileIOListener> mCallback;
615 nsRefPtr<CacheFileIOManager> mIOMan;
616 nsRefPtr<CacheFileHandle> mHandle;
617 nsCString mKey;
620 class ReadEvent : public nsRunnable {
621 public:
622 ReadEvent(CacheFileHandle *aHandle, int64_t aOffset, char *aBuf,
623 int32_t aCount, CacheFileIOListener *aCallback)
624 : mHandle(aHandle)
625 , mOffset(aOffset)
626 , mBuf(aBuf)
627 , mCount(aCount)
628 , mCallback(aCallback)
630 MOZ_COUNT_CTOR(ReadEvent);
632 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
633 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::read-background");
636 protected:
637 ~ReadEvent()
639 MOZ_COUNT_DTOR(ReadEvent);
642 public:
643 NS_IMETHOD Run()
645 nsresult rv;
647 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-background");
648 if (mHandle->IsClosed()) {
649 rv = NS_ERROR_NOT_INITIALIZED;
650 } else {
651 rv = CacheFileIOManager::gInstance->ReadInternal(
652 mHandle, mOffset, mBuf, mCount);
654 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-background");
656 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::read-result");
657 mCallback->OnDataRead(mHandle, mBuf, rv);
658 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::read-result");
660 return NS_OK;
663 protected:
664 nsRefPtr<CacheFileHandle> mHandle;
665 int64_t mOffset;
666 char *mBuf;
667 int32_t mCount;
668 nsCOMPtr<CacheFileIOListener> mCallback;
671 class WriteEvent : public nsRunnable {
672 public:
673 WriteEvent(CacheFileHandle *aHandle, int64_t aOffset, const char *aBuf,
674 int32_t aCount, bool aValidate, CacheFileIOListener *aCallback)
675 : mHandle(aHandle)
676 , mOffset(aOffset)
677 , mBuf(aBuf)
678 , mCount(aCount)
679 , mValidate(aValidate)
680 , mCallback(aCallback)
682 MOZ_COUNT_CTOR(WriteEvent);
684 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
685 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::write-background");
688 protected:
689 ~WriteEvent()
691 MOZ_COUNT_DTOR(WriteEvent);
693 if (!mCallback && mBuf) {
694 free(const_cast<char *>(mBuf));
698 public:
699 NS_IMETHOD Run()
701 nsresult rv;
703 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-background");
704 if (mHandle->IsClosed()) {
705 rv = NS_ERROR_NOT_INITIALIZED;
706 } else {
707 rv = CacheFileIOManager::gInstance->WriteInternal(
708 mHandle, mOffset, mBuf, mCount, mValidate);
710 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-background");
712 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::write-result");
713 if (mCallback) {
714 mCallback->OnDataWritten(mHandle, mBuf, rv);
715 } else {
716 free(const_cast<char *>(mBuf));
717 mBuf = nullptr;
719 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::write-result");
721 return NS_OK;
724 protected:
725 nsRefPtr<CacheFileHandle> mHandle;
726 int64_t mOffset;
727 const char *mBuf;
728 int32_t mCount;
729 bool mValidate;
730 nsCOMPtr<CacheFileIOListener> mCallback;
733 class DoomFileEvent : public nsRunnable {
734 public:
735 DoomFileEvent(CacheFileHandle *aHandle,
736 CacheFileIOListener *aCallback)
737 : mCallback(aCallback)
738 , mHandle(aHandle)
740 MOZ_COUNT_CTOR(DoomFileEvent);
742 MOZ_EVENT_TRACER_NAME_OBJECT(static_cast<nsIRunnable*>(this), aHandle->Key().get());
743 MOZ_EVENT_TRACER_WAIT(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
746 protected:
747 ~DoomFileEvent()
749 MOZ_COUNT_DTOR(DoomFileEvent);
752 public:
753 NS_IMETHOD Run()
755 nsresult rv;
757 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
758 if (mHandle->IsClosed()) {
759 rv = NS_ERROR_NOT_INITIALIZED;
760 } else {
761 rv = CacheFileIOManager::gInstance->DoomFileInternal(mHandle);
763 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-background");
765 MOZ_EVENT_TRACER_EXEC(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
766 if (mCallback) {
767 mCallback->OnFileDoomed(mHandle, rv);
769 MOZ_EVENT_TRACER_DONE(static_cast<nsIRunnable*>(this), "net::cache::doom-result");
771 return NS_OK;
774 protected:
775 nsCOMPtr<CacheFileIOListener> mCallback;
776 nsCOMPtr<nsIEventTarget> mTarget;
777 nsRefPtr<CacheFileHandle> mHandle;
780 class DoomFileByKeyEvent : public nsRunnable {
781 public:
782 DoomFileByKeyEvent(const nsACString &aKey,
783 CacheFileIOListener *aCallback)
784 : mCallback(aCallback)
786 MOZ_COUNT_CTOR(DoomFileByKeyEvent);
788 SHA1Sum sum;
789 sum.update(aKey.BeginReading(), aKey.Length());
790 sum.finish(mHash);
792 mIOMan = CacheFileIOManager::gInstance;
795 protected:
796 ~DoomFileByKeyEvent()
798 MOZ_COUNT_DTOR(DoomFileByKeyEvent);
801 public:
802 NS_IMETHOD Run()
804 nsresult rv;
806 if (!mIOMan) {
807 rv = NS_ERROR_NOT_INITIALIZED;
808 } else {
809 rv = mIOMan->DoomFileByKeyInternal(&mHash, false);
810 mIOMan = nullptr;
813 if (mCallback) {
814 mCallback->OnFileDoomed(nullptr, rv);
817 return NS_OK;
820 protected:
821 SHA1Sum::Hash mHash;
822 nsCOMPtr<CacheFileIOListener> mCallback;
823 nsRefPtr<CacheFileIOManager> mIOMan;
826 class ReleaseNSPRHandleEvent : public nsRunnable {
827 public:
828 explicit ReleaseNSPRHandleEvent(CacheFileHandle *aHandle)
829 : mHandle(aHandle)
831 MOZ_COUNT_CTOR(ReleaseNSPRHandleEvent);
834 protected:
835 ~ReleaseNSPRHandleEvent()
837 MOZ_COUNT_DTOR(ReleaseNSPRHandleEvent);
840 public:
841 NS_IMETHOD Run()
843 if (mHandle->mFD && !mHandle->IsClosed()) {
844 CacheFileIOManager::gInstance->ReleaseNSPRHandleInternal(mHandle);
847 return NS_OK;
850 protected:
851 nsRefPtr<CacheFileHandle> mHandle;
854 class TruncateSeekSetEOFEvent : public nsRunnable {
855 public:
856 TruncateSeekSetEOFEvent(CacheFileHandle *aHandle, int64_t aTruncatePos,
857 int64_t aEOFPos, CacheFileIOListener *aCallback)
858 : mHandle(aHandle)
859 , mTruncatePos(aTruncatePos)
860 , mEOFPos(aEOFPos)
861 , mCallback(aCallback)
863 MOZ_COUNT_CTOR(TruncateSeekSetEOFEvent);
866 protected:
867 ~TruncateSeekSetEOFEvent()
869 MOZ_COUNT_DTOR(TruncateSeekSetEOFEvent);
872 public:
873 NS_IMETHOD Run()
875 nsresult rv;
877 if (mHandle->IsClosed()) {
878 rv = NS_ERROR_NOT_INITIALIZED;
879 } else {
880 rv = CacheFileIOManager::gInstance->TruncateSeekSetEOFInternal(
881 mHandle, mTruncatePos, mEOFPos);
884 if (mCallback) {
885 mCallback->OnEOFSet(mHandle, rv);
888 return NS_OK;
891 protected:
892 nsRefPtr<CacheFileHandle> mHandle;
893 int64_t mTruncatePos;
894 int64_t mEOFPos;
895 nsCOMPtr<CacheFileIOListener> mCallback;
898 class RenameFileEvent : public nsRunnable {
899 public:
900 RenameFileEvent(CacheFileHandle *aHandle, const nsACString &aNewName,
901 CacheFileIOListener *aCallback)
902 : mHandle(aHandle)
903 , mNewName(aNewName)
904 , mCallback(aCallback)
906 MOZ_COUNT_CTOR(RenameFileEvent);
909 protected:
910 ~RenameFileEvent()
912 MOZ_COUNT_DTOR(RenameFileEvent);
915 public:
916 NS_IMETHOD Run()
918 nsresult rv;
920 if (mHandle->IsClosed()) {
921 rv = NS_ERROR_NOT_INITIALIZED;
922 } else {
923 rv = CacheFileIOManager::gInstance->RenameFileInternal(mHandle,
924 mNewName);
927 if (mCallback) {
928 mCallback->OnFileRenamed(mHandle, rv);
931 return NS_OK;
934 protected:
935 nsRefPtr<CacheFileHandle> mHandle;
936 nsCString mNewName;
937 nsCOMPtr<CacheFileIOListener> mCallback;
940 class InitIndexEntryEvent : public nsRunnable {
941 public:
942 InitIndexEntryEvent(CacheFileHandle *aHandle, uint32_t aAppId,
943 bool aAnonymous, bool aInBrowser)
944 : mHandle(aHandle)
945 , mAppId(aAppId)
946 , mAnonymous(aAnonymous)
947 , mInBrowser(aInBrowser)
949 MOZ_COUNT_CTOR(InitIndexEntryEvent);
952 protected:
953 ~InitIndexEntryEvent()
955 MOZ_COUNT_DTOR(InitIndexEntryEvent);
958 public:
959 NS_IMETHOD Run()
961 if (mHandle->IsClosed() || mHandle->IsDoomed()) {
962 return NS_OK;
965 CacheIndex::InitEntry(mHandle->Hash(), mAppId, mAnonymous, mInBrowser);
967 // We cannot set the filesize before we init the entry. If we're opening
968 // an existing entry file, frecency and expiration time will be set after
969 // parsing the entry file, but we must set the filesize here since nobody is
970 // going to set it if there is no write to the file.
971 uint32_t sizeInK = mHandle->FileSizeInK();
972 CacheIndex::UpdateEntry(mHandle->Hash(), nullptr, nullptr, &sizeInK);
974 return NS_OK;
977 protected:
978 nsRefPtr<CacheFileHandle> mHandle;
979 uint32_t mAppId;
980 bool mAnonymous;
981 bool mInBrowser;
984 class UpdateIndexEntryEvent : public nsRunnable {
985 public:
986 UpdateIndexEntryEvent(CacheFileHandle *aHandle, const uint32_t *aFrecency,
987 const uint32_t *aExpirationTime)
988 : mHandle(aHandle)
989 , mHasFrecency(false)
990 , mHasExpirationTime(false)
992 MOZ_COUNT_CTOR(UpdateIndexEntryEvent);
993 if (aFrecency) {
994 mHasFrecency = true;
995 mFrecency = *aFrecency;
997 if (aExpirationTime) {
998 mHasExpirationTime = true;
999 mExpirationTime = *aExpirationTime;
1003 protected:
1004 ~UpdateIndexEntryEvent()
1006 MOZ_COUNT_DTOR(UpdateIndexEntryEvent);
1009 public:
1010 NS_IMETHOD Run()
1012 if (mHandle->IsClosed() || mHandle->IsDoomed()) {
1013 return NS_OK;
1016 CacheIndex::UpdateEntry(mHandle->Hash(),
1017 mHasFrecency ? &mFrecency : nullptr,
1018 mHasExpirationTime ? &mExpirationTime : nullptr,
1019 nullptr);
1020 return NS_OK;
1023 protected:
1024 nsRefPtr<CacheFileHandle> mHandle;
1025 bool mHasFrecency;
1026 bool mHasExpirationTime;
1027 uint32_t mFrecency;
1028 uint32_t mExpirationTime;
1031 class MetadataWriteScheduleEvent : public nsRunnable
1033 public:
1034 enum EMode {
1035 SCHEDULE,
1036 UNSCHEDULE,
1037 SHUTDOWN
1038 } mMode;
1040 nsRefPtr<CacheFile> mFile;
1041 nsRefPtr<CacheFileIOManager> mIOMan;
1043 MetadataWriteScheduleEvent(CacheFileIOManager * aManager,
1044 CacheFile * aFile,
1045 EMode aMode)
1046 : mMode(aMode)
1047 , mFile(aFile)
1048 , mIOMan(aManager)
1051 virtual ~MetadataWriteScheduleEvent() { }
1053 NS_IMETHOD Run()
1055 nsRefPtr<CacheFileIOManager> ioMan = CacheFileIOManager::gInstance;
1056 if (!ioMan) {
1057 NS_WARNING("CacheFileIOManager already gone in MetadataWriteScheduleEvent::Run()");
1058 return NS_OK;
1061 switch (mMode)
1063 case SCHEDULE:
1064 ioMan->ScheduleMetadataWriteInternal(mFile);
1065 break;
1066 case UNSCHEDULE:
1067 ioMan->UnscheduleMetadataWriteInternal(mFile);
1068 break;
1069 case SHUTDOWN:
1070 ioMan->ShutdownMetadataWriteSchedulingInternal();
1071 break;
1073 return NS_OK;
1077 CacheFileIOManager * CacheFileIOManager::gInstance = nullptr;
1079 NS_IMPL_ISUPPORTS(CacheFileIOManager, nsITimerCallback)
1081 CacheFileIOManager::CacheFileIOManager()
1082 : mShuttingDown(false)
1083 , mTreeCreated(false)
1084 , mOverLimitEvicting(false)
1085 , mRemovingTrashDirs(false)
1087 LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
1088 MOZ_COUNT_CTOR(CacheFileIOManager);
1089 MOZ_ASSERT(!gInstance, "multiple CacheFileIOManager instances!");
1092 CacheFileIOManager::~CacheFileIOManager()
1094 LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
1095 MOZ_COUNT_DTOR(CacheFileIOManager);
1098 // static
1099 nsresult
1100 CacheFileIOManager::Init()
1102 LOG(("CacheFileIOManager::Init()"));
1104 MOZ_ASSERT(NS_IsMainThread());
1106 if (gInstance) {
1107 return NS_ERROR_ALREADY_INITIALIZED;
1110 nsRefPtr<CacheFileIOManager> ioMan = new CacheFileIOManager();
1112 nsresult rv = ioMan->InitInternal();
1113 NS_ENSURE_SUCCESS(rv, rv);
1115 ioMan.swap(gInstance);
1116 return NS_OK;
1119 nsresult
1120 CacheFileIOManager::InitInternal()
1122 nsresult rv;
1124 mIOThread = new CacheIOThread();
1126 rv = mIOThread->Init();
1127 MOZ_ASSERT(NS_SUCCEEDED(rv), "Can't create background thread");
1128 NS_ENSURE_SUCCESS(rv, rv);
1130 mStartTime = TimeStamp::NowLoRes();
1132 return NS_OK;
1135 // static
1136 nsresult
1137 CacheFileIOManager::Shutdown()
1139 LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance));
1141 MOZ_ASSERT(NS_IsMainThread());
1143 if (!gInstance) {
1144 return NS_ERROR_NOT_INITIALIZED;
1147 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2> shutdownTimer;
1149 CacheIndex::PreShutdown();
1151 ShutdownMetadataWriteScheduling();
1154 mozilla::Mutex lock("CacheFileIOManager::Shutdown() lock");
1155 mozilla::CondVar condVar(lock, "CacheFileIOManager::Shutdown() condVar");
1157 MutexAutoLock autoLock(lock);
1158 nsRefPtr<ShutdownEvent> ev = new ShutdownEvent(&lock, &condVar);
1159 DebugOnly<nsresult> rv;
1160 rv = gInstance->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
1161 MOZ_ASSERT(NS_SUCCEEDED(rv));
1162 condVar.Wait();
1165 MOZ_ASSERT(gInstance->mHandles.HandleCount() == 0);
1166 MOZ_ASSERT(gInstance->mHandlesByLastUsed.Length() == 0);
1168 if (gInstance->mIOThread) {
1169 gInstance->mIOThread->Shutdown();
1172 CacheIndex::Shutdown();
1174 if (CacheObserver::ClearCacheOnShutdown()) {
1175 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE> totalTimer;
1176 gInstance->SyncRemoveAllCacheFiles();
1179 nsRefPtr<CacheFileIOManager> ioMan;
1180 ioMan.swap(gInstance);
1182 return NS_OK;
1185 nsresult
1186 CacheFileIOManager::ShutdownInternal()
1188 LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
1190 MOZ_ASSERT(mIOThread->IsCurrentThread());
1192 // No new handles can be created after this flag is set
1193 mShuttingDown = true;
1195 // close all handles and delete all associated files
1196 nsTArray<nsRefPtr<CacheFileHandle> > handles;
1197 mHandles.GetAllHandles(&handles);
1198 handles.AppendElements(mSpecialHandles);
1200 for (uint32_t i=0 ; i<handles.Length() ; i++) {
1201 CacheFileHandle *h = handles[i];
1202 h->mClosed = true;
1204 h->Log();
1206 // Close file handle
1207 if (h->mFD) {
1208 ReleaseNSPRHandleInternal(h);
1211 // Remove file if entry is doomed or invalid
1212 if (h->mFileExists && (h->mIsDoomed || h->mInvalid)) {
1213 LOG(("CacheFileIOManager::ShutdownInternal() - Removing file from disk"));
1214 h->mFile->Remove(false);
1217 if (!h->IsSpecialFile() && !h->mIsDoomed &&
1218 (h->mInvalid || !h->mFileExists)) {
1219 CacheIndex::RemoveEntry(h->Hash());
1222 // Remove the handle from mHandles/mSpecialHandles
1223 if (h->IsSpecialFile()) {
1224 mSpecialHandles.RemoveElement(h);
1225 } else {
1226 mHandles.RemoveHandle(h);
1229 // Pointer to the hash is no longer valid once the last handle with the
1230 // given hash is released. Null out the pointer so that we crash if there
1231 // is a bug in this code and we dereference the pointer after this point.
1232 if (!h->IsSpecialFile()) {
1233 h->mHash = nullptr;
1237 // Assert the table is empty. When we are here, no new handles can be added
1238 // and handles will no longer remove them self from this table and we don't
1239 // want to keep invalid handles here. Also, there is no lookup after this
1240 // point to happen.
1241 MOZ_ASSERT(mHandles.HandleCount() == 0);
1243 // Release trash directory enumerator
1244 if (mTrashDirEnumerator) {
1245 mTrashDirEnumerator->Close();
1246 mTrashDirEnumerator = nullptr;
1249 return NS_OK;
1252 // static
1253 nsresult
1254 CacheFileIOManager::OnProfile()
1256 LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance));
1258 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1259 if (!ioMan) {
1260 // CacheFileIOManager::Init() failed, probably could not create the IO
1261 // thread, just go with it...
1262 return NS_ERROR_NOT_INITIALIZED;
1265 nsresult rv;
1267 nsCOMPtr<nsIFile> directory;
1269 CacheObserver::ParentDirOverride(getter_AddRefs(directory));
1271 #if defined(MOZ_WIDGET_ANDROID)
1272 nsCOMPtr<nsIFile> profilelessDirectory;
1273 char* cachePath = getenv("CACHE_DIRECTORY");
1274 if (!directory && cachePath && *cachePath) {
1275 rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
1276 true, getter_AddRefs(directory));
1277 if (NS_SUCCEEDED(rv)) {
1278 // Save this directory as the profileless path.
1279 rv = directory->Clone(getter_AddRefs(profilelessDirectory));
1280 NS_ENSURE_SUCCESS(rv, rv);
1282 // Add profile leaf name to the directory name to distinguish
1283 // multiple profiles Fennec supports.
1284 nsCOMPtr<nsIFile> profD;
1285 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1286 getter_AddRefs(profD));
1288 nsAutoCString leafName;
1289 if (NS_SUCCEEDED(rv)) {
1290 rv = profD->GetNativeLeafName(leafName);
1292 if (NS_SUCCEEDED(rv)) {
1293 rv = directory->AppendNative(leafName);
1295 if (NS_FAILED(rv)) {
1296 directory = nullptr;
1300 #endif
1302 if (!directory) {
1303 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
1304 getter_AddRefs(directory));
1307 if (!directory) {
1308 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
1309 getter_AddRefs(directory));
1312 if (directory) {
1313 rv = directory->Append(NS_LITERAL_STRING("cache2"));
1314 NS_ENSURE_SUCCESS(rv, rv);
1317 // All functions return a clone.
1318 ioMan->mCacheDirectory.swap(directory);
1320 #if defined(MOZ_WIDGET_ANDROID)
1321 if (profilelessDirectory) {
1322 rv = profilelessDirectory->Append(NS_LITERAL_STRING("cache2"));
1323 NS_ENSURE_SUCCESS(rv, rv);
1326 ioMan->mCacheProfilelessDirectory.swap(profilelessDirectory);
1327 #endif
1329 if (ioMan->mCacheDirectory) {
1330 CacheIndex::Init(ioMan->mCacheDirectory);
1333 return NS_OK;
1336 // static
1337 already_AddRefed<nsIEventTarget>
1338 CacheFileIOManager::IOTarget()
1340 nsCOMPtr<nsIEventTarget> target;
1341 if (gInstance && gInstance->mIOThread) {
1342 target = gInstance->mIOThread->Target();
1345 return target.forget();
1348 // static
1349 already_AddRefed<CacheIOThread>
1350 CacheFileIOManager::IOThread()
1352 nsRefPtr<CacheIOThread> thread;
1353 if (gInstance) {
1354 thread = gInstance->mIOThread;
1357 return thread.forget();
1360 // static
1361 bool
1362 CacheFileIOManager::IsOnIOThread()
1364 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1365 if (ioMan && ioMan->mIOThread) {
1366 return ioMan->mIOThread->IsCurrentThread();
1369 return false;
1372 // static
1373 bool
1374 CacheFileIOManager::IsOnIOThreadOrCeased()
1376 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1377 if (ioMan && ioMan->mIOThread) {
1378 return ioMan->mIOThread->IsCurrentThread();
1381 // Ceased...
1382 return true;
1385 // static
1386 bool
1387 CacheFileIOManager::IsShutdown()
1389 if (!gInstance) {
1390 return true;
1392 return gInstance->mShuttingDown;
1395 // static
1396 nsresult
1397 CacheFileIOManager::ScheduleMetadataWrite(CacheFile * aFile)
1399 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1400 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1402 NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
1404 nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1405 ioMan, aFile, MetadataWriteScheduleEvent::SCHEDULE);
1406 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1407 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1408 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1411 nsresult
1412 CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile * aFile)
1414 MOZ_ASSERT(IsOnIOThreadOrCeased());
1416 nsresult rv;
1418 if (!mMetadataWritesTimer) {
1419 mMetadataWritesTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1420 NS_ENSURE_SUCCESS(rv, rv);
1422 rv = mMetadataWritesTimer->InitWithCallback(
1423 this, kMetadataWriteDelay, nsITimer::TYPE_ONE_SHOT);
1424 NS_ENSURE_SUCCESS(rv, rv);
1427 if (mScheduledMetadataWrites.IndexOf(aFile) !=
1428 mScheduledMetadataWrites.NoIndex) {
1429 return NS_OK;
1432 mScheduledMetadataWrites.AppendElement(aFile);
1434 return NS_OK;
1437 // static
1438 nsresult
1439 CacheFileIOManager::UnscheduleMetadataWrite(CacheFile * aFile)
1441 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1442 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1444 NS_ENSURE_TRUE(!ioMan->mShuttingDown, NS_ERROR_NOT_INITIALIZED);
1446 nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1447 ioMan, aFile, MetadataWriteScheduleEvent::UNSCHEDULE);
1448 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1449 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1450 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1453 nsresult
1454 CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile * aFile)
1456 MOZ_ASSERT(IsOnIOThreadOrCeased());
1458 mScheduledMetadataWrites.RemoveElement(aFile);
1460 if (mScheduledMetadataWrites.Length() == 0 &&
1461 mMetadataWritesTimer) {
1462 mMetadataWritesTimer->Cancel();
1463 mMetadataWritesTimer = nullptr;
1466 return NS_OK;
1469 // static
1470 nsresult
1471 CacheFileIOManager::ShutdownMetadataWriteScheduling()
1473 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1474 NS_ENSURE_TRUE(ioMan, NS_ERROR_NOT_INITIALIZED);
1476 nsRefPtr<MetadataWriteScheduleEvent> event = new MetadataWriteScheduleEvent(
1477 ioMan, nullptr, MetadataWriteScheduleEvent::SHUTDOWN);
1478 nsCOMPtr<nsIEventTarget> target = ioMan->IOTarget();
1479 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1480 return target->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
1483 nsresult
1484 CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal()
1486 MOZ_ASSERT(IsOnIOThreadOrCeased());
1488 nsTArray<nsRefPtr<CacheFile> > files;
1489 files.SwapElements(mScheduledMetadataWrites);
1490 for (uint32_t i = 0; i < files.Length(); ++i) {
1491 CacheFile * file = files[i];
1492 file->WriteMetadataIfNeeded();
1495 if (mMetadataWritesTimer) {
1496 mMetadataWritesTimer->Cancel();
1497 mMetadataWritesTimer = nullptr;
1500 return NS_OK;
1503 NS_IMETHODIMP
1504 CacheFileIOManager::Notify(nsITimer * aTimer)
1506 MOZ_ASSERT(IsOnIOThreadOrCeased());
1507 MOZ_ASSERT(mMetadataWritesTimer == aTimer);
1509 mMetadataWritesTimer = nullptr;
1511 nsTArray<nsRefPtr<CacheFile> > files;
1512 files.SwapElements(mScheduledMetadataWrites);
1513 for (uint32_t i = 0; i < files.Length(); ++i) {
1514 CacheFile * file = files[i];
1515 file->WriteMetadataIfNeeded();
1518 return NS_OK;
1521 // static
1522 nsresult
1523 CacheFileIOManager::OpenFile(const nsACString &aKey,
1524 uint32_t aFlags, CacheFileIOListener *aCallback)
1526 LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
1527 PromiseFlatCString(aKey).get(), aFlags, aCallback));
1529 nsresult rv;
1530 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1532 if (!ioMan) {
1533 return NS_ERROR_NOT_INITIALIZED;
1536 bool priority = aFlags & CacheFileIOManager::PRIORITY;
1537 nsRefPtr<OpenFileEvent> ev = new OpenFileEvent(aKey, aFlags, aCallback);
1538 rv = ioMan->mIOThread->Dispatch(ev, priority
1539 ? CacheIOThread::OPEN_PRIORITY
1540 : CacheIOThread::OPEN);
1541 NS_ENSURE_SUCCESS(rv, rv);
1543 return NS_OK;
1546 nsresult
1547 CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash *aHash,
1548 const nsACString &aKey,
1549 uint32_t aFlags,
1550 CacheFileHandle **_retval)
1552 LOG(("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
1553 "key=%s, flags=%d]", LOGSHA1(aHash), PromiseFlatCString(aKey).get(),
1554 aFlags));
1556 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1558 nsresult rv;
1560 if (mShuttingDown) {
1561 return NS_ERROR_NOT_INITIALIZED;
1564 if (!mTreeCreated) {
1565 rv = CreateCacheTree();
1566 if (NS_FAILED(rv)) return rv;
1569 nsCOMPtr<nsIFile> file;
1570 rv = GetFile(aHash, getter_AddRefs(file));
1571 NS_ENSURE_SUCCESS(rv, rv);
1573 nsRefPtr<CacheFileHandle> handle;
1574 mHandles.GetHandle(aHash, false, getter_AddRefs(handle));
1576 if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
1577 if (handle) {
1578 rv = DoomFileInternal(handle);
1579 NS_ENSURE_SUCCESS(rv, rv);
1580 handle = nullptr;
1583 rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
1584 NS_ENSURE_SUCCESS(rv, rv);
1586 bool exists;
1587 rv = file->Exists(&exists);
1588 NS_ENSURE_SUCCESS(rv, rv);
1590 if (exists) {
1591 CacheIndex::RemoveEntry(aHash);
1593 LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file from "
1594 "disk"));
1595 rv = file->Remove(false);
1596 if (NS_FAILED(rv)) {
1597 NS_WARNING("Cannot remove old entry from the disk");
1598 LOG(("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
1599 ". [rv=0x%08x]", rv));
1603 CacheIndex::AddEntry(aHash);
1604 handle->mFile.swap(file);
1605 handle->mFileSize = 0;
1608 if (handle) {
1609 handle.swap(*_retval);
1610 return NS_OK;
1613 bool exists;
1614 rv = file->Exists(&exists);
1615 NS_ENSURE_SUCCESS(rv, rv);
1617 if (exists && mContextEvictor) {
1618 if (mContextEvictor->ContextsCount() == 0) {
1619 mContextEvictor = nullptr;
1620 } else {
1621 bool wasEvicted = false;
1622 mContextEvictor->WasEvicted(aKey, file, &wasEvicted);
1623 if (wasEvicted) {
1624 LOG(("CacheFileIOManager::OpenFileInternal() - Removing file since the "
1625 "entry was evicted by EvictByContext()"));
1626 exists = false;
1627 file->Remove(false);
1628 CacheIndex::RemoveEntry(aHash);
1633 if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
1634 return NS_ERROR_NOT_AVAILABLE;
1637 rv = mHandles.NewHandle(aHash, aFlags & PRIORITY, getter_AddRefs(handle));
1638 NS_ENSURE_SUCCESS(rv, rv);
1640 if (exists) {
1641 rv = file->GetFileSize(&handle->mFileSize);
1642 NS_ENSURE_SUCCESS(rv, rv);
1644 handle->mFileExists = true;
1646 CacheIndex::EnsureEntryExists(aHash);
1647 } else {
1648 handle->mFileSize = 0;
1650 CacheIndex::AddEntry(aHash);
1653 handle->mFile.swap(file);
1654 handle.swap(*_retval);
1655 return NS_OK;
1658 nsresult
1659 CacheFileIOManager::OpenSpecialFileInternal(const nsACString &aKey,
1660 uint32_t aFlags,
1661 CacheFileHandle **_retval)
1663 LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
1664 PromiseFlatCString(aKey).get(), aFlags));
1666 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1668 nsresult rv;
1670 if (mShuttingDown) {
1671 return NS_ERROR_NOT_INITIALIZED;
1674 if (!mTreeCreated) {
1675 rv = CreateCacheTree();
1676 if (NS_FAILED(rv)) return rv;
1679 nsCOMPtr<nsIFile> file;
1680 rv = GetSpecialFile(aKey, getter_AddRefs(file));
1681 NS_ENSURE_SUCCESS(rv, rv);
1683 nsRefPtr<CacheFileHandle> handle;
1684 for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) {
1685 if (!mSpecialHandles[i]->IsDoomed() && mSpecialHandles[i]->Key() == aKey) {
1686 handle = mSpecialHandles[i];
1687 break;
1691 if ((aFlags & (OPEN | CREATE | CREATE_NEW)) == CREATE_NEW) {
1692 if (handle) {
1693 rv = DoomFileInternal(handle);
1694 NS_ENSURE_SUCCESS(rv, rv);
1695 handle = nullptr;
1698 handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
1699 mSpecialHandles.AppendElement(handle);
1701 bool exists;
1702 rv = file->Exists(&exists);
1703 NS_ENSURE_SUCCESS(rv, rv);
1705 if (exists) {
1706 LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
1707 "disk"));
1708 rv = file->Remove(false);
1709 if (NS_FAILED(rv)) {
1710 NS_WARNING("Cannot remove old entry from the disk");
1711 LOG(("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
1712 "failed. [rv=0x%08x]", rv));
1716 handle->mFile.swap(file);
1717 handle->mFileSize = 0;
1720 if (handle) {
1721 handle.swap(*_retval);
1722 return NS_OK;
1725 bool exists;
1726 rv = file->Exists(&exists);
1727 NS_ENSURE_SUCCESS(rv, rv);
1729 if (!exists && (aFlags & (OPEN | CREATE | CREATE_NEW)) == OPEN) {
1730 return NS_ERROR_NOT_AVAILABLE;
1733 handle = new CacheFileHandle(aKey, aFlags & PRIORITY);
1734 mSpecialHandles.AppendElement(handle);
1736 if (exists) {
1737 rv = file->GetFileSize(&handle->mFileSize);
1738 NS_ENSURE_SUCCESS(rv, rv);
1740 handle->mFileExists = true;
1741 } else {
1742 handle->mFileSize = 0;
1745 handle->mFile.swap(file);
1746 handle.swap(*_retval);
1747 return NS_OK;
1750 nsresult
1751 CacheFileIOManager::CloseHandleInternal(CacheFileHandle *aHandle)
1753 LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle));
1755 MOZ_ASSERT(!aHandle->IsClosed());
1757 aHandle->Log();
1759 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
1761 // Close file handle
1762 if (aHandle->mFD) {
1763 ReleaseNSPRHandleInternal(aHandle);
1766 // Delete the file if the entry was doomed or invalid
1767 if (aHandle->mIsDoomed || aHandle->mInvalid) {
1768 LOG(("CacheFileIOManager::CloseHandleInternal() - Removing file from "
1769 "disk"));
1770 aHandle->mFile->Remove(false);
1773 if (!aHandle->IsSpecialFile() && !aHandle->mIsDoomed &&
1774 (aHandle->mInvalid || !aHandle->mFileExists)) {
1775 CacheIndex::RemoveEntry(aHandle->Hash());
1778 // Don't remove handles after shutdown
1779 if (!mShuttingDown) {
1780 if (aHandle->IsSpecialFile()) {
1781 mSpecialHandles.RemoveElement(aHandle);
1782 } else {
1783 mHandles.RemoveHandle(aHandle);
1787 return NS_OK;
1790 // static
1791 nsresult
1792 CacheFileIOManager::Read(CacheFileHandle *aHandle, int64_t aOffset,
1793 char *aBuf, int32_t aCount,
1794 CacheFileIOListener *aCallback)
1796 LOG(("CacheFileIOManager::Read() [handle=%p, offset=%lld, count=%d, "
1797 "listener=%p]", aHandle, aOffset, aCount, aCallback));
1799 nsresult rv;
1800 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1802 if (aHandle->IsClosed() || !ioMan) {
1803 return NS_ERROR_NOT_INITIALIZED;
1806 nsRefPtr<ReadEvent> ev = new ReadEvent(aHandle, aOffset, aBuf, aCount,
1807 aCallback);
1808 rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
1809 ? CacheIOThread::READ_PRIORITY
1810 : CacheIOThread::READ);
1811 NS_ENSURE_SUCCESS(rv, rv);
1813 return NS_OK;
1816 nsresult
1817 CacheFileIOManager::ReadInternal(CacheFileHandle *aHandle, int64_t aOffset,
1818 char *aBuf, int32_t aCount)
1820 LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%lld, count=%d]",
1821 aHandle, aOffset, aCount));
1823 nsresult rv;
1825 if (!aHandle->mFileExists) {
1826 NS_WARNING("Trying to read from non-existent file");
1827 return NS_ERROR_NOT_AVAILABLE;
1830 if (!aHandle->mFD) {
1831 rv = OpenNSPRHandle(aHandle);
1832 NS_ENSURE_SUCCESS(rv, rv);
1833 } else {
1834 NSPRHandleUsed(aHandle);
1837 // Check again, OpenNSPRHandle could figure out the file was gone.
1838 if (!aHandle->mFileExists) {
1839 NS_WARNING("Trying to read from non-existent file");
1840 return NS_ERROR_NOT_AVAILABLE;
1843 int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
1844 if (offset == -1) {
1845 return NS_ERROR_FAILURE;
1848 int32_t bytesRead = PR_Read(aHandle->mFD, aBuf, aCount);
1849 if (bytesRead != aCount) {
1850 return NS_ERROR_FAILURE;
1853 return NS_OK;
1856 // static
1857 nsresult
1858 CacheFileIOManager::Write(CacheFileHandle *aHandle, int64_t aOffset,
1859 const char *aBuf, int32_t aCount, bool aValidate,
1860 CacheFileIOListener *aCallback)
1862 LOG(("CacheFileIOManager::Write() [handle=%p, offset=%lld, count=%d, "
1863 "validate=%d, listener=%p]", aHandle, aOffset, aCount, aValidate,
1864 aCallback));
1866 nsresult rv;
1867 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1869 if (aHandle->IsClosed() || !ioMan) {
1870 if (!aCallback) {
1871 // When no callback is provided, CacheFileIOManager is responsible for
1872 // releasing the buffer. We must release it even in case of failure.
1873 free(const_cast<char *>(aBuf));
1875 return NS_ERROR_NOT_INITIALIZED;
1878 nsRefPtr<WriteEvent> ev = new WriteEvent(aHandle, aOffset, aBuf, aCount,
1879 aValidate, aCallback);
1880 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
1881 NS_ENSURE_SUCCESS(rv, rv);
1883 return NS_OK;
1886 nsresult
1887 CacheFileIOManager::WriteInternal(CacheFileHandle *aHandle, int64_t aOffset,
1888 const char *aBuf, int32_t aCount,
1889 bool aValidate)
1891 LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%lld, count=%d, "
1892 "validate=%d]", aHandle, aOffset, aCount, aValidate));
1894 nsresult rv;
1896 if (!aHandle->mFileExists) {
1897 rv = CreateFile(aHandle);
1898 NS_ENSURE_SUCCESS(rv, rv);
1901 if (!aHandle->mFD) {
1902 rv = OpenNSPRHandle(aHandle);
1903 NS_ENSURE_SUCCESS(rv, rv);
1904 } else {
1905 NSPRHandleUsed(aHandle);
1908 // Check again, OpenNSPRHandle could figure out the file was gone.
1909 if (!aHandle->mFileExists) {
1910 return NS_ERROR_NOT_AVAILABLE;
1913 // Check whether this write would cause critical low disk space.
1914 if (aHandle->mFileSize < aOffset + aCount) {
1915 int64_t freeSpace = -1;
1916 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
1917 if (NS_WARN_IF(NS_FAILED(rv))) {
1918 LOG(("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
1919 "failed! [rv=0x%08x]", rv));
1920 } else {
1921 uint32_t limit = CacheObserver::DiskFreeSpaceHardLimit();
1922 if (freeSpace - aOffset - aCount + aHandle->mFileSize < limit) {
1923 LOG(("CacheFileIOManager::WriteInternal() - Low free space, refusing "
1924 "to write! [freeSpace=%lld, limit=%u]", freeSpace, limit));
1925 return NS_ERROR_FILE_DISK_FULL;
1930 // Write invalidates the entry by default
1931 aHandle->mInvalid = true;
1933 int64_t offset = PR_Seek64(aHandle->mFD, aOffset, PR_SEEK_SET);
1934 if (offset == -1) {
1935 return NS_ERROR_FAILURE;
1938 int32_t bytesWritten = PR_Write(aHandle->mFD, aBuf, aCount);
1940 if (bytesWritten != -1 && aHandle->mFileSize < aOffset+bytesWritten) {
1941 aHandle->mFileSize = aOffset+bytesWritten;
1943 if (!aHandle->IsDoomed() && !aHandle->IsSpecialFile()) {
1944 uint32_t size = aHandle->FileSizeInK();
1945 CacheIndex::UpdateEntry(aHandle->Hash(), nullptr, nullptr, &size);
1946 EvictIfOverLimitInternal();
1950 if (bytesWritten != aCount) {
1951 return NS_ERROR_FAILURE;
1954 // Write was successful and this write validates the entry (i.e. metadata)
1955 if (aValidate) {
1956 aHandle->mInvalid = false;
1959 return NS_OK;
1962 // static
1963 nsresult
1964 CacheFileIOManager::DoomFile(CacheFileHandle *aHandle,
1965 CacheFileIOListener *aCallback)
1967 LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]",
1968 aHandle, aCallback));
1970 nsresult rv;
1971 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
1973 if (aHandle->IsClosed() || !ioMan) {
1974 return NS_ERROR_NOT_INITIALIZED;
1977 nsRefPtr<DoomFileEvent> ev = new DoomFileEvent(aHandle, aCallback);
1978 rv = ioMan->mIOThread->Dispatch(ev, aHandle->IsPriority()
1979 ? CacheIOThread::OPEN_PRIORITY
1980 : CacheIOThread::OPEN);
1981 NS_ENSURE_SUCCESS(rv, rv);
1983 return NS_OK;
1986 nsresult
1987 CacheFileIOManager::DoomFileInternal(CacheFileHandle *aHandle)
1989 LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle));
1990 aHandle->Log();
1992 nsresult rv;
1994 if (aHandle->IsDoomed()) {
1995 return NS_OK;
1998 if (aHandle->mFileExists) {
1999 // we need to move the current file to the doomed directory
2000 if (aHandle->mFD) {
2001 ReleaseNSPRHandleInternal(aHandle);
2004 // find unused filename
2005 nsCOMPtr<nsIFile> file;
2006 rv = GetDoomedFile(getter_AddRefs(file));
2007 NS_ENSURE_SUCCESS(rv, rv);
2009 nsCOMPtr<nsIFile> parentDir;
2010 rv = file->GetParent(getter_AddRefs(parentDir));
2011 NS_ENSURE_SUCCESS(rv, rv);
2013 nsAutoCString leafName;
2014 rv = file->GetNativeLeafName(leafName);
2015 NS_ENSURE_SUCCESS(rv, rv);
2017 rv = aHandle->mFile->MoveToNative(parentDir, leafName);
2018 if (NS_ERROR_FILE_NOT_FOUND == rv || NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv) {
2019 LOG((" file already removed under our hands"));
2020 aHandle->mFileExists = false;
2021 rv = NS_OK;
2022 } else {
2023 NS_ENSURE_SUCCESS(rv, rv);
2024 aHandle->mFile.swap(file);
2028 if (!aHandle->IsSpecialFile()) {
2029 CacheIndex::RemoveEntry(aHandle->Hash());
2032 aHandle->mIsDoomed = true;
2034 if (!aHandle->IsSpecialFile()) {
2035 nsRefPtr<CacheStorageService> storageService = CacheStorageService::Self();
2036 if (storageService) {
2037 nsAutoCString idExtension, url;
2038 nsCOMPtr<nsILoadContextInfo> info =
2039 CacheFileUtils::ParseKey(aHandle->Key(), &idExtension, &url);
2040 MOZ_ASSERT(info);
2041 if (info) {
2042 storageService->CacheFileDoomed(info, idExtension, url);
2047 return NS_OK;
2050 // static
2051 nsresult
2052 CacheFileIOManager::DoomFileByKey(const nsACString &aKey,
2053 CacheFileIOListener *aCallback)
2055 LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
2056 PromiseFlatCString(aKey).get(), aCallback));
2058 nsresult rv;
2059 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2061 if (!ioMan) {
2062 return NS_ERROR_NOT_INITIALIZED;
2065 nsRefPtr<DoomFileByKeyEvent> ev = new DoomFileByKeyEvent(aKey, aCallback);
2066 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2067 NS_ENSURE_SUCCESS(rv, rv);
2069 return NS_OK;
2072 nsresult
2073 CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash *aHash,
2074 bool aFailIfAlreadyDoomed)
2076 LOG(("CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x,"
2077 " failIfAlreadyDoomed=%d]", LOGSHA1(aHash), aFailIfAlreadyDoomed));
2079 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2081 nsresult rv;
2083 if (mShuttingDown) {
2084 return NS_ERROR_NOT_INITIALIZED;
2087 if (!mCacheDirectory) {
2088 return NS_ERROR_FILE_INVALID_PATH;
2091 // Find active handle
2092 nsRefPtr<CacheFileHandle> handle;
2093 mHandles.GetHandle(aHash, true, getter_AddRefs(handle));
2095 if (handle) {
2096 handle->Log();
2098 if (handle->IsDoomed()) {
2099 return aFailIfAlreadyDoomed ? NS_ERROR_NOT_AVAILABLE : NS_OK;
2102 return DoomFileInternal(handle);
2105 // There is no handle for this file, delete the file if exists
2106 nsCOMPtr<nsIFile> file;
2107 rv = GetFile(aHash, getter_AddRefs(file));
2108 NS_ENSURE_SUCCESS(rv, rv);
2110 bool exists;
2111 rv = file->Exists(&exists);
2112 NS_ENSURE_SUCCESS(rv, rv);
2114 if (!exists) {
2115 return NS_ERROR_NOT_AVAILABLE;
2118 LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from "
2119 "disk"));
2120 rv = file->Remove(false);
2121 if (NS_FAILED(rv)) {
2122 NS_WARNING("Cannot remove old entry from the disk");
2123 LOG(("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
2124 "[rv=0x%08x]", rv));
2127 CacheIndex::RemoveEntry(aHash);
2129 return NS_OK;
2132 // static
2133 nsresult
2134 CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle *aHandle)
2136 LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle));
2138 nsresult rv;
2139 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2141 if (aHandle->IsClosed() || !ioMan) {
2142 return NS_ERROR_NOT_INITIALIZED;
2145 nsRefPtr<ReleaseNSPRHandleEvent> ev = new ReleaseNSPRHandleEvent(aHandle);
2146 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::CLOSE);
2147 NS_ENSURE_SUCCESS(rv, rv);
2149 return NS_OK;
2152 nsresult
2153 CacheFileIOManager::ReleaseNSPRHandleInternal(CacheFileHandle *aHandle)
2155 LOG(("CacheFileIOManager::ReleaseNSPRHandleInternal() [handle=%p]", aHandle));
2157 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2158 MOZ_ASSERT(aHandle->mFD);
2160 DebugOnly<bool> found;
2161 found = mHandlesByLastUsed.RemoveElement(aHandle);
2162 MOZ_ASSERT(found);
2164 PR_Close(aHandle->mFD);
2165 aHandle->mFD = nullptr;
2167 return NS_OK;
2170 // static
2171 nsresult
2172 CacheFileIOManager::TruncateSeekSetEOF(CacheFileHandle *aHandle,
2173 int64_t aTruncatePos, int64_t aEOFPos,
2174 CacheFileIOListener *aCallback)
2176 LOG(("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, truncatePos=%lld, "
2177 "EOFPos=%lld, listener=%p]", aHandle, aTruncatePos, aEOFPos, aCallback));
2179 nsresult rv;
2180 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2182 if (aHandle->IsClosed() || !ioMan) {
2183 return NS_ERROR_NOT_INITIALIZED;
2186 nsRefPtr<TruncateSeekSetEOFEvent> ev = new TruncateSeekSetEOFEvent(
2187 aHandle, aTruncatePos, aEOFPos,
2188 aCallback);
2189 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
2190 NS_ENSURE_SUCCESS(rv, rv);
2192 return NS_OK;
2195 // static
2196 void CacheFileIOManager::GetCacheDirectory(nsIFile** result)
2198 *result = nullptr;
2200 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2201 if (!ioMan) {
2202 return;
2205 ioMan->mCacheDirectory->Clone(result);
2208 #if defined(MOZ_WIDGET_ANDROID)
2210 // static
2211 void CacheFileIOManager::GetProfilelessCacheDirectory(nsIFile** result)
2213 *result = nullptr;
2215 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2216 if (!ioMan || !ioMan->mCacheProfilelessDirectory) {
2217 return;
2220 ioMan->mCacheProfilelessDirectory->Clone(result);
2223 #endif
2225 // static
2226 nsresult
2227 CacheFileIOManager::GetEntryInfo(const SHA1Sum::Hash *aHash,
2228 CacheStorageService::EntryInfoCallback *aCallback)
2230 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
2232 nsresult rv;
2234 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2235 if (!ioMan) {
2236 return NS_ERROR_NOT_INITIALIZED;
2239 nsAutoCString enhanceId;
2240 nsAutoCString uriSpec;
2242 nsRefPtr<CacheFileHandle> handle;
2243 ioMan->mHandles.GetHandle(aHash, false, getter_AddRefs(handle));
2244 if (handle) {
2245 nsRefPtr<nsILoadContextInfo> info =
2246 CacheFileUtils::ParseKey(handle->Key(), &enhanceId, &uriSpec);
2248 MOZ_ASSERT(info);
2249 if (!info) {
2250 return NS_OK; // ignore
2253 nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
2254 if (!service) {
2255 return NS_ERROR_NOT_INITIALIZED;
2258 // Invokes OnCacheEntryInfo when an existing entry is found
2259 if (service->GetCacheEntryInfo(info, enhanceId, uriSpec, aCallback)) {
2260 return NS_OK;
2263 // When we are here, there is no existing entry and we need
2264 // to synchrnously load metadata from a disk file.
2267 // Locate the actual file
2268 nsCOMPtr<nsIFile> file;
2269 ioMan->GetFile(aHash, getter_AddRefs(file));
2271 // Read metadata from the file synchronously
2272 nsRefPtr<CacheFileMetadata> metadata = new CacheFileMetadata();
2273 rv = metadata->SyncReadMetadata(file);
2274 if (NS_FAILED(rv)) {
2275 return NS_OK;
2278 // Now get the context + enhance id + URL from the key.
2279 nsAutoCString key;
2280 metadata->GetKey(key);
2282 nsRefPtr<nsILoadContextInfo> info =
2283 CacheFileUtils::ParseKey(key, &enhanceId, &uriSpec);
2284 MOZ_ASSERT(info);
2285 if (!info) {
2286 return NS_OK;
2289 // Pick all data to pass to the callback.
2290 int64_t dataSize = metadata->Offset();
2291 uint32_t fetchCount;
2292 if (NS_FAILED(metadata->GetFetchCount(&fetchCount))) {
2293 fetchCount = 0;
2295 uint32_t expirationTime;
2296 if (NS_FAILED(metadata->GetExpirationTime(&expirationTime))) {
2297 expirationTime = 0;
2299 uint32_t lastModified;
2300 if (NS_FAILED(metadata->GetLastModified(&lastModified))) {
2301 lastModified = 0;
2304 // Call directly on the callback.
2305 aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, fetchCount,
2306 lastModified, expirationTime);
2308 return NS_OK;
2311 static nsresult
2312 TruncFile(PRFileDesc *aFD, uint32_t aEOF)
2314 #if defined(XP_UNIX)
2315 if (ftruncate(PR_FileDesc2NativeHandle(aFD), aEOF) != 0) {
2316 NS_ERROR("ftruncate failed");
2317 return NS_ERROR_FAILURE;
2319 #elif defined(XP_WIN)
2320 int32_t cnt = PR_Seek(aFD, aEOF, PR_SEEK_SET);
2321 if (cnt == -1) {
2322 return NS_ERROR_FAILURE;
2324 if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(aFD))) {
2325 NS_ERROR("SetEndOfFile failed");
2326 return NS_ERROR_FAILURE;
2328 #else
2329 MOZ_ASSERT(false, "Not implemented!");
2330 #endif
2332 return NS_OK;
2335 nsresult
2336 CacheFileIOManager::TruncateSeekSetEOFInternal(CacheFileHandle *aHandle,
2337 int64_t aTruncatePos,
2338 int64_t aEOFPos)
2340 LOG(("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
2341 "truncatePos=%lld, EOFPos=%lld]", aHandle, aTruncatePos, aEOFPos));
2343 nsresult rv;
2345 if (!aHandle->mFileExists) {
2346 rv = CreateFile(aHandle);
2347 NS_ENSURE_SUCCESS(rv, rv);
2350 if (!aHandle->mFD) {
2351 rv = OpenNSPRHandle(aHandle);
2352 NS_ENSURE_SUCCESS(rv, rv);
2353 } else {
2354 NSPRHandleUsed(aHandle);
2357 // Check again, OpenNSPRHandle could figure out the file was gone.
2358 if (!aHandle->mFileExists) {
2359 return NS_ERROR_NOT_AVAILABLE;
2362 // This operation always invalidates the entry
2363 aHandle->mInvalid = true;
2365 rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aTruncatePos));
2366 NS_ENSURE_SUCCESS(rv, rv);
2368 rv = TruncFile(aHandle->mFD, static_cast<uint32_t>(aEOFPos));
2369 NS_ENSURE_SUCCESS(rv, rv);
2371 return NS_OK;
2374 // static
2375 nsresult
2376 CacheFileIOManager::RenameFile(CacheFileHandle *aHandle,
2377 const nsACString &aNewName,
2378 CacheFileIOListener *aCallback)
2380 LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
2381 aHandle, PromiseFlatCString(aNewName).get(), aCallback));
2383 nsresult rv;
2384 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2386 if (aHandle->IsClosed() || !ioMan) {
2387 return NS_ERROR_NOT_INITIALIZED;
2390 if (!aHandle->IsSpecialFile()) {
2391 return NS_ERROR_UNEXPECTED;
2394 nsRefPtr<RenameFileEvent> ev = new RenameFileEvent(aHandle, aNewName,
2395 aCallback);
2396 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
2397 NS_ENSURE_SUCCESS(rv, rv);
2399 return NS_OK;
2402 nsresult
2403 CacheFileIOManager::RenameFileInternal(CacheFileHandle *aHandle,
2404 const nsACString &aNewName)
2406 LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",
2407 aHandle, PromiseFlatCString(aNewName).get()));
2409 nsresult rv;
2411 MOZ_ASSERT(aHandle->IsSpecialFile());
2413 if (aHandle->IsDoomed()) {
2414 return NS_ERROR_NOT_AVAILABLE;
2417 // Doom old handle if it exists and is not doomed
2418 for (uint32_t i = 0 ; i < mSpecialHandles.Length() ; i++) {
2419 if (!mSpecialHandles[i]->IsDoomed() &&
2420 mSpecialHandles[i]->Key() == aNewName) {
2421 MOZ_ASSERT(aHandle != mSpecialHandles[i]);
2422 rv = DoomFileInternal(mSpecialHandles[i]);
2423 NS_ENSURE_SUCCESS(rv, rv);
2424 break;
2428 nsCOMPtr<nsIFile> file;
2429 rv = GetSpecialFile(aNewName, getter_AddRefs(file));
2430 NS_ENSURE_SUCCESS(rv, rv);
2432 bool exists;
2433 rv = file->Exists(&exists);
2434 NS_ENSURE_SUCCESS(rv, rv);
2436 if (exists) {
2437 LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file from "
2438 "disk"));
2439 rv = file->Remove(false);
2440 if (NS_FAILED(rv)) {
2441 NS_WARNING("Cannot remove file from the disk");
2442 LOG(("CacheFileIOManager::RenameFileInternal() - Removing old file failed"
2443 ". [rv=0x%08x]", rv));
2447 if (!aHandle->FileExists()) {
2448 aHandle->mKey = aNewName;
2449 return NS_OK;
2452 if (aHandle->mFD) {
2453 ReleaseNSPRHandleInternal(aHandle);
2456 rv = aHandle->mFile->MoveToNative(nullptr, aNewName);
2457 NS_ENSURE_SUCCESS(rv, rv);
2459 aHandle->mKey = aNewName;
2460 return NS_OK;
2463 // static
2464 nsresult
2465 CacheFileIOManager::EvictIfOverLimit()
2467 LOG(("CacheFileIOManager::EvictIfOverLimit()"));
2469 nsresult rv;
2470 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2472 if (!ioMan) {
2473 return NS_ERROR_NOT_INITIALIZED;
2476 nsCOMPtr<nsIRunnable> ev;
2477 ev = NS_NewRunnableMethod(ioMan,
2478 &CacheFileIOManager::EvictIfOverLimitInternal);
2480 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2481 NS_ENSURE_SUCCESS(rv, rv);
2483 return NS_OK;
2486 nsresult
2487 CacheFileIOManager::EvictIfOverLimitInternal()
2489 LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
2491 nsresult rv;
2493 MOZ_ASSERT(mIOThread->IsCurrentThread());
2495 if (mShuttingDown) {
2496 return NS_ERROR_NOT_INITIALIZED;
2499 if (mOverLimitEvicting) {
2500 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
2501 "running."));
2502 return NS_OK;
2505 int64_t freeSpace;
2506 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
2507 if (NS_WARN_IF(NS_FAILED(rv))) {
2508 freeSpace = -1;
2510 // Do not change smart size.
2511 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - "
2512 "GetDiskSpaceAvailable() failed! [rv=0x%08x]", rv));
2513 } else {
2514 UpdateSmartCacheSize(freeSpace);
2517 uint32_t cacheUsage;
2518 rv = CacheIndex::GetCacheSize(&cacheUsage);
2519 NS_ENSURE_SUCCESS(rv, rv);
2521 uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
2522 uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
2524 if (cacheUsage <= cacheLimit &&
2525 (freeSpace == -1 || freeSpace >= freeSpaceLimit)) {
2526 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free "
2527 "space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
2528 "freeSpace=%lld, freeSpaceLimit=%u]", cacheUsage, cacheLimit,
2529 freeSpace, freeSpaceLimit));
2530 return NS_OK;
2533 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
2534 "limit. Starting overlimit eviction. [cacheSize=%u, limit=%u]",
2535 cacheUsage, cacheLimit));
2537 nsCOMPtr<nsIRunnable> ev;
2538 ev = NS_NewRunnableMethod(this,
2539 &CacheFileIOManager::OverLimitEvictionInternal);
2541 rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
2542 NS_ENSURE_SUCCESS(rv, rv);
2544 mOverLimitEvicting = true;
2545 return NS_OK;
2548 nsresult
2549 CacheFileIOManager::OverLimitEvictionInternal()
2551 LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
2553 nsresult rv;
2555 MOZ_ASSERT(mIOThread->IsCurrentThread());
2557 // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
2558 // here and set it to true again once we dispatch another event that will
2559 // continue with the eviction. The reason why we do so is that we can fail
2560 // early anywhere in this method and the variable will contain a correct
2561 // value. Otherwise we would need to set it to false on every failing place.
2562 mOverLimitEvicting = false;
2564 if (mShuttingDown) {
2565 return NS_ERROR_NOT_INITIALIZED;
2568 while (true) {
2569 int64_t freeSpace = -1;
2570 rv = mCacheDirectory->GetDiskSpaceAvailable(&freeSpace);
2571 if (NS_WARN_IF(NS_FAILED(rv))) {
2572 // Do not change smart size.
2573 LOG(("CacheFileIOManager::EvictIfOverLimitInternal() - "
2574 "GetDiskSpaceAvailable() failed! [rv=0x%08x]", rv));
2575 } else {
2576 UpdateSmartCacheSize(freeSpace);
2579 uint32_t cacheUsage;
2580 rv = CacheIndex::GetCacheSize(&cacheUsage);
2581 NS_ENSURE_SUCCESS(rv, rv);
2583 uint32_t cacheLimit = CacheObserver::DiskCacheCapacity() >> 10;
2584 uint32_t freeSpaceLimit = CacheObserver::DiskFreeSpaceSoftLimit();
2586 if (cacheUsage > cacheLimit) {
2587 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
2588 "limit. [cacheSize=%u, limit=%u]", cacheUsage, cacheLimit));
2589 } else if (freeSpace != 1 && freeSpace < freeSpaceLimit) {
2590 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Free space under "
2591 "limit. [freeSpace=%lld, freeSpaceLimit=%u]", freeSpace,
2592 freeSpaceLimit));
2593 } else {
2594 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and "
2595 "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
2596 "freeSpace=%lld, freeSpaceLimit=%u]", cacheUsage, cacheLimit,
2597 freeSpace, freeSpaceLimit));
2598 return NS_OK;
2601 if (CacheIOThread::YieldAndRerun()) {
2602 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
2603 "for higher level events."));
2604 mOverLimitEvicting = true;
2605 return NS_OK;
2608 SHA1Sum::Hash hash;
2609 uint32_t cnt;
2610 static uint32_t consecutiveFailures = 0;
2611 rv = CacheIndex::GetEntryForEviction(&hash, &cnt);
2612 NS_ENSURE_SUCCESS(rv, rv);
2614 rv = DoomFileByKeyInternal(&hash, true);
2615 if (NS_SUCCEEDED(rv)) {
2616 consecutiveFailures = 0;
2617 } else if (rv == NS_ERROR_NOT_AVAILABLE) {
2618 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - "
2619 "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv));
2620 // TODO index is outdated, start update
2622 #ifdef DEBUG
2623 // Dooming should never fail due to already doomed handle, but bug 1028415
2624 // shows that this unexpected state can happen. Assert in debug build so
2625 // we can find the cause if we ever find a way to reproduce it with NSPR
2626 // logging enabled.
2627 nsRefPtr<CacheFileHandle> handle;
2628 mHandles.GetHandle(&hash, true, getter_AddRefs(handle));
2629 MOZ_ASSERT(!handle || !handle->IsDoomed());
2630 #endif
2632 // Make sure index won't return the same entry again
2633 CacheIndex::RemoveEntry(&hash);
2634 consecutiveFailures = 0;
2635 } else {
2636 // This shouldn't normally happen, but the eviction must not fail
2637 // completely if we ever encounter this problem.
2638 NS_WARNING("CacheFileIOManager::OverLimitEvictionInternal() - Unexpected "
2639 "failure of DoomFileByKeyInternal()");
2641 LOG(("CacheFileIOManager::OverLimitEvictionInternal() - "
2642 "DoomFileByKeyInternal() failed. [rv=0x%08x]", rv));
2644 // Normally, CacheIndex::UpdateEntry() is called only to update newly
2645 // created/opened entries which are always fresh and UpdateEntry() expects
2646 // and checks this flag. The way we use UpdateEntry() here is a kind of
2647 // hack and we must make sure the flag is set by calling
2648 // EnsureEntryExists().
2649 rv = CacheIndex::EnsureEntryExists(&hash);
2650 NS_ENSURE_SUCCESS(rv, rv);
2652 // Move the entry at the end of both lists to make sure we won't end up
2653 // failing on one entry forever.
2654 uint32_t frecency = 0;
2655 uint32_t expTime = nsICacheEntry::NO_EXPIRATION_TIME;
2656 rv = CacheIndex::UpdateEntry(&hash, &frecency, &expTime, nullptr);
2657 NS_ENSURE_SUCCESS(rv, rv);
2659 consecutiveFailures++;
2660 if (consecutiveFailures >= cnt) {
2661 // This doesn't necessarily mean that we've tried to doom every entry
2662 // but we've reached a sane number of tries. It is likely that another
2663 // eviction will start soon. And as said earlier, this normally doesn't
2664 // happen at all.
2665 return NS_OK;
2670 NS_NOTREACHED("We should never get here");
2671 return NS_OK;
2674 // static
2675 nsresult
2676 CacheFileIOManager::EvictAll()
2678 LOG(("CacheFileIOManager::EvictAll()"));
2680 nsresult rv;
2681 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2683 if (!ioMan) {
2684 return NS_ERROR_NOT_INITIALIZED;
2687 nsCOMPtr<nsIRunnable> ev;
2688 ev = NS_NewRunnableMethod(ioMan, &CacheFileIOManager::EvictAllInternal);
2690 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2691 if (NS_WARN_IF(NS_FAILED(rv))) {
2692 return rv;
2695 return NS_OK;
2698 namespace {
2700 class EvictionNotifierRunnable : public nsRunnable
2702 public:
2703 NS_DECL_NSIRUNNABLE
2706 NS_IMETHODIMP
2707 EvictionNotifierRunnable::Run()
2709 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
2710 if (obsSvc) {
2711 obsSvc->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr);
2713 return NS_OK;
2716 } // anonymous namespace
2718 nsresult
2719 CacheFileIOManager::EvictAllInternal()
2721 LOG(("CacheFileIOManager::EvictAllInternal()"));
2723 nsresult rv;
2725 MOZ_ASSERT(mIOThread->IsCurrentThread());
2727 nsRefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable();
2729 if (!mCacheDirectory) {
2730 // This is a kind of hack. Somebody called EvictAll() without a profile.
2731 // This happens in xpcshell tests that use cache without profile. We need
2732 // to notify observers in this case since the tests are waiting for it.
2733 NS_DispatchToMainThread(r);
2734 return NS_ERROR_FILE_INVALID_PATH;
2737 if (mShuttingDown) {
2738 return NS_ERROR_NOT_INITIALIZED;
2741 if (!mTreeCreated) {
2742 rv = CreateCacheTree();
2743 if (NS_FAILED(rv)) {
2744 return rv;
2748 // Doom all active handles
2749 nsTArray<nsRefPtr<CacheFileHandle> > handles;
2750 mHandles.GetActiveHandles(&handles);
2752 for (uint32_t i = 0; i < handles.Length(); ++i) {
2753 rv = DoomFileInternal(handles[i]);
2754 if (NS_WARN_IF(NS_FAILED(rv))) {
2755 LOG(("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
2756 "[handle=%p]", handles[i].get()));
2760 nsCOMPtr<nsIFile> file;
2761 rv = mCacheDirectory->Clone(getter_AddRefs(file));
2762 if (NS_WARN_IF(NS_FAILED(rv))) {
2763 return rv;
2766 rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
2767 if (NS_WARN_IF(NS_FAILED(rv))) {
2768 return rv;
2771 // Trash current entries directory
2772 rv = TrashDirectory(file);
2773 if (NS_WARN_IF(NS_FAILED(rv))) {
2774 return rv;
2777 // Files are now inaccessible in entries directory, notify observers.
2778 NS_DispatchToMainThread(r);
2780 // Create a new empty entries directory
2781 rv = CheckAndCreateDir(mCacheDirectory, kEntriesDir, false);
2782 if (NS_WARN_IF(NS_FAILED(rv))) {
2783 return rv;
2786 CacheIndex::RemoveAll();
2788 return NS_OK;
2791 // static
2792 nsresult
2793 CacheFileIOManager::EvictByContext(nsILoadContextInfo *aLoadContextInfo)
2795 LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
2796 aLoadContextInfo));
2798 nsresult rv;
2799 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
2801 if (!ioMan) {
2802 return NS_ERROR_NOT_INITIALIZED;
2805 nsCOMPtr<nsIRunnable> ev;
2806 ev = NS_NewRunnableMethodWithArg<nsCOMPtr<nsILoadContextInfo> >
2807 (ioMan, &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo);
2809 rv = ioMan->mIOThread->DispatchAfterPendingOpens(ev);
2810 if (NS_WARN_IF(NS_FAILED(rv))) {
2811 return rv;
2814 return NS_OK;
2817 nsresult
2818 CacheFileIOManager::EvictByContextInternal(nsILoadContextInfo *aLoadContextInfo)
2820 LOG(("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
2821 "anonymous=%u, inBrowser=%u, appId=%u]", aLoadContextInfo,
2822 aLoadContextInfo->IsAnonymous(), aLoadContextInfo->IsInBrowserElement(),
2823 aLoadContextInfo->AppId()));
2825 nsresult rv;
2827 MOZ_ASSERT(mIOThread->IsCurrentThread());
2829 MOZ_ASSERT(!aLoadContextInfo->IsPrivate());
2830 if (aLoadContextInfo->IsPrivate()) {
2831 return NS_ERROR_INVALID_ARG;
2834 if (!mCacheDirectory) {
2835 return NS_ERROR_FILE_INVALID_PATH;
2838 if (mShuttingDown) {
2839 return NS_ERROR_NOT_INITIALIZED;
2842 if (!mTreeCreated) {
2843 rv = CreateCacheTree();
2844 if (NS_FAILED(rv)) {
2845 return rv;
2849 // Doom all active handles that matches the load context
2850 nsTArray<nsRefPtr<CacheFileHandle> > handles;
2851 mHandles.GetActiveHandles(&handles);
2853 for (uint32_t i = 0; i < handles.Length(); ++i) {
2854 bool equals;
2855 rv = CacheFileUtils::KeyMatchesLoadContextInfo(handles[i]->Key(),
2856 aLoadContextInfo,
2857 &equals);
2858 if (NS_FAILED(rv)) {
2859 LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
2860 "handle! [handle=%p, key=%s]", handles[i].get(),
2861 handles[i]->Key().get()));
2862 MOZ_CRASH("Unexpected error!");
2865 if (equals) {
2866 rv = DoomFileInternal(handles[i]);
2867 if (NS_WARN_IF(NS_FAILED(rv))) {
2868 LOG(("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
2869 " [handle=%p]", handles[i].get()));
2874 if (!mContextEvictor) {
2875 mContextEvictor = new CacheFileContextEvictor();
2876 mContextEvictor->Init(mCacheDirectory);
2879 mContextEvictor->AddContext(aLoadContextInfo);
2881 return NS_OK;
2884 // static
2885 nsresult
2886 CacheFileIOManager::CacheIndexStateChanged()
2888 LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
2890 nsresult rv;
2892 // CacheFileIOManager lives longer than CacheIndex so gInstance must be
2893 // non-null here.
2894 MOZ_ASSERT(gInstance);
2896 // We have to re-distatch even if we are on IO thread to prevent reentering
2897 // the lock in CacheIndex
2898 nsCOMPtr<nsIRunnable> ev;
2899 ev = NS_NewRunnableMethod(
2900 gInstance, &CacheFileIOManager::CacheIndexStateChangedInternal);
2902 nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
2903 MOZ_ASSERT(ioTarget);
2905 rv = ioTarget->Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
2906 if (NS_WARN_IF(NS_FAILED(rv))) {
2907 return rv;
2910 return NS_OK;
2913 nsresult
2914 CacheFileIOManager::CacheIndexStateChangedInternal()
2916 if (mShuttingDown) {
2917 // ignore notification during shutdown
2918 return NS_OK;
2921 if (!mContextEvictor) {
2922 return NS_OK;
2925 mContextEvictor->CacheIndexStateChanged();
2926 return NS_OK;
2929 nsresult
2930 CacheFileIOManager::TrashDirectory(nsIFile *aFile)
2932 #ifdef PR_LOGGING
2933 nsAutoCString path;
2934 aFile->GetNativePath(path);
2935 #endif
2936 LOG(("CacheFileIOManager::TrashDirectory() [file=%s]", path.get()));
2938 nsresult rv;
2940 MOZ_ASSERT(mIOThread->IsCurrentThread());
2941 MOZ_ASSERT(mCacheDirectory);
2943 // When the directory is empty, it is cheaper to remove it directly instead of
2944 // using the trash mechanism.
2945 bool isEmpty;
2946 rv = IsEmptyDirectory(aFile, &isEmpty);
2947 NS_ENSURE_SUCCESS(rv, rv);
2949 if (isEmpty) {
2950 rv = aFile->Remove(false);
2951 LOG(("CacheFileIOManager::TrashDirectory() - Directory removed [rv=0x%08x]",
2952 rv));
2953 return rv;
2956 #ifdef DEBUG
2957 nsCOMPtr<nsIFile> dirCheck;
2958 rv = aFile->GetParent(getter_AddRefs(dirCheck));
2959 NS_ENSURE_SUCCESS(rv, rv);
2961 bool equals = false;
2962 rv = dirCheck->Equals(mCacheDirectory, &equals);
2963 NS_ENSURE_SUCCESS(rv, rv);
2965 MOZ_ASSERT(equals);
2966 #endif
2968 nsCOMPtr<nsIFile> dir, trash;
2969 nsAutoCString leaf;
2971 rv = aFile->Clone(getter_AddRefs(dir));
2972 NS_ENSURE_SUCCESS(rv, rv);
2974 rv = aFile->Clone(getter_AddRefs(trash));
2975 NS_ENSURE_SUCCESS(rv, rv);
2977 srand(static_cast<unsigned>(PR_Now()));
2978 while (true) {
2979 leaf = kTrashDir;
2980 leaf.AppendInt(rand());
2981 rv = trash->SetNativeLeafName(leaf);
2982 NS_ENSURE_SUCCESS(rv, rv);
2984 bool exists;
2985 if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
2986 break;
2990 LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",
2991 leaf.get()));
2993 rv = dir->MoveToNative(nullptr, leaf);
2994 NS_ENSURE_SUCCESS(rv, rv);
2996 StartRemovingTrash();
2997 return NS_OK;
3000 // static
3001 void
3002 CacheFileIOManager::OnTrashTimer(nsITimer *aTimer, void *aClosure)
3004 LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer,
3005 aClosure));
3007 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
3009 if (!ioMan) {
3010 return;
3013 ioMan->mTrashTimer = nullptr;
3014 ioMan->StartRemovingTrash();
3017 nsresult
3018 CacheFileIOManager::StartRemovingTrash()
3020 LOG(("CacheFileIOManager::StartRemovingTrash()"));
3022 nsresult rv;
3024 MOZ_ASSERT(mIOThread->IsCurrentThread());
3026 if (mShuttingDown) {
3027 return NS_ERROR_NOT_INITIALIZED;
3030 if (!mCacheDirectory) {
3031 return NS_ERROR_FILE_INVALID_PATH;
3034 if (mTrashTimer) {
3035 LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));
3036 return NS_OK;
3039 if (mRemovingTrashDirs) {
3040 LOG(("CacheFileIOManager::StartRemovingTrash() - Trash removing in "
3041 "progress."));
3042 return NS_OK;
3045 uint32_t elapsed = (TimeStamp::NowLoRes() - mStartTime).ToMilliseconds();
3046 if (elapsed < kRemoveTrashStartDelay) {
3047 nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
3048 NS_ENSURE_SUCCESS(rv, rv);
3050 nsCOMPtr<nsIEventTarget> ioTarget = IOTarget();
3051 MOZ_ASSERT(ioTarget);
3053 rv = timer->SetTarget(ioTarget);
3054 NS_ENSURE_SUCCESS(rv, rv);
3056 rv = timer->InitWithFuncCallback(CacheFileIOManager::OnTrashTimer, nullptr,
3057 kRemoveTrashStartDelay - elapsed,
3058 nsITimer::TYPE_ONE_SHOT);
3059 NS_ENSURE_SUCCESS(rv, rv);
3061 mTrashTimer.swap(timer);
3062 return NS_OK;
3065 nsCOMPtr<nsIRunnable> ev;
3066 ev = NS_NewRunnableMethod(this,
3067 &CacheFileIOManager::RemoveTrashInternal);
3069 rv = mIOThread->Dispatch(ev, CacheIOThread::EVICT);
3070 NS_ENSURE_SUCCESS(rv, rv);
3072 mRemovingTrashDirs = true;
3073 return NS_OK;
3076 nsresult
3077 CacheFileIOManager::RemoveTrashInternal()
3079 LOG(("CacheFileIOManager::RemoveTrashInternal()"));
3081 nsresult rv;
3083 MOZ_ASSERT(mIOThread->IsCurrentThread());
3085 if (mShuttingDown) {
3086 return NS_ERROR_NOT_INITIALIZED;
3089 MOZ_ASSERT(!mTrashTimer);
3090 MOZ_ASSERT(mRemovingTrashDirs);
3092 if (!mTreeCreated) {
3093 rv = CreateCacheTree();
3094 if (NS_FAILED(rv)) {
3095 return rv;
3099 // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
3100 // here and set it again once we dispatch a continuation event. By doing so,
3101 // we don't have to drop the flag on any possible early return.
3102 mRemovingTrashDirs = false;
3104 while (true) {
3105 if (CacheIOThread::YieldAndRerun()) {
3106 LOG(("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
3107 "higher level events."));
3108 mRemovingTrashDirs = true;
3109 return NS_OK;
3112 // Find some trash directory
3113 if (!mTrashDir) {
3114 MOZ_ASSERT(!mTrashDirEnumerator);
3116 rv = FindTrashDirToRemove();
3117 if (rv == NS_ERROR_NOT_AVAILABLE) {
3118 LOG(("CacheFileIOManager::RemoveTrashInternal() - No trash directory "
3119 "found."));
3120 return NS_OK;
3122 NS_ENSURE_SUCCESS(rv, rv);
3124 nsCOMPtr<nsISimpleEnumerator> enumerator;
3125 rv = mTrashDir->GetDirectoryEntries(getter_AddRefs(enumerator));
3126 if (NS_SUCCEEDED(rv)) {
3127 mTrashDirEnumerator = do_QueryInterface(enumerator, &rv);
3128 NS_ENSURE_SUCCESS(rv, rv);
3131 continue; // check elapsed time
3134 // We null out mTrashDirEnumerator once we remove all files in the
3135 // directory, so remove the trash directory if we don't have enumerator.
3136 if (!mTrashDirEnumerator) {
3137 rv = mTrashDir->Remove(false);
3138 if (NS_FAILED(rv)) {
3139 // There is no reason why removing an empty directory should fail, but
3140 // if it does, we should continue and try to remove all other trash
3141 // directories.
3142 nsAutoCString leafName;
3143 mTrashDir->GetNativeLeafName(leafName);
3144 mFailedTrashDirs.AppendElement(leafName);
3145 LOG(("CacheFileIOManager::RemoveTrashInternal() - Cannot remove "
3146 "trashdir. [name=%s]", leafName.get()));
3149 mTrashDir = nullptr;
3150 continue; // check elapsed time
3153 nsCOMPtr<nsIFile> file;
3154 rv = mTrashDirEnumerator->GetNextFile(getter_AddRefs(file));
3155 if (!file) {
3156 mTrashDirEnumerator->Close();
3157 mTrashDirEnumerator = nullptr;
3158 continue; // check elapsed time
3159 } else {
3160 bool isDir = false;
3161 file->IsDirectory(&isDir);
3162 if (isDir) {
3163 NS_WARNING("Found a directory in a trash directory! It will be removed "
3164 "recursively, but this can block IO thread for a while!");
3165 #ifdef PR_LOGGING
3166 nsAutoCString path;
3167 file->GetNativePath(path);
3168 #endif
3169 LOG(("CacheFileIOManager::RemoveTrashInternal() - Found a directory in a trash "
3170 "directory! It will be removed recursively, but this can block IO "
3171 "thread for a while! [file=%s]", path.get()));
3173 file->Remove(isDir);
3177 NS_NOTREACHED("We should never get here");
3178 return NS_OK;
3181 nsresult
3182 CacheFileIOManager::FindTrashDirToRemove()
3184 LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
3186 nsresult rv;
3188 // We call this method on the main thread during shutdown when user wants to
3189 // remove all cache files.
3190 MOZ_ASSERT(mIOThread->IsCurrentThread() || mShuttingDown);
3192 nsCOMPtr<nsISimpleEnumerator> iter;
3193 rv = mCacheDirectory->GetDirectoryEntries(getter_AddRefs(iter));
3194 NS_ENSURE_SUCCESS(rv, rv);
3196 bool more;
3197 nsCOMPtr<nsISupports> elem;
3199 while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
3200 rv = iter->GetNext(getter_AddRefs(elem));
3201 if (NS_FAILED(rv)) {
3202 continue;
3205 nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
3206 if (!file) {
3207 continue;
3210 bool isDir = false;
3211 file->IsDirectory(&isDir);
3212 if (!isDir) {
3213 continue;
3216 nsAutoCString leafName;
3217 rv = file->GetNativeLeafName(leafName);
3218 if (NS_FAILED(rv)) {
3219 continue;
3222 if (leafName.Length() < strlen(kTrashDir)) {
3223 continue;
3226 if (!StringBeginsWith(leafName, NS_LITERAL_CSTRING(kTrashDir))) {
3227 continue;
3230 if (mFailedTrashDirs.Contains(leafName)) {
3231 continue;
3234 LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",
3235 leafName.get()));
3237 mTrashDir = file;
3238 return NS_OK;
3241 // When we're here we've tried to delete all trash directories. Clear
3242 // mFailedTrashDirs so we will try to delete them again when we start removing
3243 // trash directories next time.
3244 mFailedTrashDirs.Clear();
3245 return NS_ERROR_NOT_AVAILABLE;
3248 // static
3249 nsresult
3250 CacheFileIOManager::InitIndexEntry(CacheFileHandle *aHandle,
3251 uint32_t aAppId,
3252 bool aAnonymous,
3253 bool aInBrowser)
3255 LOG(("CacheFileIOManager::InitIndexEntry() [handle=%p, appId=%u, anonymous=%d"
3256 ", inBrowser=%d]", aHandle, aAppId, aAnonymous, aInBrowser));
3258 nsresult rv;
3259 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
3261 if (aHandle->IsClosed() || !ioMan) {
3262 return NS_ERROR_NOT_INITIALIZED;
3265 if (aHandle->IsSpecialFile()) {
3266 return NS_ERROR_UNEXPECTED;
3269 nsRefPtr<InitIndexEntryEvent> ev =
3270 new InitIndexEntryEvent(aHandle, aAppId, aAnonymous, aInBrowser);
3271 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
3272 NS_ENSURE_SUCCESS(rv, rv);
3274 return NS_OK;
3277 // static
3278 nsresult
3279 CacheFileIOManager::UpdateIndexEntry(CacheFileHandle *aHandle,
3280 const uint32_t *aFrecency,
3281 const uint32_t *aExpirationTime)
3283 LOG(("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
3284 "expirationTime=%s]", aHandle,
3285 aFrecency ? nsPrintfCString("%u", *aFrecency).get() : "",
3286 aExpirationTime ? nsPrintfCString("%u", *aExpirationTime).get() : ""));
3288 nsresult rv;
3289 nsRefPtr<CacheFileIOManager> ioMan = gInstance;
3291 if (aHandle->IsClosed() || !ioMan) {
3292 return NS_ERROR_NOT_INITIALIZED;
3295 if (aHandle->IsSpecialFile()) {
3296 return NS_ERROR_UNEXPECTED;
3299 nsRefPtr<UpdateIndexEntryEvent> ev =
3300 new UpdateIndexEntryEvent(aHandle, aFrecency, aExpirationTime);
3301 rv = ioMan->mIOThread->Dispatch(ev, CacheIOThread::WRITE);
3302 NS_ENSURE_SUCCESS(rv, rv);
3304 return NS_OK;
3307 nsresult
3308 CacheFileIOManager::CreateFile(CacheFileHandle *aHandle)
3310 MOZ_ASSERT(!aHandle->mFD);
3311 MOZ_ASSERT(aHandle->mFile);
3313 nsresult rv;
3315 if (aHandle->IsDoomed()) {
3316 nsCOMPtr<nsIFile> file;
3318 rv = GetDoomedFile(getter_AddRefs(file));
3319 NS_ENSURE_SUCCESS(rv, rv);
3321 aHandle->mFile.swap(file);
3322 } else {
3323 bool exists;
3324 if (NS_SUCCEEDED(aHandle->mFile->Exists(&exists)) && exists) {
3325 NS_WARNING("Found a file that should not exist!");
3329 rv = OpenNSPRHandle(aHandle, true);
3330 NS_ENSURE_SUCCESS(rv, rv);
3332 aHandle->mFileSize = 0;
3333 return NS_OK;
3336 // static
3337 void
3338 CacheFileIOManager::HashToStr(const SHA1Sum::Hash *aHash, nsACString &_retval)
3340 _retval.Truncate();
3341 const char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7',
3342 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
3343 for (uint32_t i=0 ; i<sizeof(SHA1Sum::Hash) ; i++) {
3344 _retval.Append(hexChars[(*aHash)[i] >> 4]);
3345 _retval.Append(hexChars[(*aHash)[i] & 0xF]);
3349 // static
3350 nsresult
3351 CacheFileIOManager::StrToHash(const nsACString &aHash, SHA1Sum::Hash *_retval)
3353 if (aHash.Length() != 2*sizeof(SHA1Sum::Hash)) {
3354 return NS_ERROR_INVALID_ARG;
3357 for (uint32_t i=0 ; i<aHash.Length() ; i++) {
3358 uint8_t value;
3360 if (aHash[i] >= '0' && aHash[i] <= '9') {
3361 value = aHash[i] - '0';
3362 } else if (aHash[i] >= 'A' && aHash[i] <= 'F') {
3363 value = aHash[i] - 'A' + 10;
3364 } else if (aHash[i] >= 'a' && aHash[i] <= 'f') {
3365 value = aHash[i] - 'a' + 10;
3366 } else {
3367 return NS_ERROR_INVALID_ARG;
3370 if (i%2 == 0) {
3371 (reinterpret_cast<uint8_t *>(_retval))[i/2] = value << 4;
3372 } else {
3373 (reinterpret_cast<uint8_t *>(_retval))[i/2] += value;
3377 return NS_OK;
3380 nsresult
3381 CacheFileIOManager::GetFile(const SHA1Sum::Hash *aHash, nsIFile **_retval)
3383 nsresult rv;
3384 nsCOMPtr<nsIFile> file;
3385 rv = mCacheDirectory->Clone(getter_AddRefs(file));
3386 NS_ENSURE_SUCCESS(rv, rv);
3388 rv = file->AppendNative(NS_LITERAL_CSTRING(kEntriesDir));
3389 NS_ENSURE_SUCCESS(rv, rv);
3391 nsAutoCString leafName;
3392 HashToStr(aHash, leafName);
3394 rv = file->AppendNative(leafName);
3395 NS_ENSURE_SUCCESS(rv, rv);
3397 file.swap(*_retval);
3398 return NS_OK;
3401 nsresult
3402 CacheFileIOManager::GetSpecialFile(const nsACString &aKey, nsIFile **_retval)
3404 nsresult rv;
3405 nsCOMPtr<nsIFile> file;
3406 rv = mCacheDirectory->Clone(getter_AddRefs(file));
3407 NS_ENSURE_SUCCESS(rv, rv);
3409 rv = file->AppendNative(aKey);
3410 NS_ENSURE_SUCCESS(rv, rv);
3412 file.swap(*_retval);
3413 return NS_OK;
3416 nsresult
3417 CacheFileIOManager::GetDoomedFile(nsIFile **_retval)
3419 nsresult rv;
3420 nsCOMPtr<nsIFile> file;
3421 rv = mCacheDirectory->Clone(getter_AddRefs(file));
3422 NS_ENSURE_SUCCESS(rv, rv);
3424 rv = file->AppendNative(NS_LITERAL_CSTRING(kDoomedDir));
3425 NS_ENSURE_SUCCESS(rv, rv);
3427 rv = file->AppendNative(NS_LITERAL_CSTRING("dummyleaf"));
3428 NS_ENSURE_SUCCESS(rv, rv);
3430 srand(static_cast<unsigned>(PR_Now()));
3431 nsAutoCString leafName;
3432 uint32_t iter=0;
3433 while (true) {
3434 iter++;
3435 leafName.AppendInt(rand());
3436 rv = file->SetNativeLeafName(leafName);
3437 NS_ENSURE_SUCCESS(rv, rv);
3439 bool exists;
3440 if (NS_SUCCEEDED(file->Exists(&exists)) && !exists) {
3441 break;
3444 leafName.Truncate();
3447 // Telemetry::Accumulate(Telemetry::DISK_CACHE_GETDOOMEDFILE_ITERATIONS, iter);
3449 file.swap(*_retval);
3450 return NS_OK;
3453 nsresult
3454 CacheFileIOManager::IsEmptyDirectory(nsIFile *aFile, bool *_retval)
3456 MOZ_ASSERT(mIOThread->IsCurrentThread());
3458 nsresult rv;
3460 nsCOMPtr<nsISimpleEnumerator> enumerator;
3461 rv = aFile->GetDirectoryEntries(getter_AddRefs(enumerator));
3462 NS_ENSURE_SUCCESS(rv, rv);
3464 bool hasMoreElements = false;
3465 rv = enumerator->HasMoreElements(&hasMoreElements);
3466 NS_ENSURE_SUCCESS(rv, rv);
3468 *_retval = !hasMoreElements;
3469 return NS_OK;
3472 nsresult
3473 CacheFileIOManager::CheckAndCreateDir(nsIFile *aFile, const char *aDir,
3474 bool aEnsureEmptyDir)
3476 nsresult rv;
3478 nsCOMPtr<nsIFile> file;
3479 if (!aDir) {
3480 file = aFile;
3481 } else {
3482 nsAutoCString dir(aDir);
3483 rv = aFile->Clone(getter_AddRefs(file));
3484 NS_ENSURE_SUCCESS(rv, rv);
3485 rv = file->AppendNative(dir);
3486 NS_ENSURE_SUCCESS(rv, rv);
3489 bool exists = false;
3490 rv = file->Exists(&exists);
3491 if (NS_SUCCEEDED(rv) && exists) {
3492 bool isDirectory = false;
3493 rv = file->IsDirectory(&isDirectory);
3494 if (NS_FAILED(rv) || !isDirectory) {
3495 // Try to remove the file
3496 rv = file->Remove(false);
3497 if (NS_SUCCEEDED(rv)) {
3498 exists = false;
3501 NS_ENSURE_SUCCESS(rv, rv);
3504 if (aEnsureEmptyDir && NS_SUCCEEDED(rv) && exists) {
3505 bool isEmpty;
3506 rv = IsEmptyDirectory(file, &isEmpty);
3507 NS_ENSURE_SUCCESS(rv, rv);
3509 if (!isEmpty) {
3510 rv = TrashDirectory(file);
3511 NS_ENSURE_SUCCESS(rv, rv);
3513 exists = false;
3517 if (NS_SUCCEEDED(rv) && !exists) {
3518 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
3520 if (NS_FAILED(rv)) {
3521 NS_WARNING("Cannot create directory");
3522 return NS_ERROR_FAILURE;
3525 return NS_OK;
3528 nsresult
3529 CacheFileIOManager::CreateCacheTree()
3531 MOZ_ASSERT(mIOThread->IsCurrentThread());
3532 MOZ_ASSERT(!mTreeCreated);
3534 if (!mCacheDirectory) {
3535 return NS_ERROR_FILE_INVALID_PATH;
3538 nsresult rv;
3540 // ensure parent directory exists
3541 nsCOMPtr<nsIFile> parentDir;
3542 rv = mCacheDirectory->GetParent(getter_AddRefs(parentDir));
3543 NS_ENSURE_SUCCESS(rv, rv);
3544 rv = CheckAndCreateDir(parentDir, nullptr, false);
3545 NS_ENSURE_SUCCESS(rv, rv);
3547 // ensure cache directory exists
3548 rv = CheckAndCreateDir(mCacheDirectory, nullptr, false);
3549 NS_ENSURE_SUCCESS(rv, rv);
3551 // ensure entries directory exists
3552 rv = CheckAndCreateDir(mCacheDirectory, kEntriesDir, false);
3553 NS_ENSURE_SUCCESS(rv, rv);
3555 // ensure doomed directory exists
3556 rv = CheckAndCreateDir(mCacheDirectory, kDoomedDir, true);
3557 NS_ENSURE_SUCCESS(rv, rv);
3559 mTreeCreated = true;
3561 if (!mContextEvictor) {
3562 nsRefPtr<CacheFileContextEvictor> contextEvictor;
3563 contextEvictor = new CacheFileContextEvictor();
3565 // Init() method will try to load unfinished contexts from the disk. Store
3566 // the evictor as a member only when there is some unfinished job.
3567 contextEvictor->Init(mCacheDirectory);
3568 if (contextEvictor->ContextsCount()) {
3569 contextEvictor.swap(mContextEvictor);
3573 StartRemovingTrash();
3575 return NS_OK;
3578 nsresult
3579 CacheFileIOManager::OpenNSPRHandle(CacheFileHandle *aHandle, bool aCreate)
3581 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3582 MOZ_ASSERT(!aHandle->mFD);
3583 MOZ_ASSERT(mHandlesByLastUsed.IndexOf(aHandle) == mHandlesByLastUsed.NoIndex);
3584 MOZ_ASSERT(mHandlesByLastUsed.Length() <= kOpenHandlesLimit);
3585 MOZ_ASSERT((aCreate && !aHandle->mFileExists) ||
3586 (!aCreate && aHandle->mFileExists));
3588 nsresult rv;
3590 if (mHandlesByLastUsed.Length() == kOpenHandlesLimit) {
3591 // close handle that hasn't been used for the longest time
3592 rv = ReleaseNSPRHandleInternal(mHandlesByLastUsed[0]);
3593 NS_ENSURE_SUCCESS(rv, rv);
3596 if (aCreate) {
3597 rv = aHandle->mFile->OpenNSPRFileDesc(
3598 PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0600, &aHandle->mFD);
3599 NS_ENSURE_SUCCESS(rv, rv);
3601 aHandle->mFileExists = true;
3602 } else {
3603 rv = aHandle->mFile->OpenNSPRFileDesc(PR_RDWR, 0600, &aHandle->mFD);
3604 if (NS_ERROR_FILE_NOT_FOUND == rv) {
3605 LOG((" file doesn't exists"));
3606 aHandle->mFileExists = false;
3607 return DoomFileInternal(aHandle);
3609 NS_ENSURE_SUCCESS(rv, rv);
3612 mHandlesByLastUsed.AppendElement(aHandle);
3613 return NS_OK;
3616 void
3617 CacheFileIOManager::NSPRHandleUsed(CacheFileHandle *aHandle)
3619 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3620 MOZ_ASSERT(aHandle->mFD);
3622 DebugOnly<bool> found;
3623 found = mHandlesByLastUsed.RemoveElement(aHandle);
3624 MOZ_ASSERT(found);
3626 mHandlesByLastUsed.AppendElement(aHandle);
3629 nsresult
3630 CacheFileIOManager::SyncRemoveDir(nsIFile *aFile, const char *aDir)
3632 nsresult rv;
3633 nsCOMPtr<nsIFile> file;
3635 if (!aDir) {
3636 file = aFile;
3637 } else {
3638 rv = aFile->Clone(getter_AddRefs(file));
3639 if (NS_WARN_IF(NS_FAILED(rv))) {
3640 return rv;
3643 rv = file->AppendNative(nsDependentCString(aDir));
3644 if (NS_WARN_IF(NS_FAILED(rv))) {
3645 return rv;
3649 #ifdef PR_LOGGING
3650 nsAutoCString path;
3651 file->GetNativePath(path);
3652 #endif
3654 LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
3655 path.get()));
3657 rv = file->Remove(true);
3658 if (NS_WARN_IF(NS_FAILED(rv))) {
3659 LOG(("CacheFileIOManager::SyncRemoveDir() - Removing failed! [rv=0x%08x]",
3660 rv));
3663 return rv;
3666 void
3667 CacheFileIOManager::SyncRemoveAllCacheFiles()
3669 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
3671 nsresult rv;
3673 SyncRemoveDir(mCacheDirectory, kEntriesDir);
3674 SyncRemoveDir(mCacheDirectory, kDoomedDir);
3676 // Clear any intermediate state of trash dir enumeration.
3677 mFailedTrashDirs.Clear();
3678 mTrashDir = nullptr;
3680 while (true) {
3681 // FindTrashDirToRemove() fills mTrashDir if there is any trash directory.
3682 rv = FindTrashDirToRemove();
3683 if (rv == NS_ERROR_NOT_AVAILABLE) {
3684 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory "
3685 "found."));
3686 break;
3688 if (NS_WARN_IF(NS_FAILED(rv))) {
3689 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles() - "
3690 "FindTrashDirToRemove() returned an unexpected error. [rv=0x%08x]",
3691 rv));
3692 break;
3695 rv = SyncRemoveDir(mTrashDir, nullptr);
3696 if (NS_FAILED(rv)) {
3697 nsAutoCString leafName;
3698 mTrashDir->GetNativeLeafName(leafName);
3699 mFailedTrashDirs.AppendElement(leafName);
3704 // Returns default ("smart") size (in KB) of cache, given available disk space
3705 // (also in KB)
3706 static uint32_t
3707 SmartCacheSize(const uint32_t availKB)
3709 uint32_t maxSize = kMaxCacheSizeKB;
3711 if (availKB > 100 * 1024 * 1024) {
3712 return maxSize; // skip computing if we're over 100 GB
3715 // Grow/shrink in 10 MB units, deliberately, so that in the common case we
3716 // don't shrink cache and evict items every time we startup (it's important
3717 // that we don't slow down startup benchmarks).
3718 uint32_t sz10MBs = 0;
3719 uint32_t avail10MBs = availKB / (1024*10);
3721 // .5% of space above 25 GB
3722 if (avail10MBs > 2500) {
3723 sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
3724 avail10MBs = 2500;
3726 // 1% of space between 7GB -> 25 GB
3727 if (avail10MBs > 700) {
3728 sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
3729 avail10MBs = 700;
3731 // 5% of space between 500 MB -> 7 GB
3732 if (avail10MBs > 50) {
3733 sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
3734 avail10MBs = 50;
3737 #ifdef ANDROID
3738 // On Android, smaller/older devices may have very little storage and
3739 // device owners may be sensitive to storage footprint: Use a smaller
3740 // percentage of available space and a smaller minimum.
3742 // 20% of space up to 500 MB (10 MB min)
3743 sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
3744 #else
3745 // 40% of space up to 500 MB (50 MB min)
3746 sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
3747 #endif
3749 return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
3752 nsresult
3753 CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace)
3755 MOZ_ASSERT(mIOThread->IsCurrentThread());
3757 nsresult rv;
3759 if (!CacheObserver::UseNewCache()) {
3760 return NS_ERROR_NOT_AVAILABLE;
3763 if (!CacheObserver::SmartCacheSizeEnabled()) {
3764 return NS_ERROR_NOT_AVAILABLE;
3767 // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
3768 static const TimeDuration kUpdateLimit =
3769 TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval);
3770 if (!mLastSmartSizeTime.IsNull() &&
3771 (TimeStamp::NowLoRes() - mLastSmartSizeTime) < kUpdateLimit) {
3772 return NS_OK;
3775 // Do not compute smart size when cache size is not reliable.
3776 bool isUpToDate = false;
3777 CacheIndex::IsUpToDate(&isUpToDate);
3778 if (!isUpToDate) {
3779 return NS_ERROR_NOT_AVAILABLE;
3782 uint32_t cacheUsage;
3783 rv = CacheIndex::GetCacheSize(&cacheUsage);
3784 if (NS_WARN_IF(NS_FAILED(rv))) {
3785 LOG(("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! "
3786 "[rv=0x%08x]", rv));
3787 return rv;
3790 mLastSmartSizeTime = TimeStamp::NowLoRes();
3792 uint32_t smartSize = SmartCacheSize(static_cast<uint32_t>(aFreeSpace / 1024) +
3793 cacheUsage);
3795 if (smartSize == (CacheObserver::DiskCacheCapacity() >> 10)) {
3796 // Smart size has not changed.
3797 return NS_OK;
3800 CacheObserver::SetDiskCacheCapacity(smartSize << 10);
3802 return NS_OK;
3805 // Memory reporting
3807 namespace { // anon
3809 // A helper class that dispatches and waits for an event that gets result of
3810 // CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread
3811 // to safely get handles memory report.
3812 // We must do this, since the handle list is only accessed and managed w/o
3813 // locking on the I/O thread. That is by design.
3814 class SizeOfHandlesRunnable : public nsRunnable
3816 public:
3817 SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf,
3818 CacheFileHandles const &handles,
3819 nsTArray<CacheFileHandle *> const &specialHandles)
3820 : mMonitor("SizeOfHandlesRunnable.mMonitor")
3821 , mMallocSizeOf(mallocSizeOf)
3822 , mHandles(handles)
3823 , mSpecialHandles(specialHandles)
3827 size_t Get(CacheIOThread* thread)
3829 nsCOMPtr<nsIEventTarget> target = thread->Target();
3830 if (!target) {
3831 NS_ERROR("If we have the I/O thread we also must have the I/O target");
3832 return 0;
3835 mozilla::MonitorAutoLock mon(mMonitor);
3836 nsresult rv = target->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
3837 if (NS_FAILED(rv)) {
3838 NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");
3839 return 0;
3842 mon.Wait();
3843 return mSize;
3846 NS_IMETHOD Run()
3848 mozilla::MonitorAutoLock mon(mMonitor);
3849 // Excluding this since the object itself is a member of CacheFileIOManager
3850 // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.
3851 mSize = mHandles.SizeOfExcludingThis(mMallocSizeOf);
3852 for (uint32_t i = 0; i < mSpecialHandles.Length(); ++i) {
3853 mSize += mSpecialHandles[i]->SizeOfIncludingThis(mMallocSizeOf);
3856 mon.Notify();
3857 return NS_OK;
3860 private:
3861 mozilla::Monitor mMonitor;
3862 mozilla::MallocSizeOf mMallocSizeOf;
3863 CacheFileHandles const &mHandles;
3864 nsTArray<CacheFileHandle *> const &mSpecialHandles;
3865 size_t mSize;
3868 } // anon
3870 size_t
3871 CacheFileIOManager::SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const
3873 size_t n = 0;
3874 nsCOMPtr<nsISizeOf> sizeOf;
3876 if (mIOThread) {
3877 n += mIOThread->SizeOfIncludingThis(mallocSizeOf);
3879 // mHandles and mSpecialHandles must be accessed only on the I/O thread,
3880 // must sync dispatch.
3881 nsRefPtr<SizeOfHandlesRunnable> sizeOfHandlesRunnable =
3882 new SizeOfHandlesRunnable(mallocSizeOf, mHandles, mSpecialHandles);
3883 n += sizeOfHandlesRunnable->Get(mIOThread);
3886 // mHandlesByLastUsed just refers handles reported by mHandles.
3888 sizeOf = do_QueryInterface(mCacheDirectory);
3889 if (sizeOf)
3890 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3892 sizeOf = do_QueryInterface(mMetadataWritesTimer);
3893 if (sizeOf)
3894 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3896 sizeOf = do_QueryInterface(mTrashTimer);
3897 if (sizeOf)
3898 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3900 sizeOf = do_QueryInterface(mTrashDir);
3901 if (sizeOf)
3902 n += sizeOf->SizeOfIncludingThis(mallocSizeOf);
3904 for (uint32_t i = 0; i < mFailedTrashDirs.Length(); ++i) {
3905 n += mFailedTrashDirs[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
3908 return n;
3911 // static
3912 size_t
3913 CacheFileIOManager::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
3915 if (!gInstance)
3916 return 0;
3918 return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
3921 // static
3922 size_t
3923 CacheFileIOManager::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
3925 return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
3928 } // net
3929 } // mozilla