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/. */
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"
17 #include "CacheFileContextEvictor.h"
19 #include "nsIDirectoryEnumerator.h"
20 #include "nsIObserverService.h"
21 #include "nsISizeOf.h"
22 #include "mozilla/net/MozURL.h"
23 #include "mozilla/Telemetry.h"
24 #include "mozilla/DebugOnly.h"
25 #include "mozilla/Services.h"
26 #include "nsDirectoryServiceUtils.h"
27 #include "nsAppDirectoryServiceDefs.h"
28 #include "private/pprio.h"
29 #include "mozilla/IntegerPrintfMacros.h"
30 #include "mozilla/Preferences.h"
31 #include "nsNetUtil.h"
33 // include files for ftruncate (or equivalent)
41 // XXX add necessary include file for ftruncate (or equivalent)
47 #define kOpenHandlesLimit 128
48 #define kMetadataWriteDelay 5000
49 #define kRemoveTrashStartDelay 60000 // in milliseconds
50 #define kSmartSizeUpdateInterval 60000 // in milliseconds
53 const uint32_t kMaxCacheSizeKB
= 512 * 1024; // 512 MB
55 const uint32_t kMaxCacheSizeKB
= 1024 * 1024; // 1 GB
57 const uint32_t kMaxClearOnShutdownCacheSizeKB
= 150 * 1024; // 150 MB
59 bool CacheFileHandle::DispatchRelease() {
60 if (CacheFileIOManager::IsOnIOThreadOrCeased()) {
64 nsCOMPtr
<nsIEventTarget
> ioTarget
= CacheFileIOManager::IOTarget();
69 nsresult rv
= ioTarget
->Dispatch(
70 NewNonOwningRunnableMethod("net::CacheFileHandle::Release", this,
71 &CacheFileHandle::Release
),
72 nsIEventTarget::DISPATCH_NORMAL
);
80 NS_IMPL_ADDREF(CacheFileHandle
)
81 NS_IMETHODIMP_(MozExternalRefCountType
)
82 CacheFileHandle::Release() {
83 nsrefcnt count
= mRefCnt
- 1;
84 if (DispatchRelease()) {
85 // Redispatched to the IO thread.
89 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
91 LOG(("CacheFileHandle::Release() [this=%p, refcnt=%" PRIuPTR
"]", this,
93 MOZ_ASSERT(0 != mRefCnt
, "dup release");
95 NS_LOG_RELEASE(this, count
, "CacheFileHandle");
106 NS_INTERFACE_MAP_BEGIN(CacheFileHandle
)
107 NS_INTERFACE_MAP_ENTRY(nsISupports
)
110 CacheFileHandle::CacheFileHandle(const SHA1Sum::Hash
* aHash
, bool aPriority
,
111 PinningStatus aPinning
)
115 mPriority(aPriority
),
119 mDoomWhenFoundPinned(false),
120 mDoomWhenFoundNonPinned(false),
125 // If we initialize mDoomed in the initialization list, that initialization is
126 // not guaranteeded to be atomic. Whereas this assignment here is guaranteed
127 // to be atomic. TSan will see this (atomic) assignment and be satisfied
128 // that cross-thread accesses to mIsDoomed are properly synchronized.
131 "CacheFileHandle::CacheFileHandle() [this=%p, hash=%08x%08x%08x%08x%08x]",
132 this, LOGSHA1(aHash
)));
135 CacheFileHandle::CacheFileHandle(const nsACString
& aKey
, bool aPriority
,
136 PinningStatus aPinning
)
140 mPriority(aPriority
),
144 mDoomWhenFoundPinned(false),
145 mDoomWhenFoundNonPinned(false),
151 // See comment above about the initialization of mIsDoomed.
153 LOG(("CacheFileHandle::CacheFileHandle() [this=%p, key=%s]", this,
154 PromiseFlatCString(aKey
).get()));
157 CacheFileHandle::~CacheFileHandle() {
158 LOG(("CacheFileHandle::~CacheFileHandle() [this=%p]", this));
160 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
162 RefPtr
<CacheFileIOManager
> ioMan
= CacheFileIOManager::gInstance
;
163 if (!IsClosed() && ioMan
) {
164 ioMan
->CloseHandleInternal(this);
168 void CacheFileHandle::Log() {
169 nsAutoCString leafName
;
171 mFile
->GetNativeLeafName(leafName
);
176 ("CacheFileHandle::Log() - special file [this=%p, "
177 "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
178 "pinning=%" PRIu32
", fileExists=%d, fileSize=%" PRId64
179 ", leafName=%s, key=%s]",
180 this, bool(mIsDoomed
), bool(mPriority
), bool(mClosed
), bool(mInvalid
),
181 static_cast<uint32_t>(mPinning
), bool(mFileExists
), int64_t(mFileSize
),
182 leafName
.get(), mKey
.get()));
185 ("CacheFileHandle::Log() - entry file [this=%p, "
186 "hash=%08x%08x%08x%08x%08x, "
187 "isDoomed=%d, priority=%d, closed=%d, invalid=%d, "
188 "pinning=%" PRIu32
", fileExists=%d, fileSize=%" PRId64
189 ", leafName=%s, key=%s]",
190 this, LOGSHA1(mHash
), bool(mIsDoomed
), bool(mPriority
), bool(mClosed
),
191 bool(mInvalid
), static_cast<uint32_t>(mPinning
), bool(mFileExists
),
192 int64_t(mFileSize
), leafName
.get(), mKey
.get()));
196 uint32_t CacheFileHandle::FileSizeInK() const {
197 MOZ_ASSERT(mFileSize
!= -1);
198 uint64_t size64
= mFileSize
;
206 "CacheFileHandle::FileSizeInK() - FileSize is too large, "
207 "truncating to PR_UINT32_MAX");
208 size
= PR_UINT32_MAX
;
210 size
= static_cast<uint32_t>(size64
);
216 bool CacheFileHandle::SetPinned(bool aPinned
) {
217 LOG(("CacheFileHandle::SetPinned [this=%p, pinned=%d]", this, aPinned
));
219 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
221 mPinning
= aPinned
? PinningStatus::PINNED
: PinningStatus::NON_PINNED
;
223 if ((MOZ_UNLIKELY(mDoomWhenFoundPinned
) && aPinned
) ||
224 (MOZ_UNLIKELY(mDoomWhenFoundNonPinned
) && !aPinned
)) {
225 LOG((" dooming, when: pinned=%d, non-pinned=%d, found: pinned=%d",
226 bool(mDoomWhenFoundPinned
), bool(mDoomWhenFoundNonPinned
), aPinned
));
228 mDoomWhenFoundPinned
= false;
229 mDoomWhenFoundNonPinned
= false;
239 size_t CacheFileHandle::SizeOfExcludingThis(
240 mozilla::MallocSizeOf mallocSizeOf
) const {
242 nsCOMPtr
<nsISizeOf
> sizeOf
;
244 sizeOf
= do_QueryInterface(mFile
);
246 n
+= sizeOf
->SizeOfIncludingThis(mallocSizeOf
);
249 n
+= mallocSizeOf(mFD
);
250 n
+= mKey
.SizeOfExcludingThisIfUnshared(mallocSizeOf
);
254 size_t CacheFileHandle::SizeOfIncludingThis(
255 mozilla::MallocSizeOf mallocSizeOf
) const {
256 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf
);
259 /******************************************************************************
260 * CacheFileHandles::HandleHashKey
261 *****************************************************************************/
263 void CacheFileHandles::HandleHashKey::AddHandle(CacheFileHandle
* aHandle
) {
264 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
266 mHandles
.InsertElementAt(0, aHandle
);
269 void CacheFileHandles::HandleHashKey::RemoveHandle(CacheFileHandle
* aHandle
) {
270 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
272 DebugOnly
<bool> found
;
273 found
= mHandles
.RemoveElement(aHandle
);
277 already_AddRefed
<CacheFileHandle
>
278 CacheFileHandles::HandleHashKey::GetNewestHandle() {
279 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
281 RefPtr
<CacheFileHandle
> handle
;
282 if (mHandles
.Length()) {
283 handle
= mHandles
[0];
286 return handle
.forget();
289 void CacheFileHandles::HandleHashKey::GetHandles(
290 nsTArray
<RefPtr
<CacheFileHandle
> >& aResult
) {
291 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
293 for (uint32_t i
= 0; i
< mHandles
.Length(); ++i
) {
294 CacheFileHandle
* handle
= mHandles
[i
];
295 aResult
.AppendElement(handle
);
301 void CacheFileHandles::HandleHashKey::AssertHandlesState() {
302 for (uint32_t i
= 0; i
< mHandles
.Length(); ++i
) {
303 CacheFileHandle
* handle
= mHandles
[i
];
304 MOZ_ASSERT(handle
->IsDoomed());
310 size_t CacheFileHandles::HandleHashKey::SizeOfExcludingThis(
311 mozilla::MallocSizeOf mallocSizeOf
) const {
312 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
315 n
+= mallocSizeOf(mHash
.get());
316 for (uint32_t i
= 0; i
< mHandles
.Length(); ++i
) {
317 n
+= mHandles
[i
]->SizeOfIncludingThis(mallocSizeOf
);
323 /******************************************************************************
325 *****************************************************************************/
327 CacheFileHandles::CacheFileHandles() {
328 LOG(("CacheFileHandles::CacheFileHandles() [this=%p]", this));
329 MOZ_COUNT_CTOR(CacheFileHandles
);
332 CacheFileHandles::~CacheFileHandles() {
333 LOG(("CacheFileHandles::~CacheFileHandles() [this=%p]", this));
334 MOZ_COUNT_DTOR(CacheFileHandles
);
337 nsresult
CacheFileHandles::GetHandle(const SHA1Sum::Hash
* aHash
,
338 CacheFileHandle
** _retval
) {
339 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
343 LOG(("CacheFileHandles::GetHandle() [hash=%08x%08x%08x%08x%08x]",
347 // find hash entry for key
348 HandleHashKey
* entry
= mTable
.GetEntry(*aHash
);
351 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
352 "no handle entries found",
354 return NS_ERROR_NOT_AVAILABLE
;
361 // Check if the entry is doomed
362 RefPtr
<CacheFileHandle
> handle
= entry
->GetNewestHandle();
365 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
366 "no handle found %p, entry %p",
367 LOGSHA1(aHash
), handle
.get(), entry
));
368 return NS_ERROR_NOT_AVAILABLE
;
371 if (handle
->IsDoomed()) {
373 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
374 "found doomed handle %p, entry %p",
375 LOGSHA1(aHash
), handle
.get(), entry
));
376 return NS_ERROR_NOT_AVAILABLE
;
380 ("CacheFileHandles::GetHandle() hash=%08x%08x%08x%08x%08x "
381 "found handle %p, entry %p",
382 LOGSHA1(aHash
), handle
.get(), entry
));
384 handle
.forget(_retval
);
388 already_AddRefed
<CacheFileHandle
> CacheFileHandles::NewHandle(
389 const SHA1Sum::Hash
* aHash
, bool aPriority
,
390 CacheFileHandle::PinningStatus aPinning
) {
391 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
395 LOG(("CacheFileHandles::NewHandle() [hash=%08x%08x%08x%08x%08x]",
399 // find hash entry for key
400 HandleHashKey
* entry
= mTable
.PutEntry(*aHash
);
407 entry
->AssertHandlesState();
410 RefPtr
<CacheFileHandle
> handle
=
411 new CacheFileHandle(entry
->Hash(), aPriority
, aPinning
);
412 entry
->AddHandle(handle
);
415 ("CacheFileHandles::NewHandle() hash=%08x%08x%08x%08x%08x "
416 "created new handle %p, entry=%p",
417 LOGSHA1(aHash
), handle
.get(), entry
));
418 return handle
.forget();
421 void CacheFileHandles::RemoveHandle(CacheFileHandle
* aHandle
) {
422 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
431 "CacheFileHandles::RemoveHandle() [handle=%p, hash=%08x%08x%08x%08x%08x]",
432 aHandle
, LOGSHA1(aHandle
->Hash())));
435 // find hash entry for key
436 HandleHashKey
* entry
= mTable
.GetEntry(*aHandle
->Hash());
438 MOZ_ASSERT(CacheFileIOManager::IsShutdown(),
439 "Should find entry when removing a handle before shutdown");
442 ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
444 LOGSHA1(aHandle
->Hash())));
453 ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
454 "removing handle %p",
455 LOGSHA1(entry
->Hash()), aHandle
));
456 entry
->RemoveHandle(aHandle
);
458 if (entry
->IsEmpty()) {
460 ("CacheFileHandles::RemoveHandle() hash=%08x%08x%08x%08x%08x "
461 "list is empty, removing entry %p",
462 LOGSHA1(entry
->Hash()), entry
));
463 mTable
.RemoveEntry(entry
);
467 void CacheFileHandles::GetAllHandles(
468 nsTArray
<RefPtr
<CacheFileHandle
> >* _retval
) {
469 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
470 for (auto iter
= mTable
.Iter(); !iter
.Done(); iter
.Next()) {
471 iter
.Get()->GetHandles(*_retval
);
475 void CacheFileHandles::GetActiveHandles(
476 nsTArray
<RefPtr
<CacheFileHandle
> >* _retval
) {
477 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
478 for (auto iter
= mTable
.Iter(); !iter
.Done(); iter
.Next()) {
479 RefPtr
<CacheFileHandle
> handle
= iter
.Get()->GetNewestHandle();
482 if (!handle
->IsDoomed()) {
483 _retval
->AppendElement(handle
);
488 void CacheFileHandles::ClearAll() {
489 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
493 uint32_t CacheFileHandles::HandleCount() { return mTable
.Count(); }
496 void CacheFileHandles::Log(CacheFileHandlesEntry
* entry
) {
497 LOG(("CacheFileHandles::Log() BEGIN [entry=%p]", entry
));
499 nsTArray
<RefPtr
<CacheFileHandle
> > array
;
500 aEntry
->GetHandles(array
);
502 for (uint32_t i
= 0; i
< array
.Length(); ++i
) {
503 CacheFileHandle
* handle
= array
[i
];
507 LOG(("CacheFileHandles::Log() END [entry=%p]", entry
));
513 size_t CacheFileHandles::SizeOfExcludingThis(
514 mozilla::MallocSizeOf mallocSizeOf
) const {
515 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
517 return mTable
.SizeOfExcludingThis(mallocSizeOf
);
522 class ShutdownEvent
: public Runnable
{
525 : Runnable("net::ShutdownEvent"),
526 mMonitor("ShutdownEvent.mMonitor"),
530 ~ShutdownEvent() = default;
533 NS_IMETHOD
Run() override
{
534 MonitorAutoLock
mon(mMonitor
);
536 CacheFileIOManager::gInstance
->ShutdownInternal();
545 MonitorAutoLock
mon(mMonitor
);
547 nsresult rv
= CacheFileIOManager::gInstance
->mIOThread
->Dispatch(
549 CacheIOThread::WRITE
); // When writes and closing of handles is done
550 MOZ_ASSERT(NS_SUCCEEDED(rv
));
552 // If we failed to post the even there's no reason to go into the loop
553 // because mNotified will never be set to true.
555 NS_WARNING("Posting ShutdownEvent task failed");
559 TimeDuration waitTime
= TimeDuration::FromSeconds(1);
563 // If there is any IO blocking on the IO thread, this will
564 // try to cancel it. Returns no later than after two seconds.
565 MonitorAutoUnlock
unmon(mMonitor
); // Prevent delays
566 CacheFileIOManager::gInstance
->mIOThread
->CancelBlockingIO();
572 mozilla::Monitor mMonitor
;
576 // Class responsible for reporting IO performance stats
577 class IOPerfReportEvent
{
579 explicit IOPerfReportEvent(CacheFileUtils::CachePerfStats::EDataType aType
)
580 : mType(aType
), mEventCounter(0) {}
582 void Start(CacheIOThread
* aIOThread
) {
583 mStartTime
= TimeStamp::Now();
584 mEventCounter
= aIOThread
->EventCounter();
587 void Report(CacheIOThread
* aIOThread
) {
588 if (mStartTime
.IsNull()) {
592 // Single IO operations can take less than 1ms. So we use microseconds to
593 // keep a good resolution of data.
594 uint32_t duration
= (TimeStamp::Now() - mStartTime
).ToMicroseconds();
596 // This is a simple prefiltering of values that might differ a lot from the
597 // average value. Do not add the value to the filtered stats when the event
598 // had to wait in a long queue.
599 uint32_t eventCounter
= aIOThread
->EventCounter();
600 bool shortOnly
= eventCounter
- mEventCounter
< 5 ? false : true;
602 CacheFileUtils::CachePerfStats::AddValue(mType
, duration
, shortOnly
);
606 CacheFileUtils::CachePerfStats::EDataType mType
;
607 TimeStamp mStartTime
;
608 uint32_t mEventCounter
;
611 class OpenFileEvent
: public Runnable
, public IOPerfReportEvent
{
613 OpenFileEvent(const nsACString
& aKey
, uint32_t aFlags
,
614 CacheFileIOListener
* aCallback
)
615 : Runnable("net::OpenFileEvent"),
616 IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_OPEN
),
618 mCallback(aCallback
),
620 mIOMan
= CacheFileIOManager::gInstance
;
621 if (!(mFlags
& CacheFileIOManager::SPECIAL_FILE
)) {
622 Start(mIOMan
->mIOThread
);
627 ~OpenFileEvent() = default;
630 NS_IMETHOD
Run() override
{
633 if (!(mFlags
& CacheFileIOManager::SPECIAL_FILE
)) {
635 sum
.update(mKey
.BeginReading(), mKey
.Length());
640 rv
= NS_ERROR_NOT_INITIALIZED
;
642 if (mFlags
& CacheFileIOManager::SPECIAL_FILE
) {
643 rv
= mIOMan
->OpenSpecialFileInternal(mKey
, mFlags
,
644 getter_AddRefs(mHandle
));
646 rv
= mIOMan
->OpenFileInternal(&mHash
, mKey
, mFlags
,
647 getter_AddRefs(mHandle
));
648 if (NS_SUCCEEDED(rv
)) {
649 Report(mIOMan
->mIOThread
);
654 if (mHandle
->Key().IsEmpty()) {
655 mHandle
->Key() = mKey
;
660 mCallback
->OnFileOpened(mHandle
, rv
);
667 nsCOMPtr
<CacheFileIOListener
> mCallback
;
668 RefPtr
<CacheFileIOManager
> mIOMan
;
669 RefPtr
<CacheFileHandle
> mHandle
;
673 class ReadEvent
: public Runnable
, public IOPerfReportEvent
{
675 ReadEvent(CacheFileHandle
* aHandle
, int64_t aOffset
, char* aBuf
,
676 int32_t aCount
, CacheFileIOListener
* aCallback
)
677 : Runnable("net::ReadEvent"),
678 IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_READ
),
683 mCallback(aCallback
) {
684 if (!mHandle
->IsSpecialFile()) {
685 Start(CacheFileIOManager::gInstance
->mIOThread
);
690 ~ReadEvent() = default;
693 NS_IMETHOD
Run() override
{
696 if (mHandle
->IsClosed() || (mCallback
&& mCallback
->IsKilled())) {
697 rv
= NS_ERROR_NOT_INITIALIZED
;
699 rv
= CacheFileIOManager::gInstance
->ReadInternal(mHandle
, mOffset
, mBuf
,
701 if (NS_SUCCEEDED(rv
)) {
702 Report(CacheFileIOManager::gInstance
->mIOThread
);
706 mCallback
->OnDataRead(mHandle
, mBuf
, rv
);
711 RefPtr
<CacheFileHandle
> mHandle
;
715 nsCOMPtr
<CacheFileIOListener
> mCallback
;
718 class WriteEvent
: public Runnable
, public IOPerfReportEvent
{
720 WriteEvent(CacheFileHandle
* aHandle
, int64_t aOffset
, const char* aBuf
,
721 int32_t aCount
, bool aValidate
, bool aTruncate
,
722 CacheFileIOListener
* aCallback
)
723 : Runnable("net::WriteEvent"),
724 IOPerfReportEvent(CacheFileUtils::CachePerfStats::IO_WRITE
),
729 mValidate(aValidate
),
730 mTruncate(aTruncate
),
731 mCallback(aCallback
) {
732 if (!mHandle
->IsSpecialFile()) {
733 Start(CacheFileIOManager::gInstance
->mIOThread
);
739 if (!mCallback
&& mBuf
) {
740 free(const_cast<char*>(mBuf
));
745 NS_IMETHOD
Run() override
{
748 if (mHandle
->IsClosed() || (mCallback
&& mCallback
->IsKilled())) {
749 // We usually get here only after the internal shutdown
750 // (i.e. mShuttingDown == true). Pretend write has succeeded
751 // to avoid any past-shutdown file dooming.
752 rv
= (CacheObserver::IsPastShutdownIOLag() ||
753 CacheFileIOManager::gInstance
->mShuttingDown
)
755 : NS_ERROR_NOT_INITIALIZED
;
757 rv
= CacheFileIOManager::gInstance
->WriteInternal(
758 mHandle
, mOffset
, mBuf
, mCount
, mValidate
, mTruncate
);
759 if (NS_SUCCEEDED(rv
)) {
760 Report(CacheFileIOManager::gInstance
->mIOThread
);
762 if (NS_FAILED(rv
) && !mCallback
) {
763 // No listener is going to handle the error, doom the file
764 CacheFileIOManager::gInstance
->DoomFileInternal(mHandle
);
768 mCallback
->OnDataWritten(mHandle
, mBuf
, rv
);
770 free(const_cast<char*>(mBuf
));
778 RefPtr
<CacheFileHandle
> mHandle
;
784 nsCOMPtr
<CacheFileIOListener
> mCallback
;
787 class DoomFileEvent
: public Runnable
{
789 DoomFileEvent(CacheFileHandle
* aHandle
, CacheFileIOListener
* aCallback
)
790 : Runnable("net::DoomFileEvent"),
791 mCallback(aCallback
),
795 ~DoomFileEvent() = default;
798 NS_IMETHOD
Run() override
{
801 if (mHandle
->IsClosed()) {
802 rv
= NS_ERROR_NOT_INITIALIZED
;
804 rv
= CacheFileIOManager::gInstance
->DoomFileInternal(mHandle
);
808 mCallback
->OnFileDoomed(mHandle
, rv
);
815 nsCOMPtr
<CacheFileIOListener
> mCallback
;
816 nsCOMPtr
<nsIEventTarget
> mTarget
;
817 RefPtr
<CacheFileHandle
> mHandle
;
820 class DoomFileByKeyEvent
: public Runnable
{
822 DoomFileByKeyEvent(const nsACString
& aKey
, CacheFileIOListener
* aCallback
)
823 : Runnable("net::DoomFileByKeyEvent"), mCallback(aCallback
) {
825 sum
.update(aKey
.BeginReading(), aKey
.Length());
828 mIOMan
= CacheFileIOManager::gInstance
;
832 ~DoomFileByKeyEvent() = default;
835 NS_IMETHOD
Run() override
{
839 rv
= NS_ERROR_NOT_INITIALIZED
;
841 rv
= mIOMan
->DoomFileByKeyInternal(&mHash
);
846 mCallback
->OnFileDoomed(nullptr, rv
);
854 nsCOMPtr
<CacheFileIOListener
> mCallback
;
855 RefPtr
<CacheFileIOManager
> mIOMan
;
858 class ReleaseNSPRHandleEvent
: public Runnable
{
860 explicit ReleaseNSPRHandleEvent(CacheFileHandle
* aHandle
)
861 : Runnable("net::ReleaseNSPRHandleEvent"), mHandle(aHandle
) {}
864 ~ReleaseNSPRHandleEvent() = default;
867 NS_IMETHOD
Run() override
{
868 if (!mHandle
->IsClosed()) {
869 CacheFileIOManager::gInstance
->MaybeReleaseNSPRHandleInternal(mHandle
);
876 RefPtr
<CacheFileHandle
> mHandle
;
879 class TruncateSeekSetEOFEvent
: public Runnable
{
881 TruncateSeekSetEOFEvent(CacheFileHandle
* aHandle
, int64_t aTruncatePos
,
882 int64_t aEOFPos
, CacheFileIOListener
* aCallback
)
883 : Runnable("net::TruncateSeekSetEOFEvent"),
885 mTruncatePos(aTruncatePos
),
887 mCallback(aCallback
) {}
890 ~TruncateSeekSetEOFEvent() = default;
893 NS_IMETHOD
Run() override
{
896 if (mHandle
->IsClosed() || (mCallback
&& mCallback
->IsKilled())) {
897 rv
= NS_ERROR_NOT_INITIALIZED
;
899 rv
= CacheFileIOManager::gInstance
->TruncateSeekSetEOFInternal(
900 mHandle
, mTruncatePos
, mEOFPos
);
904 mCallback
->OnEOFSet(mHandle
, rv
);
911 RefPtr
<CacheFileHandle
> mHandle
;
912 int64_t mTruncatePos
;
914 nsCOMPtr
<CacheFileIOListener
> mCallback
;
917 class RenameFileEvent
: public Runnable
{
919 RenameFileEvent(CacheFileHandle
* aHandle
, const nsACString
& aNewName
,
920 CacheFileIOListener
* aCallback
)
921 : Runnable("net::RenameFileEvent"),
924 mCallback(aCallback
) {}
927 ~RenameFileEvent() = default;
930 NS_IMETHOD
Run() override
{
933 if (mHandle
->IsClosed()) {
934 rv
= NS_ERROR_NOT_INITIALIZED
;
936 rv
= CacheFileIOManager::gInstance
->RenameFileInternal(mHandle
, mNewName
);
940 mCallback
->OnFileRenamed(mHandle
, rv
);
947 RefPtr
<CacheFileHandle
> mHandle
;
949 nsCOMPtr
<CacheFileIOListener
> mCallback
;
952 class InitIndexEntryEvent
: public Runnable
{
954 InitIndexEntryEvent(CacheFileHandle
* aHandle
,
955 OriginAttrsHash aOriginAttrsHash
, bool aAnonymous
,
957 : Runnable("net::InitIndexEntryEvent"),
959 mOriginAttrsHash(aOriginAttrsHash
),
960 mAnonymous(aAnonymous
),
961 mPinning(aPinning
) {}
964 ~InitIndexEntryEvent() = default;
967 NS_IMETHOD
Run() override
{
968 if (mHandle
->IsClosed() || mHandle
->IsDoomed()) {
972 CacheIndex::InitEntry(mHandle
->Hash(), mOriginAttrsHash
, mAnonymous
,
975 // We cannot set the filesize before we init the entry. If we're opening
976 // an existing entry file, frecency will be set after parsing the entry
977 // file, but we must set the filesize here since nobody is going to set it
978 // if there is no write to the file.
979 uint32_t sizeInK
= mHandle
->FileSizeInK();
980 CacheIndex::UpdateEntry(mHandle
->Hash(), nullptr, nullptr, nullptr, nullptr,
987 RefPtr
<CacheFileHandle
> mHandle
;
988 OriginAttrsHash mOriginAttrsHash
;
993 class UpdateIndexEntryEvent
: public Runnable
{
995 UpdateIndexEntryEvent(CacheFileHandle
* aHandle
, const uint32_t* aFrecency
,
996 const bool* aHasAltData
, const uint16_t* aOnStartTime
,
997 const uint16_t* aOnStopTime
,
998 const uint8_t* aContentType
)
999 : Runnable("net::UpdateIndexEntryEvent"),
1001 mHasFrecency(false),
1002 mHasHasAltData(false),
1003 mHasOnStartTime(false),
1004 mHasOnStopTime(false),
1005 mHasContentType(false),
1010 mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN
) {
1012 mHasFrecency
= true;
1013 mFrecency
= *aFrecency
;
1016 mHasHasAltData
= true;
1017 mHasAltData
= *aHasAltData
;
1020 mHasOnStartTime
= true;
1021 mOnStartTime
= *aOnStartTime
;
1024 mHasOnStopTime
= true;
1025 mOnStopTime
= *aOnStopTime
;
1028 mHasContentType
= true;
1029 mContentType
= *aContentType
;
1034 ~UpdateIndexEntryEvent() = default;
1037 NS_IMETHOD
Run() override
{
1038 if (mHandle
->IsClosed() || mHandle
->IsDoomed()) {
1042 CacheIndex::UpdateEntry(mHandle
->Hash(),
1043 mHasFrecency
? &mFrecency
: nullptr,
1044 mHasHasAltData
? &mHasAltData
: nullptr,
1045 mHasOnStartTime
? &mOnStartTime
: nullptr,
1046 mHasOnStopTime
? &mOnStopTime
: nullptr,
1047 mHasContentType
? &mContentType
: nullptr, nullptr);
1052 RefPtr
<CacheFileHandle
> mHandle
;
1055 bool mHasHasAltData
;
1056 bool mHasOnStartTime
;
1057 bool mHasOnStopTime
;
1058 bool mHasContentType
;
1062 uint16_t mOnStartTime
;
1063 uint16_t mOnStopTime
;
1064 uint8_t mContentType
;
1067 class MetadataWriteScheduleEvent
: public Runnable
{
1069 enum EMode
{ SCHEDULE
, UNSCHEDULE
, SHUTDOWN
} mMode
;
1071 RefPtr
<CacheFile
> mFile
;
1072 RefPtr
<CacheFileIOManager
> mIOMan
;
1074 MetadataWriteScheduleEvent(CacheFileIOManager
* aManager
, CacheFile
* aFile
,
1076 : Runnable("net::MetadataWriteScheduleEvent"),
1081 virtual ~MetadataWriteScheduleEvent() = default;
1083 NS_IMETHOD
Run() override
{
1084 RefPtr
<CacheFileIOManager
> ioMan
= CacheFileIOManager::gInstance
;
1087 "CacheFileIOManager already gone in "
1088 "MetadataWriteScheduleEvent::Run()");
1094 ioMan
->ScheduleMetadataWriteInternal(mFile
);
1097 ioMan
->UnscheduleMetadataWriteInternal(mFile
);
1100 ioMan
->ShutdownMetadataWriteSchedulingInternal();
1107 StaticRefPtr
<CacheFileIOManager
> CacheFileIOManager::gInstance
;
1109 NS_IMPL_ISUPPORTS(CacheFileIOManager
, nsITimerCallback
, nsINamed
)
1111 CacheFileIOManager::CacheFileIOManager()
1112 : mShuttingDown(false),
1113 mTreeCreated(false),
1114 mTreeCreationFailed(false),
1115 mOverLimitEvicting(false),
1116 mCacheSizeOnHardLimit(false),
1117 mRemovingTrashDirs(false) {
1118 LOG(("CacheFileIOManager::CacheFileIOManager [this=%p]", this));
1119 MOZ_ASSERT(!gInstance
, "multiple CacheFileIOManager instances!");
1122 CacheFileIOManager::~CacheFileIOManager() {
1123 LOG(("CacheFileIOManager::~CacheFileIOManager [this=%p]", this));
1127 nsresult
CacheFileIOManager::Init() {
1128 LOG(("CacheFileIOManager::Init()"));
1130 MOZ_ASSERT(NS_IsMainThread());
1133 return NS_ERROR_ALREADY_INITIALIZED
;
1136 RefPtr
<CacheFileIOManager
> ioMan
= new CacheFileIOManager();
1138 nsresult rv
= ioMan
->InitInternal();
1139 NS_ENSURE_SUCCESS(rv
, rv
);
1141 gInstance
= std::move(ioMan
);
1145 nsresult
CacheFileIOManager::InitInternal() {
1148 mIOThread
= new CacheIOThread();
1150 rv
= mIOThread
->Init();
1151 MOZ_ASSERT(NS_SUCCEEDED(rv
), "Can't create background thread");
1152 NS_ENSURE_SUCCESS(rv
, rv
);
1154 mStartTime
= TimeStamp::NowLoRes();
1160 nsresult
CacheFileIOManager::Shutdown() {
1161 LOG(("CacheFileIOManager::Shutdown() [gInstance=%p]", gInstance
.get()));
1163 MOZ_ASSERT(NS_IsMainThread());
1166 return NS_ERROR_NOT_INITIALIZED
;
1169 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_V2
> shutdownTimer
;
1171 CacheIndex::PreShutdown();
1173 ShutdownMetadataWriteScheduling();
1175 RefPtr
<ShutdownEvent
> ev
= new ShutdownEvent();
1178 MOZ_ASSERT(gInstance
->mHandles
.HandleCount() == 0);
1179 MOZ_ASSERT(gInstance
->mHandlesByLastUsed
.Length() == 0);
1181 if (gInstance
->mIOThread
) {
1182 gInstance
->mIOThread
->Shutdown();
1185 CacheIndex::Shutdown();
1187 if (CacheObserver::ClearCacheOnShutdown()) {
1188 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE2_SHUTDOWN_CLEAR_PRIVATE
>
1190 gInstance
->SyncRemoveAllCacheFiles();
1193 gInstance
= nullptr;
1198 void CacheFileIOManager::ShutdownInternal() {
1199 LOG(("CacheFileIOManager::ShutdownInternal() [this=%p]", this));
1201 MOZ_ASSERT(mIOThread
->IsCurrentThread());
1203 // No new handles can be created after this flag is set
1204 mShuttingDown
= true;
1207 mTrashTimer
->Cancel();
1208 mTrashTimer
= nullptr;
1211 // close all handles and delete all associated files
1212 nsTArray
<RefPtr
<CacheFileHandle
> > handles
;
1213 mHandles
.GetAllHandles(&handles
);
1214 handles
.AppendElements(mSpecialHandles
);
1216 for (uint32_t i
= 0; i
< handles
.Length(); i
++) {
1217 CacheFileHandle
* h
= handles
[i
];
1222 // Close completely written files.
1223 MaybeReleaseNSPRHandleInternal(h
);
1224 // Don't bother removing invalid and/or doomed files to improve
1225 // shutdown perfomrance.
1226 // Doomed files are already in the doomed directory from which
1227 // we never reuse files and delete the dir on next session startup.
1228 // Invalid files don't have metadata and thus won't load anyway
1229 // (hashes won't match).
1231 if (!h
->IsSpecialFile() && !h
->mIsDoomed
&& !h
->mFileExists
) {
1232 CacheIndex::RemoveEntry(h
->Hash());
1235 // Remove the handle from mHandles/mSpecialHandles
1236 if (h
->IsSpecialFile()) {
1237 mSpecialHandles
.RemoveElement(h
);
1239 mHandles
.RemoveHandle(h
);
1242 // Pointer to the hash is no longer valid once the last handle with the
1243 // given hash is released. Null out the pointer so that we crash if there
1244 // is a bug in this code and we dereference the pointer after this point.
1245 if (!h
->IsSpecialFile()) {
1250 // Assert the table is empty. When we are here, no new handles can be added
1251 // and handles will no longer remove them self from this table and we don't
1252 // want to keep invalid handles here. Also, there is no lookup after this
1254 MOZ_ASSERT(mHandles
.HandleCount() == 0);
1256 // Release trash directory enumerator
1257 if (mTrashDirEnumerator
) {
1258 mTrashDirEnumerator
->Close();
1259 mTrashDirEnumerator
= nullptr;
1262 if (mContextEvictor
) {
1263 mContextEvictor
->Shutdown();
1264 mContextEvictor
= nullptr;
1269 nsresult
CacheFileIOManager::OnProfile() {
1270 LOG(("CacheFileIOManager::OnProfile() [gInstance=%p]", gInstance
.get()));
1272 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1274 // CacheFileIOManager::Init() failed, probably could not create the IO
1275 // thread, just go with it...
1276 return NS_ERROR_NOT_INITIALIZED
;
1281 nsCOMPtr
<nsIFile
> directory
;
1283 CacheObserver::ParentDirOverride(getter_AddRefs(directory
));
1285 #if defined(MOZ_WIDGET_ANDROID)
1286 nsCOMPtr
<nsIFile
> profilelessDirectory
;
1287 char* cachePath
= getenv("CACHE_DIRECTORY");
1288 if (!directory
&& cachePath
&& *cachePath
) {
1289 rv
= NS_NewNativeLocalFile(nsDependentCString(cachePath
), true,
1290 getter_AddRefs(directory
));
1291 if (NS_SUCCEEDED(rv
)) {
1292 // Save this directory as the profileless path.
1293 rv
= directory
->Clone(getter_AddRefs(profilelessDirectory
));
1294 NS_ENSURE_SUCCESS(rv
, rv
);
1296 // Add profile leaf name to the directory name to distinguish
1297 // multiple profiles Fennec supports.
1298 nsCOMPtr
<nsIFile
> profD
;
1299 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
1300 getter_AddRefs(profD
));
1302 nsAutoCString leafName
;
1303 if (NS_SUCCEEDED(rv
)) {
1304 rv
= profD
->GetNativeLeafName(leafName
);
1306 if (NS_SUCCEEDED(rv
)) {
1307 rv
= directory
->AppendNative(leafName
);
1309 if (NS_FAILED(rv
)) {
1310 directory
= nullptr;
1317 rv
= NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR
,
1318 getter_AddRefs(directory
));
1322 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR
,
1323 getter_AddRefs(directory
));
1327 rv
= directory
->Append(u
"cache2"_ns
);
1328 NS_ENSURE_SUCCESS(rv
, rv
);
1331 // All functions return a clone.
1332 ioMan
->mCacheDirectory
.swap(directory
);
1334 #if defined(MOZ_WIDGET_ANDROID)
1335 if (profilelessDirectory
) {
1336 rv
= profilelessDirectory
->Append(u
"cache2"_ns
);
1337 NS_ENSURE_SUCCESS(rv
, rv
);
1340 ioMan
->mCacheProfilelessDirectory
.swap(profilelessDirectory
);
1343 if (ioMan
->mCacheDirectory
) {
1344 CacheIndex::Init(ioMan
->mCacheDirectory
);
1351 already_AddRefed
<nsIEventTarget
> CacheFileIOManager::IOTarget() {
1352 nsCOMPtr
<nsIEventTarget
> target
;
1353 if (gInstance
&& gInstance
->mIOThread
) {
1354 target
= gInstance
->mIOThread
->Target();
1357 return target
.forget();
1361 already_AddRefed
<CacheIOThread
> CacheFileIOManager::IOThread() {
1362 RefPtr
<CacheIOThread
> thread
;
1364 thread
= gInstance
->mIOThread
;
1367 return thread
.forget();
1371 bool CacheFileIOManager::IsOnIOThread() {
1372 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1373 if (ioMan
&& ioMan
->mIOThread
) {
1374 return ioMan
->mIOThread
->IsCurrentThread();
1381 bool CacheFileIOManager::IsOnIOThreadOrCeased() {
1382 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1383 if (ioMan
&& ioMan
->mIOThread
) {
1384 return ioMan
->mIOThread
->IsCurrentThread();
1392 bool CacheFileIOManager::IsShutdown() {
1396 return gInstance
->mShuttingDown
;
1400 nsresult
CacheFileIOManager::ScheduleMetadataWrite(CacheFile
* aFile
) {
1401 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1402 NS_ENSURE_TRUE(ioMan
, NS_ERROR_NOT_INITIALIZED
);
1404 NS_ENSURE_TRUE(!ioMan
->mShuttingDown
, NS_ERROR_NOT_INITIALIZED
);
1406 RefPtr
<MetadataWriteScheduleEvent
> event
= new MetadataWriteScheduleEvent(
1407 ioMan
, aFile
, MetadataWriteScheduleEvent::SCHEDULE
);
1408 nsCOMPtr
<nsIEventTarget
> target
= ioMan
->IOTarget();
1409 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
1410 return target
->Dispatch(event
, nsIEventTarget::DISPATCH_NORMAL
);
1413 nsresult
CacheFileIOManager::ScheduleMetadataWriteInternal(CacheFile
* aFile
) {
1414 MOZ_ASSERT(IsOnIOThreadOrCeased());
1418 if (!mMetadataWritesTimer
) {
1419 rv
= NS_NewTimerWithCallback(getter_AddRefs(mMetadataWritesTimer
), this,
1420 kMetadataWriteDelay
, nsITimer::TYPE_ONE_SHOT
);
1421 NS_ENSURE_SUCCESS(rv
, rv
);
1424 if (mScheduledMetadataWrites
.IndexOf(aFile
) !=
1425 mScheduledMetadataWrites
.NoIndex
) {
1429 mScheduledMetadataWrites
.AppendElement(aFile
);
1435 nsresult
CacheFileIOManager::UnscheduleMetadataWrite(CacheFile
* aFile
) {
1436 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1437 NS_ENSURE_TRUE(ioMan
, NS_ERROR_NOT_INITIALIZED
);
1439 NS_ENSURE_TRUE(!ioMan
->mShuttingDown
, NS_ERROR_NOT_INITIALIZED
);
1441 RefPtr
<MetadataWriteScheduleEvent
> event
= new MetadataWriteScheduleEvent(
1442 ioMan
, aFile
, MetadataWriteScheduleEvent::UNSCHEDULE
);
1443 nsCOMPtr
<nsIEventTarget
> target
= ioMan
->IOTarget();
1444 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
1445 return target
->Dispatch(event
, nsIEventTarget::DISPATCH_NORMAL
);
1448 void CacheFileIOManager::UnscheduleMetadataWriteInternal(CacheFile
* aFile
) {
1449 MOZ_ASSERT(IsOnIOThreadOrCeased());
1451 mScheduledMetadataWrites
.RemoveElement(aFile
);
1453 if (mScheduledMetadataWrites
.Length() == 0 && mMetadataWritesTimer
) {
1454 mMetadataWritesTimer
->Cancel();
1455 mMetadataWritesTimer
= nullptr;
1460 nsresult
CacheFileIOManager::ShutdownMetadataWriteScheduling() {
1461 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1462 NS_ENSURE_TRUE(ioMan
, NS_ERROR_NOT_INITIALIZED
);
1464 RefPtr
<MetadataWriteScheduleEvent
> event
= new MetadataWriteScheduleEvent(
1465 ioMan
, nullptr, MetadataWriteScheduleEvent::SHUTDOWN
);
1466 nsCOMPtr
<nsIEventTarget
> target
= ioMan
->IOTarget();
1467 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
1468 return target
->Dispatch(event
, nsIEventTarget::DISPATCH_NORMAL
);
1471 void CacheFileIOManager::ShutdownMetadataWriteSchedulingInternal() {
1472 MOZ_ASSERT(IsOnIOThreadOrCeased());
1474 nsTArray
<RefPtr
<CacheFile
> > files
= std::move(mScheduledMetadataWrites
);
1475 for (uint32_t i
= 0; i
< files
.Length(); ++i
) {
1476 CacheFile
* file
= files
[i
];
1477 file
->WriteMetadataIfNeeded();
1480 if (mMetadataWritesTimer
) {
1481 mMetadataWritesTimer
->Cancel();
1482 mMetadataWritesTimer
= nullptr;
1487 CacheFileIOManager::Notify(nsITimer
* aTimer
) {
1488 MOZ_ASSERT(IsOnIOThreadOrCeased());
1489 MOZ_ASSERT(mMetadataWritesTimer
== aTimer
);
1491 mMetadataWritesTimer
= nullptr;
1493 nsTArray
<RefPtr
<CacheFile
> > files
= std::move(mScheduledMetadataWrites
);
1494 for (uint32_t i
= 0; i
< files
.Length(); ++i
) {
1495 CacheFile
* file
= files
[i
];
1496 file
->WriteMetadataIfNeeded();
1503 CacheFileIOManager::GetName(nsACString
& aName
) {
1504 aName
.AssignLiteral("CacheFileIOManager");
1509 nsresult
CacheFileIOManager::OpenFile(const nsACString
& aKey
, uint32_t aFlags
,
1510 CacheFileIOListener
* aCallback
) {
1511 LOG(("CacheFileIOManager::OpenFile() [key=%s, flags=%d, listener=%p]",
1512 PromiseFlatCString(aKey
).get(), aFlags
, aCallback
));
1515 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1518 return NS_ERROR_NOT_INITIALIZED
;
1521 bool priority
= aFlags
& CacheFileIOManager::PRIORITY
;
1522 RefPtr
<OpenFileEvent
> ev
= new OpenFileEvent(aKey
, aFlags
, aCallback
);
1523 rv
= ioMan
->mIOThread
->Dispatch(
1524 ev
, priority
? CacheIOThread::OPEN_PRIORITY
: CacheIOThread::OPEN
);
1525 NS_ENSURE_SUCCESS(rv
, rv
);
1530 nsresult
CacheFileIOManager::OpenFileInternal(const SHA1Sum::Hash
* aHash
,
1531 const nsACString
& aKey
,
1533 CacheFileHandle
** _retval
) {
1535 ("CacheFileIOManager::OpenFileInternal() [hash=%08x%08x%08x%08x%08x, "
1536 "key=%s, flags=%d]",
1537 LOGSHA1(aHash
), PromiseFlatCString(aKey
).get(), aFlags
));
1539 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1543 if (mShuttingDown
) {
1544 return NS_ERROR_NOT_INITIALIZED
;
1547 CacheIOThread::Cancelable
cancelable(
1548 true /* never called for special handles */);
1550 if (!mTreeCreated
) {
1551 rv
= CreateCacheTree();
1552 if (NS_FAILED(rv
)) return rv
;
1555 CacheFileHandle::PinningStatus pinning
=
1556 aFlags
& PINNED
? CacheFileHandle::PinningStatus::PINNED
1557 : CacheFileHandle::PinningStatus::NON_PINNED
;
1559 nsCOMPtr
<nsIFile
> file
;
1560 rv
= GetFile(aHash
, getter_AddRefs(file
));
1561 NS_ENSURE_SUCCESS(rv
, rv
);
1563 RefPtr
<CacheFileHandle
> handle
;
1564 mHandles
.GetHandle(aHash
, getter_AddRefs(handle
));
1566 if ((aFlags
& (OPEN
| CREATE
| CREATE_NEW
)) == CREATE_NEW
) {
1568 rv
= DoomFileInternal(handle
);
1569 NS_ENSURE_SUCCESS(rv
, rv
);
1573 handle
= mHandles
.NewHandle(aHash
, aFlags
& PRIORITY
, pinning
);
1576 rv
= file
->Exists(&exists
);
1577 NS_ENSURE_SUCCESS(rv
, rv
);
1580 CacheIndex::RemoveEntry(aHash
);
1583 ("CacheFileIOManager::OpenFileInternal() - Removing old file from "
1585 rv
= file
->Remove(false);
1586 if (NS_FAILED(rv
)) {
1587 NS_WARNING("Cannot remove old entry from the disk");
1589 ("CacheFileIOManager::OpenFileInternal() - Removing old file failed"
1590 ". [rv=0x%08" PRIx32
"]",
1591 static_cast<uint32_t>(rv
)));
1595 CacheIndex::AddEntry(aHash
);
1596 handle
->mFile
.swap(file
);
1597 handle
->mFileSize
= 0;
1601 handle
.swap(*_retval
);
1605 bool exists
, evictedAsPinned
= false, evictedAsNonPinned
= false;
1606 rv
= file
->Exists(&exists
);
1607 NS_ENSURE_SUCCESS(rv
, rv
);
1609 if (exists
&& mContextEvictor
) {
1610 if (mContextEvictor
->ContextsCount() == 0) {
1611 mContextEvictor
= nullptr;
1613 mContextEvictor
->WasEvicted(aKey
, file
, &evictedAsPinned
,
1614 &evictedAsNonPinned
);
1618 if (!exists
&& (aFlags
& (OPEN
| CREATE
| CREATE_NEW
)) == OPEN
) {
1619 return NS_ERROR_NOT_AVAILABLE
;
1623 // For existing files we determine the pinning status later, after the
1624 // metadata gets parsed.
1625 pinning
= CacheFileHandle::PinningStatus::UNKNOWN
;
1628 handle
= mHandles
.NewHandle(aHash
, aFlags
& PRIORITY
, pinning
);
1630 // If this file has been found evicted through the context file evictor
1631 // above for any of pinned or non-pinned state, these calls ensure we doom
1632 // the handle ASAP we know the real pinning state after metadta has been
1633 // parsed. DoomFileInternal on the |handle| doesn't doom right now, since
1634 // the pinning state is unknown and we pass down a pinning restriction.
1635 if (evictedAsPinned
) {
1636 rv
= DoomFileInternal(handle
, DOOM_WHEN_PINNED
);
1637 MOZ_ASSERT(!handle
->IsDoomed() && NS_SUCCEEDED(rv
));
1639 if (evictedAsNonPinned
) {
1640 rv
= DoomFileInternal(handle
, DOOM_WHEN_NON_PINNED
);
1641 MOZ_ASSERT(!handle
->IsDoomed() && NS_SUCCEEDED(rv
));
1644 int64_t fileSize
= -1;
1645 rv
= file
->GetFileSize(&fileSize
);
1646 NS_ENSURE_SUCCESS(rv
, rv
);
1648 handle
->mFileSize
= fileSize
;
1649 handle
->mFileExists
= true;
1651 CacheIndex::EnsureEntryExists(aHash
);
1653 handle
->mFileSize
= 0;
1655 CacheIndex::AddEntry(aHash
);
1658 handle
->mFile
.swap(file
);
1659 handle
.swap(*_retval
);
1663 nsresult
CacheFileIOManager::OpenSpecialFileInternal(
1664 const nsACString
& aKey
, uint32_t aFlags
, CacheFileHandle
** _retval
) {
1665 LOG(("CacheFileIOManager::OpenSpecialFileInternal() [key=%s, flags=%d]",
1666 PromiseFlatCString(aKey
).get(), aFlags
));
1668 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
1672 if (mShuttingDown
) {
1673 return NS_ERROR_NOT_INITIALIZED
;
1676 if (!mTreeCreated
) {
1677 rv
= CreateCacheTree();
1678 if (NS_FAILED(rv
)) return rv
;
1681 nsCOMPtr
<nsIFile
> file
;
1682 rv
= GetSpecialFile(aKey
, getter_AddRefs(file
));
1683 NS_ENSURE_SUCCESS(rv
, rv
);
1685 RefPtr
<CacheFileHandle
> handle
;
1686 for (uint32_t i
= 0; i
< mSpecialHandles
.Length(); i
++) {
1687 if (!mSpecialHandles
[i
]->IsDoomed() && mSpecialHandles
[i
]->Key() == aKey
) {
1688 handle
= mSpecialHandles
[i
];
1693 if ((aFlags
& (OPEN
| CREATE
| CREATE_NEW
)) == CREATE_NEW
) {
1695 rv
= DoomFileInternal(handle
);
1696 NS_ENSURE_SUCCESS(rv
, rv
);
1700 handle
= new CacheFileHandle(aKey
, aFlags
& PRIORITY
,
1701 CacheFileHandle::PinningStatus::NON_PINNED
);
1702 mSpecialHandles
.AppendElement(handle
);
1705 rv
= file
->Exists(&exists
);
1706 NS_ENSURE_SUCCESS(rv
, rv
);
1710 ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file from "
1712 rv
= file
->Remove(false);
1713 if (NS_FAILED(rv
)) {
1714 NS_WARNING("Cannot remove old entry from the disk");
1716 ("CacheFileIOManager::OpenSpecialFileInternal() - Removing file "
1717 "failed. [rv=0x%08" PRIx32
"]",
1718 static_cast<uint32_t>(rv
)));
1722 handle
->mFile
.swap(file
);
1723 handle
->mFileSize
= 0;
1727 handle
.swap(*_retval
);
1732 rv
= file
->Exists(&exists
);
1733 NS_ENSURE_SUCCESS(rv
, rv
);
1735 if (!exists
&& (aFlags
& (OPEN
| CREATE
| CREATE_NEW
)) == OPEN
) {
1736 return NS_ERROR_NOT_AVAILABLE
;
1739 handle
= new CacheFileHandle(aKey
, aFlags
& PRIORITY
,
1740 CacheFileHandle::PinningStatus::NON_PINNED
);
1741 mSpecialHandles
.AppendElement(handle
);
1744 int64_t fileSize
= -1;
1745 rv
= file
->GetFileSize(&fileSize
);
1746 NS_ENSURE_SUCCESS(rv
, rv
);
1748 handle
->mFileSize
= fileSize
;
1749 handle
->mFileExists
= true;
1751 handle
->mFileSize
= 0;
1754 handle
->mFile
.swap(file
);
1755 handle
.swap(*_retval
);
1759 void CacheFileIOManager::CloseHandleInternal(CacheFileHandle
* aHandle
) {
1761 LOG(("CacheFileIOManager::CloseHandleInternal() [handle=%p]", aHandle
));
1763 MOZ_ASSERT(!aHandle
->IsClosed());
1767 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
1769 CacheIOThread::Cancelable
cancelable(!aHandle
->IsSpecialFile());
1771 // Maybe close file handle (can be legally bypassed after shutdown)
1772 rv
= MaybeReleaseNSPRHandleInternal(aHandle
);
1774 // Delete the file if the entry was doomed or invalid and
1775 // filedesc properly closed
1776 if ((aHandle
->mIsDoomed
|| aHandle
->mInvalid
) && aHandle
->mFileExists
&&
1779 ("CacheFileIOManager::CloseHandleInternal() - Removing file from "
1782 rv
= aHandle
->mFile
->Remove(false);
1783 if (NS_SUCCEEDED(rv
)) {
1784 aHandle
->mFileExists
= false;
1786 LOG((" failed to remove the file [rv=0x%08" PRIx32
"]",
1787 static_cast<uint32_t>(rv
)));
1791 if (!aHandle
->IsSpecialFile() && !aHandle
->mIsDoomed
&&
1792 (aHandle
->mInvalid
|| !aHandle
->mFileExists
)) {
1793 CacheIndex::RemoveEntry(aHandle
->Hash());
1796 // Don't remove handles after shutdown
1797 if (!mShuttingDown
) {
1798 if (aHandle
->IsSpecialFile()) {
1799 mSpecialHandles
.RemoveElement(aHandle
);
1801 mHandles
.RemoveHandle(aHandle
);
1807 nsresult
CacheFileIOManager::Read(CacheFileHandle
* aHandle
, int64_t aOffset
,
1808 char* aBuf
, int32_t aCount
,
1809 CacheFileIOListener
* aCallback
) {
1810 LOG(("CacheFileIOManager::Read() [handle=%p, offset=%" PRId64
", count=%d, "
1812 aHandle
, aOffset
, aCount
, aCallback
));
1814 if (CacheObserver::ShuttingDown()) {
1815 LOG((" no reads after shutdown"));
1816 return NS_ERROR_NOT_INITIALIZED
;
1820 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1822 if (aHandle
->IsClosed() || !ioMan
) {
1823 return NS_ERROR_NOT_INITIALIZED
;
1826 RefPtr
<ReadEvent
> ev
=
1827 new ReadEvent(aHandle
, aOffset
, aBuf
, aCount
, aCallback
);
1828 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->IsPriority()
1829 ? CacheIOThread::READ_PRIORITY
1830 : CacheIOThread::READ
);
1831 NS_ENSURE_SUCCESS(rv
, rv
);
1836 nsresult
CacheFileIOManager::ReadInternal(CacheFileHandle
* aHandle
,
1837 int64_t aOffset
, char* aBuf
,
1839 LOG(("CacheFileIOManager::ReadInternal() [handle=%p, offset=%" PRId64
1841 aHandle
, aOffset
, aCount
));
1845 if (CacheObserver::ShuttingDown()) {
1846 LOG((" no reads after shutdown"));
1847 return NS_ERROR_NOT_INITIALIZED
;
1850 if (!aHandle
->mFileExists
) {
1851 NS_WARNING("Trying to read from non-existent file");
1852 return NS_ERROR_NOT_AVAILABLE
;
1855 CacheIOThread::Cancelable
cancelable(!aHandle
->IsSpecialFile());
1857 if (!aHandle
->mFD
) {
1858 rv
= OpenNSPRHandle(aHandle
);
1859 NS_ENSURE_SUCCESS(rv
, rv
);
1861 NSPRHandleUsed(aHandle
);
1864 // Check again, OpenNSPRHandle could figure out the file was gone.
1865 if (!aHandle
->mFileExists
) {
1866 NS_WARNING("Trying to read from non-existent file");
1867 return NS_ERROR_NOT_AVAILABLE
;
1870 int64_t offset
= PR_Seek64(aHandle
->mFD
, aOffset
, PR_SEEK_SET
);
1872 return NS_ERROR_FAILURE
;
1875 int32_t bytesRead
= PR_Read(aHandle
->mFD
, aBuf
, aCount
);
1876 if (bytesRead
!= aCount
) {
1877 return NS_ERROR_FAILURE
;
1884 nsresult
CacheFileIOManager::Write(CacheFileHandle
* aHandle
, int64_t aOffset
,
1885 const char* aBuf
, int32_t aCount
,
1886 bool aValidate
, bool aTruncate
,
1887 CacheFileIOListener
* aCallback
) {
1888 LOG(("CacheFileIOManager::Write() [handle=%p, offset=%" PRId64
", count=%d, "
1889 "validate=%d, truncate=%d, listener=%p]",
1890 aHandle
, aOffset
, aCount
, aValidate
, aTruncate
, aCallback
));
1893 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
1895 if (aHandle
->IsClosed() || (aCallback
&& aCallback
->IsKilled()) || !ioMan
) {
1897 // When no callback is provided, CacheFileIOManager is responsible for
1898 // releasing the buffer. We must release it even in case of failure.
1899 free(const_cast<char*>(aBuf
));
1901 return NS_ERROR_NOT_INITIALIZED
;
1904 RefPtr
<WriteEvent
> ev
= new WriteEvent(aHandle
, aOffset
, aBuf
, aCount
,
1905 aValidate
, aTruncate
, aCallback
);
1906 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->mPriority
1907 ? CacheIOThread::WRITE_PRIORITY
1908 : CacheIOThread::WRITE
);
1909 NS_ENSURE_SUCCESS(rv
, rv
);
1914 static nsresult
TruncFile(PRFileDesc
* aFD
, int64_t aEOF
) {
1915 #if defined(XP_UNIX)
1916 if (ftruncate(PR_FileDesc2NativeHandle(aFD
), aEOF
) != 0) {
1917 NS_ERROR("ftruncate failed");
1918 return NS_ERROR_FAILURE
;
1920 #elif defined(XP_WIN)
1921 int64_t cnt
= PR_Seek64(aFD
, aEOF
, PR_SEEK_SET
);
1923 return NS_ERROR_FAILURE
;
1925 if (!SetEndOfFile((HANDLE
)PR_FileDesc2NativeHandle(aFD
))) {
1926 NS_ERROR("SetEndOfFile failed");
1927 return NS_ERROR_FAILURE
;
1930 MOZ_ASSERT(false, "Not implemented!");
1931 return NS_ERROR_NOT_IMPLEMENTED
;
1937 nsresult
CacheFileIOManager::WriteInternal(CacheFileHandle
* aHandle
,
1938 int64_t aOffset
, const char* aBuf
,
1939 int32_t aCount
, bool aValidate
,
1941 LOG(("CacheFileIOManager::WriteInternal() [handle=%p, offset=%" PRId64
1943 "validate=%d, truncate=%d]",
1944 aHandle
, aOffset
, aCount
, aValidate
, aTruncate
));
1948 if (aHandle
->mKilled
) {
1949 LOG((" handle already killed, nothing written"));
1953 if (CacheObserver::ShuttingDown() && (!aValidate
|| !aHandle
->mFD
)) {
1954 aHandle
->mKilled
= true;
1955 LOG((" killing the handle, nothing written"));
1959 if (CacheObserver::IsPastShutdownIOLag()) {
1960 LOG((" past the shutdown I/O lag, nothing written"));
1961 // Pretend the write has succeeded, otherwise upper layers will doom
1962 // the file and we end up with I/O anyway.
1966 CacheIOThread::Cancelable
cancelable(!aHandle
->IsSpecialFile());
1968 if (!aHandle
->mFileExists
) {
1969 rv
= CreateFile(aHandle
);
1970 NS_ENSURE_SUCCESS(rv
, rv
);
1973 if (!aHandle
->mFD
) {
1974 rv
= OpenNSPRHandle(aHandle
);
1975 NS_ENSURE_SUCCESS(rv
, rv
);
1977 NSPRHandleUsed(aHandle
);
1980 // Check again, OpenNSPRHandle could figure out the file was gone.
1981 if (!aHandle
->mFileExists
) {
1982 return NS_ERROR_NOT_AVAILABLE
;
1985 // When this operation would increase cache size, check whether the cache size
1986 // reached the hard limit and whether it would cause critical low disk space.
1987 if (aHandle
->mFileSize
< aOffset
+ aCount
) {
1988 if (mOverLimitEvicting
&& mCacheSizeOnHardLimit
) {
1990 ("CacheFileIOManager::WriteInternal() - failing because cache size "
1991 "reached hard limit!"));
1992 return NS_ERROR_FILE_DISK_FULL
;
1996 rv
= mCacheDirectory
->GetDiskSpaceAvailable(&freeSpace
);
1997 if (NS_WARN_IF(NS_FAILED(rv
))) {
2000 ("CacheFileIOManager::WriteInternal() - GetDiskSpaceAvailable() "
2001 "failed! [rv=0x%08" PRIx32
"]",
2002 static_cast<uint32_t>(rv
)));
2004 freeSpace
>>= 10; // bytes to kilobytes
2005 uint32_t limit
= CacheObserver::DiskFreeSpaceHardLimit();
2006 if (freeSpace
- aOffset
- aCount
+ aHandle
->mFileSize
< limit
) {
2008 ("CacheFileIOManager::WriteInternal() - Low free space, refusing "
2009 "to write! [freeSpace=%" PRId64
"kB, limit=%ukB]",
2011 return NS_ERROR_FILE_DISK_FULL
;
2016 // Write invalidates the entry by default
2017 aHandle
->mInvalid
= true;
2019 int64_t offset
= PR_Seek64(aHandle
->mFD
, aOffset
, PR_SEEK_SET
);
2021 return NS_ERROR_FAILURE
;
2024 int32_t bytesWritten
= PR_Write(aHandle
->mFD
, aBuf
, aCount
);
2026 if (bytesWritten
!= -1) {
2027 uint32_t oldSizeInK
= aHandle
->FileSizeInK();
2028 int64_t writeEnd
= aOffset
+ bytesWritten
;
2031 rv
= TruncFile(aHandle
->mFD
, writeEnd
);
2032 NS_ENSURE_SUCCESS(rv
, rv
);
2034 aHandle
->mFileSize
= writeEnd
;
2036 if (aHandle
->mFileSize
< writeEnd
) {
2037 aHandle
->mFileSize
= writeEnd
;
2041 uint32_t newSizeInK
= aHandle
->FileSizeInK();
2043 if (oldSizeInK
!= newSizeInK
&& !aHandle
->IsDoomed() &&
2044 !aHandle
->IsSpecialFile()) {
2045 CacheIndex::UpdateEntry(aHandle
->Hash(), nullptr, nullptr, nullptr,
2046 nullptr, nullptr, &newSizeInK
);
2048 if (oldSizeInK
< newSizeInK
) {
2049 EvictIfOverLimitInternal();
2053 CacheIndex::UpdateTotalBytesWritten(bytesWritten
);
2056 if (bytesWritten
!= aCount
) {
2057 return NS_ERROR_FAILURE
;
2060 // Write was successful and this write validates the entry (i.e. metadata)
2062 aHandle
->mInvalid
= false;
2069 nsresult
CacheFileIOManager::DoomFile(CacheFileHandle
* aHandle
,
2070 CacheFileIOListener
* aCallback
) {
2071 LOG(("CacheFileIOManager::DoomFile() [handle=%p, listener=%p]", aHandle
,
2075 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2077 if (aHandle
->IsClosed() || !ioMan
) {
2078 return NS_ERROR_NOT_INITIALIZED
;
2081 RefPtr
<DoomFileEvent
> ev
= new DoomFileEvent(aHandle
, aCallback
);
2082 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->IsPriority()
2083 ? CacheIOThread::OPEN_PRIORITY
2084 : CacheIOThread::OPEN
);
2085 NS_ENSURE_SUCCESS(rv
, rv
);
2090 nsresult
CacheFileIOManager::DoomFileInternal(
2091 CacheFileHandle
* aHandle
, PinningDoomRestriction aPinningDoomRestriction
) {
2092 LOG(("CacheFileIOManager::DoomFileInternal() [handle=%p]", aHandle
));
2095 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2099 if (aHandle
->IsDoomed()) {
2103 CacheIOThread::Cancelable
cancelable(!aHandle
->IsSpecialFile());
2105 if (aPinningDoomRestriction
> NO_RESTRICTION
) {
2106 switch (aHandle
->mPinning
) {
2107 case CacheFileHandle::PinningStatus::NON_PINNED
:
2108 if (MOZ_LIKELY(aPinningDoomRestriction
!= DOOM_WHEN_NON_PINNED
)) {
2109 LOG((" not dooming, it's a non-pinned handle"));
2115 case CacheFileHandle::PinningStatus::PINNED
:
2116 if (MOZ_UNLIKELY(aPinningDoomRestriction
!= DOOM_WHEN_PINNED
)) {
2117 LOG((" not dooming, it's a pinned handle"));
2123 case CacheFileHandle::PinningStatus::UNKNOWN
:
2124 if (MOZ_LIKELY(aPinningDoomRestriction
== DOOM_WHEN_NON_PINNED
)) {
2125 LOG((" doom when non-pinned set"));
2126 aHandle
->mDoomWhenFoundNonPinned
= true;
2127 } else if (MOZ_UNLIKELY(aPinningDoomRestriction
== DOOM_WHEN_PINNED
)) {
2128 LOG((" doom when pinned set"));
2129 aHandle
->mDoomWhenFoundPinned
= true;
2132 LOG((" pinning status not known, deferring doom decision"));
2137 if (aHandle
->mFileExists
) {
2138 // we need to move the current file to the doomed directory
2139 rv
= MaybeReleaseNSPRHandleInternal(aHandle
, true);
2140 NS_ENSURE_SUCCESS(rv
, rv
);
2142 // find unused filename
2143 nsCOMPtr
<nsIFile
> file
;
2144 rv
= GetDoomedFile(getter_AddRefs(file
));
2145 NS_ENSURE_SUCCESS(rv
, rv
);
2147 nsCOMPtr
<nsIFile
> parentDir
;
2148 rv
= file
->GetParent(getter_AddRefs(parentDir
));
2149 NS_ENSURE_SUCCESS(rv
, rv
);
2151 nsAutoCString leafName
;
2152 rv
= file
->GetNativeLeafName(leafName
);
2153 NS_ENSURE_SUCCESS(rv
, rv
);
2155 rv
= aHandle
->mFile
->MoveToNative(parentDir
, leafName
);
2156 if (NS_ERROR_FILE_NOT_FOUND
== rv
||
2157 NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
== rv
) {
2158 LOG((" file already removed under our hands"));
2159 aHandle
->mFileExists
= false;
2162 NS_ENSURE_SUCCESS(rv
, rv
);
2163 aHandle
->mFile
.swap(file
);
2167 if (!aHandle
->IsSpecialFile()) {
2168 CacheIndex::RemoveEntry(aHandle
->Hash());
2171 aHandle
->mIsDoomed
= true;
2173 if (!aHandle
->IsSpecialFile()) {
2174 RefPtr
<CacheStorageService
> storageService
= CacheStorageService::Self();
2175 if (storageService
) {
2176 nsAutoCString idExtension
, url
;
2177 nsCOMPtr
<nsILoadContextInfo
> info
=
2178 CacheFileUtils::ParseKey(aHandle
->Key(), &idExtension
, &url
);
2181 storageService
->CacheFileDoomed(info
, idExtension
, url
);
2190 nsresult
CacheFileIOManager::DoomFileByKey(const nsACString
& aKey
,
2191 CacheFileIOListener
* aCallback
) {
2192 LOG(("CacheFileIOManager::DoomFileByKey() [key=%s, listener=%p]",
2193 PromiseFlatCString(aKey
).get(), aCallback
));
2196 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2199 return NS_ERROR_NOT_INITIALIZED
;
2202 RefPtr
<DoomFileByKeyEvent
> ev
= new DoomFileByKeyEvent(aKey
, aCallback
);
2203 rv
= ioMan
->mIOThread
->DispatchAfterPendingOpens(ev
);
2204 NS_ENSURE_SUCCESS(rv
, rv
);
2209 nsresult
CacheFileIOManager::DoomFileByKeyInternal(const SHA1Sum::Hash
* aHash
) {
2211 "CacheFileIOManager::DoomFileByKeyInternal() [hash=%08x%08x%08x%08x%08x]",
2214 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2218 if (mShuttingDown
) {
2219 return NS_ERROR_NOT_INITIALIZED
;
2222 if (!mCacheDirectory
) {
2223 return NS_ERROR_FILE_INVALID_PATH
;
2226 // Find active handle
2227 RefPtr
<CacheFileHandle
> handle
;
2228 mHandles
.GetHandle(aHash
, getter_AddRefs(handle
));
2233 return DoomFileInternal(handle
);
2236 CacheIOThread::Cancelable
cancelable(true);
2238 // There is no handle for this file, delete the file if exists
2239 nsCOMPtr
<nsIFile
> file
;
2240 rv
= GetFile(aHash
, getter_AddRefs(file
));
2241 NS_ENSURE_SUCCESS(rv
, rv
);
2244 rv
= file
->Exists(&exists
);
2245 NS_ENSURE_SUCCESS(rv
, rv
);
2248 return NS_ERROR_NOT_AVAILABLE
;
2252 ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file from "
2254 rv
= file
->Remove(false);
2255 if (NS_FAILED(rv
)) {
2256 NS_WARNING("Cannot remove old entry from the disk");
2258 ("CacheFileIOManager::DoomFileByKeyInternal() - Removing file failed. "
2259 "[rv=0x%08" PRIx32
"]",
2260 static_cast<uint32_t>(rv
)));
2263 CacheIndex::RemoveEntry(aHash
);
2269 nsresult
CacheFileIOManager::ReleaseNSPRHandle(CacheFileHandle
* aHandle
) {
2270 LOG(("CacheFileIOManager::ReleaseNSPRHandle() [handle=%p]", aHandle
));
2273 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2275 if (aHandle
->IsClosed() || !ioMan
) {
2276 return NS_ERROR_NOT_INITIALIZED
;
2279 RefPtr
<ReleaseNSPRHandleEvent
> ev
= new ReleaseNSPRHandleEvent(aHandle
);
2280 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->mPriority
2281 ? CacheIOThread::WRITE_PRIORITY
2282 : CacheIOThread::WRITE
);
2283 NS_ENSURE_SUCCESS(rv
, rv
);
2288 nsresult
CacheFileIOManager::MaybeReleaseNSPRHandleInternal(
2289 CacheFileHandle
* aHandle
, bool aIgnoreShutdownLag
) {
2291 ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() [handle=%p, "
2292 "ignore shutdown=%d]",
2293 aHandle
, aIgnoreShutdownLag
));
2295 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
2298 DebugOnly
<bool> found
;
2299 found
= mHandlesByLastUsed
.RemoveElement(aHandle
);
2303 PRFileDesc
* fd
= aHandle
->mFD
;
2304 aHandle
->mFD
= nullptr;
2306 // Leak invalid (w/o metadata) and doomed handles immediately after shutdown.
2307 // Leak other handles when past the shutdown time maximum lag.
2310 ((aHandle
->mInvalid
|| aHandle
->mIsDoomed
) &&
2311 MOZ_UNLIKELY(CacheObserver::ShuttingDown())) ||
2313 MOZ_UNLIKELY(!aIgnoreShutdownLag
&&
2314 CacheObserver::IsPastShutdownIOLag())) {
2315 // Don't bother closing this file. Return a failure code from here will
2316 // cause any following IO operation on the file (mainly removal) to be
2317 // bypassed, which is what we want.
2318 // For mInvalid == true the entry will never be used, since it doesn't
2319 // have correct metadata, thus we don't need to worry about removing it.
2320 // For mIsDoomed == true the file is already in the doomed sub-dir and
2321 // will be removed on next session start.
2322 LOG((" past the shutdown I/O lag, leaking file handle"));
2323 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
2327 // The filedesc has already been closed before, just let go.
2331 CacheIOThread::Cancelable
cancelable(!aHandle
->IsSpecialFile());
2333 PRStatus status
= PR_Close(fd
);
2334 if (status
!= PR_SUCCESS
) {
2336 ("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() "
2337 "failed to close [handle=%p, status=%u]",
2339 return NS_ERROR_FAILURE
;
2342 LOG(("CacheFileIOManager::MaybeReleaseNSPRHandleInternal() DONE"));
2348 nsresult
CacheFileIOManager::TruncateSeekSetEOF(
2349 CacheFileHandle
* aHandle
, int64_t aTruncatePos
, int64_t aEOFPos
,
2350 CacheFileIOListener
* aCallback
) {
2352 ("CacheFileIOManager::TruncateSeekSetEOF() [handle=%p, "
2353 "truncatePos=%" PRId64
", "
2354 "EOFPos=%" PRId64
", listener=%p]",
2355 aHandle
, aTruncatePos
, aEOFPos
, aCallback
));
2358 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2360 if (aHandle
->IsClosed() || (aCallback
&& aCallback
->IsKilled()) || !ioMan
) {
2361 return NS_ERROR_NOT_INITIALIZED
;
2364 RefPtr
<TruncateSeekSetEOFEvent
> ev
=
2365 new TruncateSeekSetEOFEvent(aHandle
, aTruncatePos
, aEOFPos
, aCallback
);
2366 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->mPriority
2367 ? CacheIOThread::WRITE_PRIORITY
2368 : CacheIOThread::WRITE
);
2369 NS_ENSURE_SUCCESS(rv
, rv
);
2375 void CacheFileIOManager::GetCacheDirectory(nsIFile
** result
) {
2378 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2379 if (!ioMan
|| !ioMan
->mCacheDirectory
) {
2383 ioMan
->mCacheDirectory
->Clone(result
);
2386 #if defined(MOZ_WIDGET_ANDROID)
2389 void CacheFileIOManager::GetProfilelessCacheDirectory(nsIFile
** result
) {
2392 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2393 if (!ioMan
|| !ioMan
->mCacheProfilelessDirectory
) {
2397 ioMan
->mCacheProfilelessDirectory
->Clone(result
);
2403 nsresult
CacheFileIOManager::GetEntryInfo(
2404 const SHA1Sum::Hash
* aHash
,
2405 CacheStorageService::EntryInfoCallback
* aCallback
) {
2406 MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
2410 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2412 return NS_ERROR_NOT_INITIALIZED
;
2415 nsAutoCString enhanceId
;
2416 nsAutoCString uriSpec
;
2418 RefPtr
<CacheFileHandle
> handle
;
2419 ioMan
->mHandles
.GetHandle(aHash
, getter_AddRefs(handle
));
2421 RefPtr
<nsILoadContextInfo
> info
=
2422 CacheFileUtils::ParseKey(handle
->Key(), &enhanceId
, &uriSpec
);
2426 return NS_OK
; // ignore
2429 RefPtr
<CacheStorageService
> service
= CacheStorageService::Self();
2431 return NS_ERROR_NOT_INITIALIZED
;
2434 // Invokes OnCacheEntryInfo when an existing entry is found
2435 if (service
->GetCacheEntryInfo(info
, enhanceId
, uriSpec
, aCallback
)) {
2439 // When we are here, there is no existing entry and we need
2440 // to synchrnously load metadata from a disk file.
2443 // Locate the actual file
2444 nsCOMPtr
<nsIFile
> file
;
2445 ioMan
->GetFile(aHash
, getter_AddRefs(file
));
2447 // Read metadata from the file synchronously
2448 RefPtr
<CacheFileMetadata
> metadata
= new CacheFileMetadata();
2449 rv
= metadata
->SyncReadMetadata(file
);
2450 if (NS_FAILED(rv
)) {
2454 // Now get the context + enhance id + URL from the key.
2455 RefPtr
<nsILoadContextInfo
> info
=
2456 CacheFileUtils::ParseKey(metadata
->GetKey(), &enhanceId
, &uriSpec
);
2462 // Pick all data to pass to the callback.
2463 int64_t dataSize
= metadata
->Offset();
2464 uint32_t fetchCount
= metadata
->GetFetchCount();
2465 uint32_t expirationTime
= metadata
->GetExpirationTime();
2466 uint32_t lastModified
= metadata
->GetLastModified();
2468 // Call directly on the callback.
2469 aCallback
->OnEntryInfo(uriSpec
, enhanceId
, dataSize
, fetchCount
, lastModified
,
2470 expirationTime
, metadata
->Pinned(), info
);
2475 nsresult
CacheFileIOManager::TruncateSeekSetEOFInternal(
2476 CacheFileHandle
* aHandle
, int64_t aTruncatePos
, int64_t aEOFPos
) {
2478 ("CacheFileIOManager::TruncateSeekSetEOFInternal() [handle=%p, "
2479 "truncatePos=%" PRId64
", EOFPos=%" PRId64
"]",
2480 aHandle
, aTruncatePos
, aEOFPos
));
2484 if (aHandle
->mKilled
) {
2485 LOG((" handle already killed, file not truncated"));
2489 if (CacheObserver::ShuttingDown() && !aHandle
->mFD
) {
2490 aHandle
->mKilled
= true;
2491 LOG((" killing the handle, file not truncated"));
2495 CacheIOThread::Cancelable
cancelable(!aHandle
->IsSpecialFile());
2497 if (!aHandle
->mFileExists
) {
2498 rv
= CreateFile(aHandle
);
2499 NS_ENSURE_SUCCESS(rv
, rv
);
2502 if (!aHandle
->mFD
) {
2503 rv
= OpenNSPRHandle(aHandle
);
2504 NS_ENSURE_SUCCESS(rv
, rv
);
2506 NSPRHandleUsed(aHandle
);
2509 // Check again, OpenNSPRHandle could figure out the file was gone.
2510 if (!aHandle
->mFileExists
) {
2511 return NS_ERROR_NOT_AVAILABLE
;
2514 // When this operation would increase cache size, check whether the cache size
2515 // reached the hard limit and whether it would cause critical low disk space.
2516 if (aHandle
->mFileSize
< aEOFPos
) {
2517 if (mOverLimitEvicting
&& mCacheSizeOnHardLimit
) {
2519 ("CacheFileIOManager::TruncateSeekSetEOFInternal() - failing because "
2520 "cache size reached hard limit!"));
2521 return NS_ERROR_FILE_DISK_FULL
;
2525 rv
= mCacheDirectory
->GetDiskSpaceAvailable(&freeSpace
);
2526 if (NS_WARN_IF(NS_FAILED(rv
))) {
2529 ("CacheFileIOManager::TruncateSeekSetEOFInternal() - "
2530 "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32
"]",
2531 static_cast<uint32_t>(rv
)));
2533 freeSpace
>>= 10; // bytes to kilobytes
2534 uint32_t limit
= CacheObserver::DiskFreeSpaceHardLimit();
2535 if (freeSpace
- aEOFPos
+ aHandle
->mFileSize
< limit
) {
2537 ("CacheFileIOManager::TruncateSeekSetEOFInternal() - Low free space"
2538 ", refusing to write! [freeSpace=%" PRId64
"kB, limit=%ukB]",
2540 return NS_ERROR_FILE_DISK_FULL
;
2545 // This operation always invalidates the entry
2546 aHandle
->mInvalid
= true;
2548 rv
= TruncFile(aHandle
->mFD
, aTruncatePos
);
2549 NS_ENSURE_SUCCESS(rv
, rv
);
2551 if (aTruncatePos
!= aEOFPos
) {
2552 rv
= TruncFile(aHandle
->mFD
, aEOFPos
);
2553 NS_ENSURE_SUCCESS(rv
, rv
);
2556 uint32_t oldSizeInK
= aHandle
->FileSizeInK();
2557 aHandle
->mFileSize
= aEOFPos
;
2558 uint32_t newSizeInK
= aHandle
->FileSizeInK();
2560 if (oldSizeInK
!= newSizeInK
&& !aHandle
->IsDoomed() &&
2561 !aHandle
->IsSpecialFile()) {
2562 CacheIndex::UpdateEntry(aHandle
->Hash(), nullptr, nullptr, nullptr, nullptr,
2563 nullptr, &newSizeInK
);
2565 if (oldSizeInK
< newSizeInK
) {
2566 EvictIfOverLimitInternal();
2574 nsresult
CacheFileIOManager::RenameFile(CacheFileHandle
* aHandle
,
2575 const nsACString
& aNewName
,
2576 CacheFileIOListener
* aCallback
) {
2577 LOG(("CacheFileIOManager::RenameFile() [handle=%p, newName=%s, listener=%p]",
2578 aHandle
, PromiseFlatCString(aNewName
).get(), aCallback
));
2581 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2583 if (aHandle
->IsClosed() || !ioMan
) {
2584 return NS_ERROR_NOT_INITIALIZED
;
2587 if (!aHandle
->IsSpecialFile()) {
2588 return NS_ERROR_UNEXPECTED
;
2591 RefPtr
<RenameFileEvent
> ev
=
2592 new RenameFileEvent(aHandle
, aNewName
, aCallback
);
2593 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->mPriority
2594 ? CacheIOThread::WRITE_PRIORITY
2595 : CacheIOThread::WRITE
);
2596 NS_ENSURE_SUCCESS(rv
, rv
);
2601 nsresult
CacheFileIOManager::RenameFileInternal(CacheFileHandle
* aHandle
,
2602 const nsACString
& aNewName
) {
2603 LOG(("CacheFileIOManager::RenameFileInternal() [handle=%p, newName=%s]",
2604 aHandle
, PromiseFlatCString(aNewName
).get()));
2608 MOZ_ASSERT(aHandle
->IsSpecialFile());
2610 if (aHandle
->IsDoomed()) {
2611 return NS_ERROR_NOT_AVAILABLE
;
2614 // Doom old handle if it exists and is not doomed
2615 for (uint32_t i
= 0; i
< mSpecialHandles
.Length(); i
++) {
2616 if (!mSpecialHandles
[i
]->IsDoomed() &&
2617 mSpecialHandles
[i
]->Key() == aNewName
) {
2618 MOZ_ASSERT(aHandle
!= mSpecialHandles
[i
]);
2619 rv
= DoomFileInternal(mSpecialHandles
[i
]);
2620 NS_ENSURE_SUCCESS(rv
, rv
);
2625 nsCOMPtr
<nsIFile
> file
;
2626 rv
= GetSpecialFile(aNewName
, getter_AddRefs(file
));
2627 NS_ENSURE_SUCCESS(rv
, rv
);
2630 rv
= file
->Exists(&exists
);
2631 NS_ENSURE_SUCCESS(rv
, rv
);
2635 ("CacheFileIOManager::RenameFileInternal() - Removing old file from "
2637 rv
= file
->Remove(false);
2638 if (NS_FAILED(rv
)) {
2639 NS_WARNING("Cannot remove file from the disk");
2641 ("CacheFileIOManager::RenameFileInternal() - Removing old file failed"
2642 ". [rv=0x%08" PRIx32
"]",
2643 static_cast<uint32_t>(rv
)));
2647 if (!aHandle
->FileExists()) {
2648 aHandle
->mKey
= aNewName
;
2652 rv
= MaybeReleaseNSPRHandleInternal(aHandle
, true);
2653 NS_ENSURE_SUCCESS(rv
, rv
);
2655 rv
= aHandle
->mFile
->MoveToNative(nullptr, aNewName
);
2656 NS_ENSURE_SUCCESS(rv
, rv
);
2658 aHandle
->mKey
= aNewName
;
2663 nsresult
CacheFileIOManager::EvictIfOverLimit() {
2664 LOG(("CacheFileIOManager::EvictIfOverLimit()"));
2667 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2670 return NS_ERROR_NOT_INITIALIZED
;
2673 nsCOMPtr
<nsIRunnable
> ev
;
2674 ev
= NewRunnableMethod("net::CacheFileIOManager::EvictIfOverLimitInternal",
2675 ioMan
, &CacheFileIOManager::EvictIfOverLimitInternal
);
2677 rv
= ioMan
->mIOThread
->Dispatch(ev
, CacheIOThread::EVICT
);
2678 NS_ENSURE_SUCCESS(rv
, rv
);
2683 nsresult
CacheFileIOManager::EvictIfOverLimitInternal() {
2684 LOG(("CacheFileIOManager::EvictIfOverLimitInternal()"));
2688 MOZ_ASSERT(mIOThread
->IsCurrentThread());
2690 if (mShuttingDown
) {
2691 return NS_ERROR_NOT_INITIALIZED
;
2694 if (mOverLimitEvicting
) {
2696 ("CacheFileIOManager::EvictIfOverLimitInternal() - Eviction already "
2701 CacheIOThread::Cancelable
cancelable(true);
2704 rv
= mCacheDirectory
->GetDiskSpaceAvailable(&freeSpace
);
2705 if (NS_WARN_IF(NS_FAILED(rv
))) {
2708 // Do not change smart size.
2710 ("CacheFileIOManager::EvictIfOverLimitInternal() - "
2711 "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32
"]",
2712 static_cast<uint32_t>(rv
)));
2714 freeSpace
>>= 10; // bytes to kilobytes
2715 UpdateSmartCacheSize(freeSpace
);
2718 uint32_t cacheUsage
;
2719 rv
= CacheIndex::GetCacheSize(&cacheUsage
);
2720 NS_ENSURE_SUCCESS(rv
, rv
);
2722 uint32_t cacheLimit
= CacheObserver::DiskCacheCapacity();
2723 uint32_t freeSpaceLimit
= CacheObserver::DiskFreeSpaceSoftLimit();
2725 if (cacheUsage
<= cacheLimit
&&
2726 (freeSpace
== -1 || freeSpace
>= freeSpaceLimit
)) {
2728 ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size and free "
2729 "space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
2730 "freeSpace=%" PRId64
"kB, freeSpaceLimit=%ukB]",
2731 cacheUsage
, cacheLimit
, freeSpace
, freeSpaceLimit
));
2736 ("CacheFileIOManager::EvictIfOverLimitInternal() - Cache size exceeded "
2737 "limit. Starting overlimit eviction. [cacheSize=%ukB, limit=%ukB]",
2738 cacheUsage
, cacheLimit
));
2740 nsCOMPtr
<nsIRunnable
> ev
;
2741 ev
= NewRunnableMethod("net::CacheFileIOManager::OverLimitEvictionInternal",
2742 this, &CacheFileIOManager::OverLimitEvictionInternal
);
2744 rv
= mIOThread
->Dispatch(ev
, CacheIOThread::EVICT
);
2745 NS_ENSURE_SUCCESS(rv
, rv
);
2747 mOverLimitEvicting
= true;
2751 nsresult
CacheFileIOManager::OverLimitEvictionInternal() {
2752 LOG(("CacheFileIOManager::OverLimitEvictionInternal()"));
2756 MOZ_ASSERT(mIOThread
->IsCurrentThread());
2758 // mOverLimitEvicting is accessed only on IO thread, so we can set it to false
2759 // here and set it to true again once we dispatch another event that will
2760 // continue with the eviction. The reason why we do so is that we can fail
2761 // early anywhere in this method and the variable will contain a correct
2762 // value. Otherwise we would need to set it to false on every failing place.
2763 mOverLimitEvicting
= false;
2765 if (mShuttingDown
) {
2766 return NS_ERROR_NOT_INITIALIZED
;
2771 rv
= mCacheDirectory
->GetDiskSpaceAvailable(&freeSpace
);
2772 if (NS_WARN_IF(NS_FAILED(rv
))) {
2775 // Do not change smart size.
2777 ("CacheFileIOManager::EvictIfOverLimitInternal() - "
2778 "GetDiskSpaceAvailable() failed! [rv=0x%08" PRIx32
"]",
2779 static_cast<uint32_t>(rv
)));
2781 freeSpace
>>= 10; // bytes to kilobytes
2782 UpdateSmartCacheSize(freeSpace
);
2785 uint32_t cacheUsage
;
2786 rv
= CacheIndex::GetCacheSize(&cacheUsage
);
2787 NS_ENSURE_SUCCESS(rv
, rv
);
2789 uint32_t cacheLimit
= CacheObserver::DiskCacheCapacity();
2790 uint32_t freeSpaceLimit
= CacheObserver::DiskFreeSpaceSoftLimit();
2792 if (cacheUsage
> cacheLimit
) {
2794 ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size over "
2795 "limit. [cacheSize=%ukB, limit=%ukB]",
2796 cacheUsage
, cacheLimit
));
2798 // We allow cache size to go over the specified limit. Eviction should
2799 // keep the size within the limit in a long run, but in case the eviction
2800 // is too slow, the cache could go way over the limit. To prevent this we
2801 // set flag mCacheSizeOnHardLimit when the size reaches 105% of the limit
2802 // and WriteInternal() and TruncateSeekSetEOFInternal() fail to cache
2804 if ((cacheUsage
- cacheLimit
) > (cacheLimit
/ 20)) {
2806 ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size "
2807 "reached hard limit."));
2808 mCacheSizeOnHardLimit
= true;
2810 mCacheSizeOnHardLimit
= false;
2812 } else if (freeSpace
!= -1 && freeSpace
< freeSpaceLimit
) {
2814 ("CacheFileIOManager::OverLimitEvictionInternal() - Free space under "
2815 "limit. [freeSpace=%" PRId64
"kB, freeSpaceLimit=%ukB]",
2816 freeSpace
, freeSpaceLimit
));
2819 ("CacheFileIOManager::OverLimitEvictionInternal() - Cache size and "
2820 "free space in limits. [cacheSize=%ukB, cacheSizeLimit=%ukB, "
2821 "freeSpace=%" PRId64
"kB, freeSpaceLimit=%ukB]",
2822 cacheUsage
, cacheLimit
, freeSpace
, freeSpaceLimit
));
2824 mCacheSizeOnHardLimit
= false;
2828 if (CacheIOThread::YieldAndRerun()) {
2830 ("CacheFileIOManager::OverLimitEvictionInternal() - Breaking loop "
2831 "for higher level events."));
2832 mOverLimitEvicting
= true;
2838 static uint32_t consecutiveFailures
= 0;
2839 rv
= CacheIndex::GetEntryForEviction(false, &hash
, &cnt
);
2840 NS_ENSURE_SUCCESS(rv
, rv
);
2842 rv
= DoomFileByKeyInternal(&hash
);
2843 if (NS_SUCCEEDED(rv
)) {
2844 consecutiveFailures
= 0;
2845 } else if (rv
== NS_ERROR_NOT_AVAILABLE
) {
2847 ("CacheFileIOManager::OverLimitEvictionInternal() - "
2848 "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32
"]",
2849 static_cast<uint32_t>(rv
)));
2850 // TODO index is outdated, start update
2852 // Make sure index won't return the same entry again
2853 CacheIndex::RemoveEntry(&hash
);
2854 consecutiveFailures
= 0;
2856 // This shouldn't normally happen, but the eviction must not fail
2857 // completely if we ever encounter this problem.
2859 "CacheFileIOManager::OverLimitEvictionInternal() - Unexpected "
2860 "failure of DoomFileByKeyInternal()");
2863 ("CacheFileIOManager::OverLimitEvictionInternal() - "
2864 "DoomFileByKeyInternal() failed. [rv=0x%08" PRIx32
"]",
2865 static_cast<uint32_t>(rv
)));
2867 // Normally, CacheIndex::UpdateEntry() is called only to update newly
2868 // created/opened entries which are always fresh and UpdateEntry() expects
2869 // and checks this flag. The way we use UpdateEntry() here is a kind of
2870 // hack and we must make sure the flag is set by calling
2871 // EnsureEntryExists().
2872 rv
= CacheIndex::EnsureEntryExists(&hash
);
2873 NS_ENSURE_SUCCESS(rv
, rv
);
2875 // Move the entry at the end of both lists to make sure we won't end up
2876 // failing on one entry forever.
2877 uint32_t frecency
= 0;
2878 rv
= CacheIndex::UpdateEntry(&hash
, &frecency
, nullptr, nullptr, nullptr,
2880 NS_ENSURE_SUCCESS(rv
, rv
);
2882 consecutiveFailures
++;
2883 if (consecutiveFailures
>= cnt
) {
2884 // This doesn't necessarily mean that we've tried to doom every entry
2885 // but we've reached a sane number of tries. It is likely that another
2886 // eviction will start soon. And as said earlier, this normally doesn't
2893 MOZ_ASSERT_UNREACHABLE("We should never get here");
2898 nsresult
CacheFileIOManager::EvictAll() {
2899 LOG(("CacheFileIOManager::EvictAll()"));
2902 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
2905 return NS_ERROR_NOT_INITIALIZED
;
2908 nsCOMPtr
<nsIRunnable
> ev
;
2909 ev
= NewRunnableMethod("net::CacheFileIOManager::EvictAllInternal", ioMan
,
2910 &CacheFileIOManager::EvictAllInternal
);
2912 rv
= ioMan
->mIOThread
->DispatchAfterPendingOpens(ev
);
2913 if (NS_WARN_IF(NS_FAILED(rv
))) {
2922 class EvictionNotifierRunnable
: public Runnable
{
2924 EvictionNotifierRunnable() : Runnable("EvictionNotifierRunnable") {}
2929 EvictionNotifierRunnable::Run() {
2930 nsCOMPtr
<nsIObserverService
> obsSvc
= mozilla::services::GetObserverService();
2932 obsSvc
->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr);
2939 nsresult
CacheFileIOManager::EvictAllInternal() {
2940 LOG(("CacheFileIOManager::EvictAllInternal()"));
2944 MOZ_ASSERT(mIOThread
->IsCurrentThread());
2946 RefPtr
<EvictionNotifierRunnable
> r
= new EvictionNotifierRunnable();
2948 if (!mCacheDirectory
) {
2949 // This is a kind of hack. Somebody called EvictAll() without a profile.
2950 // This happens in xpcshell tests that use cache without profile. We need
2951 // to notify observers in this case since the tests are waiting for it.
2952 NS_DispatchToMainThread(r
);
2953 return NS_ERROR_FILE_INVALID_PATH
;
2956 if (mShuttingDown
) {
2957 return NS_ERROR_NOT_INITIALIZED
;
2960 if (!mTreeCreated
) {
2961 rv
= CreateCacheTree();
2962 if (NS_FAILED(rv
)) {
2967 // Doom all active handles
2968 nsTArray
<RefPtr
<CacheFileHandle
> > handles
;
2969 mHandles
.GetActiveHandles(&handles
);
2971 for (uint32_t i
= 0; i
< handles
.Length(); ++i
) {
2972 rv
= DoomFileInternal(handles
[i
]);
2973 if (NS_WARN_IF(NS_FAILED(rv
))) {
2975 ("CacheFileIOManager::EvictAllInternal() - Cannot doom handle "
2981 nsCOMPtr
<nsIFile
> file
;
2982 rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
2983 if (NS_WARN_IF(NS_FAILED(rv
))) {
2987 rv
= file
->AppendNative(nsLiteralCString(ENTRIES_DIR
));
2988 if (NS_WARN_IF(NS_FAILED(rv
))) {
2992 // Trash current entries directory
2993 rv
= TrashDirectory(file
);
2994 if (NS_WARN_IF(NS_FAILED(rv
))) {
2998 // Files are now inaccessible in entries directory, notify observers.
2999 NS_DispatchToMainThread(r
);
3001 // Create a new empty entries directory
3002 rv
= CheckAndCreateDir(mCacheDirectory
, ENTRIES_DIR
, false);
3003 if (NS_WARN_IF(NS_FAILED(rv
))) {
3007 CacheIndex::RemoveAll();
3013 nsresult
CacheFileIOManager::EvictByContext(
3014 nsILoadContextInfo
* aLoadContextInfo
, bool aPinned
,
3015 const nsAString
& aOrigin
) {
3016 LOG(("CacheFileIOManager::EvictByContext() [loadContextInfo=%p]",
3020 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
3023 return NS_ERROR_NOT_INITIALIZED
;
3026 nsCOMPtr
<nsIRunnable
> ev
;
3027 ev
= NewRunnableMethod
<nsCOMPtr
<nsILoadContextInfo
>, bool, nsString
>(
3028 "net::CacheFileIOManager::EvictByContextInternal", ioMan
,
3029 &CacheFileIOManager::EvictByContextInternal
, aLoadContextInfo
, aPinned
,
3032 rv
= ioMan
->mIOThread
->DispatchAfterPendingOpens(ev
);
3033 if (NS_WARN_IF(NS_FAILED(rv
))) {
3040 nsresult
CacheFileIOManager::EvictByContextInternal(
3041 nsILoadContextInfo
* aLoadContextInfo
, bool aPinned
,
3042 const nsAString
& aOrigin
) {
3044 ("CacheFileIOManager::EvictByContextInternal() [loadContextInfo=%p, "
3046 aLoadContextInfo
, aPinned
));
3050 if (aLoadContextInfo
) {
3051 nsAutoCString suffix
;
3052 aLoadContextInfo
->OriginAttributesPtr()->CreateSuffix(suffix
);
3053 LOG((" anonymous=%u, suffix=%s]", aLoadContextInfo
->IsAnonymous(),
3056 MOZ_ASSERT(mIOThread
->IsCurrentThread());
3058 MOZ_ASSERT(!aLoadContextInfo
->IsPrivate());
3059 if (aLoadContextInfo
->IsPrivate()) {
3060 return NS_ERROR_INVALID_ARG
;
3064 if (!mCacheDirectory
) {
3065 // This is a kind of hack. Somebody called EvictAll() without a profile.
3066 // This happens in xpcshell tests that use cache without profile. We need
3067 // to notify observers in this case since the tests are waiting for it.
3068 // Also notify for aPinned == true, those are interested as well.
3069 if (!aLoadContextInfo
) {
3070 RefPtr
<EvictionNotifierRunnable
> r
= new EvictionNotifierRunnable();
3071 NS_DispatchToMainThread(r
);
3073 return NS_ERROR_FILE_INVALID_PATH
;
3076 if (mShuttingDown
) {
3077 return NS_ERROR_NOT_INITIALIZED
;
3080 if (!mTreeCreated
) {
3081 rv
= CreateCacheTree();
3082 if (NS_FAILED(rv
)) {
3087 NS_ConvertUTF16toUTF8
origin(aOrigin
);
3089 // Doom all active handles that matches the load context
3090 nsTArray
<RefPtr
<CacheFileHandle
> > handles
;
3091 mHandles
.GetActiveHandles(&handles
);
3093 for (uint32_t i
= 0; i
< handles
.Length(); ++i
) {
3094 CacheFileHandle
* handle
= handles
[i
];
3096 nsAutoCString uriSpec
;
3097 RefPtr
<nsILoadContextInfo
> info
=
3098 CacheFileUtils::ParseKey(handle
->Key(), nullptr, &uriSpec
);
3101 ("CacheFileIOManager::EvictByContextInternal() - Cannot parse key in "
3102 "handle! [handle=%p, key=%s]",
3103 handle
, handle
->Key().get()));
3104 MOZ_CRASH("Unexpected error!");
3107 if (aLoadContextInfo
&& !info
->Equals(aLoadContextInfo
)) {
3111 if (!origin
.IsEmpty()) {
3113 rv
= MozURL::Init(getter_AddRefs(url
), uriSpec
);
3114 if (NS_FAILED(rv
)) {
3118 nsAutoCString urlOrigin
;
3119 url
->Origin(urlOrigin
);
3121 if (!urlOrigin
.Equals(origin
)) {
3126 // handle will be doomed only when pinning status is known and equal or
3127 // doom decision will be deferred until pinning status is determined.
3128 rv
= DoomFileInternal(handle
,
3129 aPinned
? CacheFileIOManager::DOOM_WHEN_PINNED
3130 : CacheFileIOManager::DOOM_WHEN_NON_PINNED
);
3131 if (NS_WARN_IF(NS_FAILED(rv
))) {
3133 ("CacheFileIOManager::EvictByContextInternal() - Cannot doom handle"
3139 if (!aLoadContextInfo
) {
3140 RefPtr
<EvictionNotifierRunnable
> r
= new EvictionNotifierRunnable();
3141 NS_DispatchToMainThread(r
);
3144 if (!mContextEvictor
) {
3145 mContextEvictor
= new CacheFileContextEvictor();
3146 mContextEvictor
->Init(mCacheDirectory
);
3149 mContextEvictor
->AddContext(aLoadContextInfo
, aPinned
, aOrigin
);
3155 nsresult
CacheFileIOManager::CacheIndexStateChanged() {
3156 LOG(("CacheFileIOManager::CacheIndexStateChanged()"));
3160 // CacheFileIOManager lives longer than CacheIndex so gInstance must be
3162 MOZ_ASSERT(gInstance
);
3164 // We have to re-distatch even if we are on IO thread to prevent reentering
3165 // the lock in CacheIndex
3166 nsCOMPtr
<nsIRunnable
> ev
= NewRunnableMethod(
3167 "net::CacheFileIOManager::CacheIndexStateChangedInternal",
3168 gInstance
.get(), &CacheFileIOManager::CacheIndexStateChangedInternal
);
3170 nsCOMPtr
<nsIEventTarget
> ioTarget
= IOTarget();
3171 MOZ_ASSERT(ioTarget
);
3173 rv
= ioTarget
->Dispatch(ev
, nsIEventTarget::DISPATCH_NORMAL
);
3174 if (NS_WARN_IF(NS_FAILED(rv
))) {
3181 void CacheFileIOManager::CacheIndexStateChangedInternal() {
3182 if (mShuttingDown
) {
3183 // ignore notification during shutdown
3187 if (!mContextEvictor
) {
3191 mContextEvictor
->CacheIndexStateChanged();
3194 nsresult
CacheFileIOManager::TrashDirectory(nsIFile
* aFile
) {
3195 LOG(("CacheFileIOManager::TrashDirectory() [file=%s]",
3196 aFile
->HumanReadablePath().get()));
3200 MOZ_ASSERT(mIOThread
->IsCurrentThread());
3201 MOZ_ASSERT(mCacheDirectory
);
3203 // When the directory is empty, it is cheaper to remove it directly instead of
3204 // using the trash mechanism.
3206 rv
= IsEmptyDirectory(aFile
, &isEmpty
);
3207 NS_ENSURE_SUCCESS(rv
, rv
);
3210 rv
= aFile
->Remove(false);
3212 ("CacheFileIOManager::TrashDirectory() - Directory removed "
3213 "[rv=0x%08" PRIx32
"]",
3214 static_cast<uint32_t>(rv
)));
3219 nsCOMPtr
<nsIFile
> dirCheck
;
3220 rv
= aFile
->GetParent(getter_AddRefs(dirCheck
));
3221 NS_ENSURE_SUCCESS(rv
, rv
);
3223 bool equals
= false;
3224 rv
= dirCheck
->Equals(mCacheDirectory
, &equals
);
3225 NS_ENSURE_SUCCESS(rv
, rv
);
3230 nsCOMPtr
<nsIFile
> dir
, trash
;
3233 rv
= aFile
->Clone(getter_AddRefs(dir
));
3234 NS_ENSURE_SUCCESS(rv
, rv
);
3236 rv
= aFile
->Clone(getter_AddRefs(trash
));
3237 NS_ENSURE_SUCCESS(rv
, rv
);
3239 const int32_t kMaxTries
= 16;
3240 srand(static_cast<unsigned>(PR_Now()));
3241 for (int32_t triesCount
= 0;; ++triesCount
) {
3243 leaf
.AppendInt(rand());
3244 rv
= trash
->SetNativeLeafName(leaf
);
3245 NS_ENSURE_SUCCESS(rv
, rv
);
3248 if (NS_SUCCEEDED(trash
->Exists(&exists
)) && !exists
) {
3253 ("CacheFileIOManager::TrashDirectory() - Trash directory already "
3257 if (triesCount
== kMaxTries
) {
3259 ("CacheFileIOManager::TrashDirectory() - Could not find unused trash "
3260 "directory in %d tries.",
3262 return NS_ERROR_FAILURE
;
3266 LOG(("CacheFileIOManager::TrashDirectory() - Renaming directory [leaf=%s]",
3269 rv
= dir
->MoveToNative(nullptr, leaf
);
3270 NS_ENSURE_SUCCESS(rv
, rv
);
3272 StartRemovingTrash();
3277 void CacheFileIOManager::OnTrashTimer(nsITimer
* aTimer
, void* aClosure
) {
3278 LOG(("CacheFileIOManager::OnTrashTimer() [timer=%p, closure=%p]", aTimer
,
3281 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
3287 ioMan
->mTrashTimer
= nullptr;
3288 ioMan
->StartRemovingTrash();
3291 nsresult
CacheFileIOManager::StartRemovingTrash() {
3292 LOG(("CacheFileIOManager::StartRemovingTrash()"));
3296 MOZ_ASSERT(mIOThread
->IsCurrentThread());
3298 if (mShuttingDown
) {
3299 return NS_ERROR_NOT_INITIALIZED
;
3302 if (!mCacheDirectory
) {
3303 return NS_ERROR_FILE_INVALID_PATH
;
3307 LOG(("CacheFileIOManager::StartRemovingTrash() - Trash timer exists."));
3311 if (mRemovingTrashDirs
) {
3313 ("CacheFileIOManager::StartRemovingTrash() - Trash removing in "
3318 uint32_t elapsed
= (TimeStamp::NowLoRes() - mStartTime
).ToMilliseconds();
3319 if (elapsed
< kRemoveTrashStartDelay
) {
3320 nsCOMPtr
<nsIEventTarget
> ioTarget
= IOTarget();
3321 MOZ_ASSERT(ioTarget
);
3323 return NS_NewTimerWithFuncCallback(
3324 getter_AddRefs(mTrashTimer
), CacheFileIOManager::OnTrashTimer
, nullptr,
3325 kRemoveTrashStartDelay
- elapsed
, nsITimer::TYPE_ONE_SHOT
,
3326 "net::CacheFileIOManager::StartRemovingTrash", ioTarget
);
3329 nsCOMPtr
<nsIRunnable
> ev
;
3330 ev
= NewRunnableMethod("net::CacheFileIOManager::RemoveTrashInternal", this,
3331 &CacheFileIOManager::RemoveTrashInternal
);
3333 rv
= mIOThread
->Dispatch(ev
, CacheIOThread::EVICT
);
3334 NS_ENSURE_SUCCESS(rv
, rv
);
3336 mRemovingTrashDirs
= true;
3340 nsresult
CacheFileIOManager::RemoveTrashInternal() {
3341 LOG(("CacheFileIOManager::RemoveTrashInternal()"));
3345 MOZ_ASSERT(mIOThread
->IsCurrentThread());
3347 if (mShuttingDown
) {
3348 return NS_ERROR_NOT_INITIALIZED
;
3351 CacheIOThread::Cancelable
cancelable(true);
3353 MOZ_ASSERT(!mTrashTimer
);
3354 MOZ_ASSERT(mRemovingTrashDirs
);
3356 if (!mTreeCreated
) {
3357 rv
= CreateCacheTree();
3358 if (NS_FAILED(rv
)) {
3363 // mRemovingTrashDirs is accessed only on IO thread, so we can drop the flag
3364 // here and set it again once we dispatch a continuation event. By doing so,
3365 // we don't have to drop the flag on any possible early return.
3366 mRemovingTrashDirs
= false;
3369 if (CacheIOThread::YieldAndRerun()) {
3371 ("CacheFileIOManager::RemoveTrashInternal() - Breaking loop for "
3372 "higher level events."));
3373 mRemovingTrashDirs
= true;
3377 // Find some trash directory
3379 MOZ_ASSERT(!mTrashDirEnumerator
);
3381 rv
= FindTrashDirToRemove();
3382 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
3384 ("CacheFileIOManager::RemoveTrashInternal() - No trash directory "
3388 NS_ENSURE_SUCCESS(rv
, rv
);
3390 rv
= mTrashDir
->GetDirectoryEntries(getter_AddRefs(mTrashDirEnumerator
));
3391 NS_ENSURE_SUCCESS(rv
, rv
);
3393 continue; // check elapsed time
3396 // We null out mTrashDirEnumerator once we remove all files in the
3397 // directory, so remove the trash directory if we don't have enumerator.
3398 if (!mTrashDirEnumerator
) {
3399 rv
= mTrashDir
->Remove(false);
3400 if (NS_FAILED(rv
)) {
3401 // There is no reason why removing an empty directory should fail, but
3402 // if it does, we should continue and try to remove all other trash
3404 nsAutoCString leafName
;
3405 mTrashDir
->GetNativeLeafName(leafName
);
3406 mFailedTrashDirs
.AppendElement(leafName
);
3408 ("CacheFileIOManager::RemoveTrashInternal() - Cannot remove "
3409 "trashdir. [name=%s]",
3413 mTrashDir
= nullptr;
3414 continue; // check elapsed time
3417 nsCOMPtr
<nsIFile
> file
;
3418 rv
= mTrashDirEnumerator
->GetNextFile(getter_AddRefs(file
));
3420 mTrashDirEnumerator
->Close();
3421 mTrashDirEnumerator
= nullptr;
3422 continue; // check elapsed time
3425 file
->IsDirectory(&isDir
);
3428 "Found a directory in a trash directory! It will be removed "
3429 "recursively, but this can block IO thread for a while!");
3430 if (LOG_ENABLED()) {
3432 ("CacheFileIOManager::RemoveTrashInternal() - Found a directory in "
3434 "directory! It will be removed recursively, but this can block IO "
3435 "thread for a while! [file=%s]",
3436 file
->HumanReadablePath().get()));
3439 file
->Remove(isDir
);
3442 MOZ_ASSERT_UNREACHABLE("We should never get here");
3446 nsresult
CacheFileIOManager::FindTrashDirToRemove() {
3447 LOG(("CacheFileIOManager::FindTrashDirToRemove()"));
3451 // We call this method on the main thread during shutdown when user wants to
3452 // remove all cache files.
3453 MOZ_ASSERT(mIOThread
->IsCurrentThread() || mShuttingDown
);
3455 nsCOMPtr
<nsIDirectoryEnumerator
> iter
;
3456 rv
= mCacheDirectory
->GetDirectoryEntries(getter_AddRefs(iter
));
3457 NS_ENSURE_SUCCESS(rv
, rv
);
3459 nsCOMPtr
<nsIFile
> file
;
3460 while (NS_SUCCEEDED(iter
->GetNextFile(getter_AddRefs(file
))) && file
) {
3462 file
->IsDirectory(&isDir
);
3467 nsAutoCString leafName
;
3468 rv
= file
->GetNativeLeafName(leafName
);
3469 if (NS_FAILED(rv
)) {
3473 if (leafName
.Length() < strlen(TRASH_DIR
)) {
3477 if (!StringBeginsWith(leafName
, nsLiteralCString(TRASH_DIR
))) {
3481 if (mFailedTrashDirs
.Contains(leafName
)) {
3485 LOG(("CacheFileIOManager::FindTrashDirToRemove() - Returning directory %s",
3492 // When we're here we've tried to delete all trash directories. Clear
3493 // mFailedTrashDirs so we will try to delete them again when we start removing
3494 // trash directories next time.
3495 mFailedTrashDirs
.Clear();
3496 return NS_ERROR_NOT_AVAILABLE
;
3500 nsresult
CacheFileIOManager::InitIndexEntry(CacheFileHandle
* aHandle
,
3501 OriginAttrsHash aOriginAttrsHash
,
3502 bool aAnonymous
, bool aPinning
) {
3504 ("CacheFileIOManager::InitIndexEntry() [handle=%p, "
3505 "originAttrsHash=%" PRIx64
", "
3506 "anonymous=%d, pinning=%d]",
3507 aHandle
, aOriginAttrsHash
, aAnonymous
, aPinning
));
3510 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
3512 if (aHandle
->IsClosed() || !ioMan
) {
3513 return NS_ERROR_NOT_INITIALIZED
;
3516 if (aHandle
->IsSpecialFile()) {
3517 return NS_ERROR_UNEXPECTED
;
3520 RefPtr
<InitIndexEntryEvent
> ev
=
3521 new InitIndexEntryEvent(aHandle
, aOriginAttrsHash
, aAnonymous
, aPinning
);
3522 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->mPriority
3523 ? CacheIOThread::WRITE_PRIORITY
3524 : CacheIOThread::WRITE
);
3525 NS_ENSURE_SUCCESS(rv
, rv
);
3531 nsresult
CacheFileIOManager::UpdateIndexEntry(CacheFileHandle
* aHandle
,
3532 const uint32_t* aFrecency
,
3533 const bool* aHasAltData
,
3534 const uint16_t* aOnStartTime
,
3535 const uint16_t* aOnStopTime
,
3536 const uint8_t* aContentType
) {
3538 ("CacheFileIOManager::UpdateIndexEntry() [handle=%p, frecency=%s, "
3539 "hasAltData=%s, onStartTime=%s, onStopTime=%s, contentType=%s]",
3540 aHandle
, aFrecency
? nsPrintfCString("%u", *aFrecency
).get() : "",
3541 aHasAltData
? (*aHasAltData
? "true" : "false") : "",
3542 aOnStartTime
? nsPrintfCString("%u", *aOnStartTime
).get() : "",
3543 aOnStopTime
? nsPrintfCString("%u", *aOnStopTime
).get() : "",
3544 aContentType
? nsPrintfCString("%u", *aContentType
).get() : ""));
3547 RefPtr
<CacheFileIOManager
> ioMan
= gInstance
;
3549 if (aHandle
->IsClosed() || !ioMan
) {
3550 return NS_ERROR_NOT_INITIALIZED
;
3553 if (aHandle
->IsSpecialFile()) {
3554 return NS_ERROR_UNEXPECTED
;
3557 RefPtr
<UpdateIndexEntryEvent
> ev
= new UpdateIndexEntryEvent(
3558 aHandle
, aFrecency
, aHasAltData
, aOnStartTime
, aOnStopTime
, aContentType
);
3559 rv
= ioMan
->mIOThread
->Dispatch(ev
, aHandle
->mPriority
3560 ? CacheIOThread::WRITE_PRIORITY
3561 : CacheIOThread::WRITE
);
3562 NS_ENSURE_SUCCESS(rv
, rv
);
3567 nsresult
CacheFileIOManager::CreateFile(CacheFileHandle
* aHandle
) {
3568 MOZ_ASSERT(!aHandle
->mFD
);
3569 MOZ_ASSERT(aHandle
->mFile
);
3573 if (aHandle
->IsDoomed()) {
3574 nsCOMPtr
<nsIFile
> file
;
3576 rv
= GetDoomedFile(getter_AddRefs(file
));
3577 NS_ENSURE_SUCCESS(rv
, rv
);
3579 aHandle
->mFile
.swap(file
);
3582 if (NS_SUCCEEDED(aHandle
->mFile
->Exists(&exists
)) && exists
) {
3583 NS_WARNING("Found a file that should not exist!");
3587 rv
= OpenNSPRHandle(aHandle
, true);
3588 NS_ENSURE_SUCCESS(rv
, rv
);
3590 aHandle
->mFileSize
= 0;
3595 void CacheFileIOManager::HashToStr(const SHA1Sum::Hash
* aHash
,
3596 nsACString
& _retval
) {
3598 const char hexChars
[] = {'0', '1', '2', '3', '4', '5', '6', '7',
3599 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
3600 for (uint32_t i
= 0; i
< sizeof(SHA1Sum::Hash
); i
++) {
3601 _retval
.Append(hexChars
[(*aHash
)[i
] >> 4]);
3602 _retval
.Append(hexChars
[(*aHash
)[i
] & 0xF]);
3607 nsresult
CacheFileIOManager::StrToHash(const nsACString
& aHash
,
3608 SHA1Sum::Hash
* _retval
) {
3609 if (aHash
.Length() != 2 * sizeof(SHA1Sum::Hash
)) {
3610 return NS_ERROR_INVALID_ARG
;
3613 for (uint32_t i
= 0; i
< aHash
.Length(); i
++) {
3616 if (aHash
[i
] >= '0' && aHash
[i
] <= '9') {
3617 value
= aHash
[i
] - '0';
3618 } else if (aHash
[i
] >= 'A' && aHash
[i
] <= 'F') {
3619 value
= aHash
[i
] - 'A' + 10;
3620 } else if (aHash
[i
] >= 'a' && aHash
[i
] <= 'f') {
3621 value
= aHash
[i
] - 'a' + 10;
3623 return NS_ERROR_INVALID_ARG
;
3627 (reinterpret_cast<uint8_t*>(_retval
))[i
/ 2] = value
<< 4;
3629 (reinterpret_cast<uint8_t*>(_retval
))[i
/ 2] += value
;
3636 nsresult
CacheFileIOManager::GetFile(const SHA1Sum::Hash
* aHash
,
3637 nsIFile
** _retval
) {
3639 nsCOMPtr
<nsIFile
> file
;
3640 rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
3641 NS_ENSURE_SUCCESS(rv
, rv
);
3643 rv
= file
->AppendNative(nsLiteralCString(ENTRIES_DIR
));
3644 NS_ENSURE_SUCCESS(rv
, rv
);
3646 nsAutoCString leafName
;
3647 HashToStr(aHash
, leafName
);
3649 rv
= file
->AppendNative(leafName
);
3650 NS_ENSURE_SUCCESS(rv
, rv
);
3652 file
.swap(*_retval
);
3656 nsresult
CacheFileIOManager::GetSpecialFile(const nsACString
& aKey
,
3657 nsIFile
** _retval
) {
3659 nsCOMPtr
<nsIFile
> file
;
3660 rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
3661 NS_ENSURE_SUCCESS(rv
, rv
);
3663 rv
= file
->AppendNative(aKey
);
3664 NS_ENSURE_SUCCESS(rv
, rv
);
3666 file
.swap(*_retval
);
3670 nsresult
CacheFileIOManager::GetDoomedFile(nsIFile
** _retval
) {
3672 nsCOMPtr
<nsIFile
> file
;
3673 rv
= mCacheDirectory
->Clone(getter_AddRefs(file
));
3674 NS_ENSURE_SUCCESS(rv
, rv
);
3676 rv
= file
->AppendNative(nsLiteralCString(DOOMED_DIR
));
3677 NS_ENSURE_SUCCESS(rv
, rv
);
3679 rv
= file
->AppendNative("dummyleaf"_ns
);
3680 NS_ENSURE_SUCCESS(rv
, rv
);
3682 const int32_t kMaxTries
= 64;
3683 srand(static_cast<unsigned>(PR_Now()));
3684 nsAutoCString leafName
;
3685 for (int32_t triesCount
= 0;; ++triesCount
) {
3686 leafName
.AppendInt(rand());
3687 rv
= file
->SetNativeLeafName(leafName
);
3688 NS_ENSURE_SUCCESS(rv
, rv
);
3691 if (NS_SUCCEEDED(file
->Exists(&exists
)) && !exists
) {
3695 if (triesCount
== kMaxTries
) {
3697 ("CacheFileIOManager::GetDoomedFile() - Could not find unused file "
3698 "name in %d tries.",
3700 return NS_ERROR_FAILURE
;
3703 leafName
.Truncate();
3706 file
.swap(*_retval
);
3710 nsresult
CacheFileIOManager::IsEmptyDirectory(nsIFile
* aFile
, bool* _retval
) {
3711 MOZ_ASSERT(mIOThread
->IsCurrentThread());
3715 nsCOMPtr
<nsIDirectoryEnumerator
> enumerator
;
3716 rv
= aFile
->GetDirectoryEntries(getter_AddRefs(enumerator
));
3717 NS_ENSURE_SUCCESS(rv
, rv
);
3719 bool hasMoreElements
= false;
3720 rv
= enumerator
->HasMoreElements(&hasMoreElements
);
3721 NS_ENSURE_SUCCESS(rv
, rv
);
3723 *_retval
= !hasMoreElements
;
3727 nsresult
CacheFileIOManager::CheckAndCreateDir(nsIFile
* aFile
, const char* aDir
,
3728 bool aEnsureEmptyDir
) {
3731 nsCOMPtr
<nsIFile
> file
;
3735 nsAutoCString
dir(aDir
);
3736 rv
= aFile
->Clone(getter_AddRefs(file
));
3737 NS_ENSURE_SUCCESS(rv
, rv
);
3738 rv
= file
->AppendNative(dir
);
3739 NS_ENSURE_SUCCESS(rv
, rv
);
3742 bool exists
= false;
3743 rv
= file
->Exists(&exists
);
3744 if (NS_SUCCEEDED(rv
) && exists
) {
3745 bool isDirectory
= false;
3746 rv
= file
->IsDirectory(&isDirectory
);
3747 if (NS_FAILED(rv
) || !isDirectory
) {
3748 // Try to remove the file
3749 rv
= file
->Remove(false);
3750 if (NS_SUCCEEDED(rv
)) {
3754 NS_ENSURE_SUCCESS(rv
, rv
);
3757 if (aEnsureEmptyDir
&& NS_SUCCEEDED(rv
) && exists
) {
3759 rv
= IsEmptyDirectory(file
, &isEmpty
);
3760 NS_ENSURE_SUCCESS(rv
, rv
);
3763 // Don't check the result, if this fails, it's OK. We do this
3764 // only for the doomed directory that doesn't need to be deleted
3765 // for the cost of completely disabling the whole browser.
3766 TrashDirectory(file
);
3770 if (NS_SUCCEEDED(rv
) && !exists
) {
3771 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0700);
3773 if (NS_FAILED(rv
)) {
3774 NS_WARNING("Cannot create directory");
3775 return NS_ERROR_FAILURE
;
3781 nsresult
CacheFileIOManager::CreateCacheTree() {
3782 MOZ_ASSERT(mIOThread
->IsCurrentThread());
3783 MOZ_ASSERT(!mTreeCreated
);
3785 if (!mCacheDirectory
|| mTreeCreationFailed
) {
3786 return NS_ERROR_FILE_INVALID_PATH
;
3791 // Set the flag here and clear it again below when the tree is created
3793 mTreeCreationFailed
= true;
3795 // ensure parent directory exists
3796 nsCOMPtr
<nsIFile
> parentDir
;
3797 rv
= mCacheDirectory
->GetParent(getter_AddRefs(parentDir
));
3798 NS_ENSURE_SUCCESS(rv
, rv
);
3799 rv
= CheckAndCreateDir(parentDir
, nullptr, false);
3800 NS_ENSURE_SUCCESS(rv
, rv
);
3802 // ensure cache directory exists
3803 rv
= CheckAndCreateDir(mCacheDirectory
, nullptr, false);
3804 NS_ENSURE_SUCCESS(rv
, rv
);
3806 // ensure entries directory exists
3807 rv
= CheckAndCreateDir(mCacheDirectory
, ENTRIES_DIR
, false);
3808 NS_ENSURE_SUCCESS(rv
, rv
);
3810 // ensure doomed directory exists
3811 rv
= CheckAndCreateDir(mCacheDirectory
, DOOMED_DIR
, true);
3812 NS_ENSURE_SUCCESS(rv
, rv
);
3814 mTreeCreated
= true;
3815 mTreeCreationFailed
= false;
3817 if (!mContextEvictor
) {
3818 RefPtr
<CacheFileContextEvictor
> contextEvictor
;
3819 contextEvictor
= new CacheFileContextEvictor();
3821 // Init() method will try to load unfinished contexts from the disk. Store
3822 // the evictor as a member only when there is some unfinished job.
3823 contextEvictor
->Init(mCacheDirectory
);
3824 if (contextEvictor
->ContextsCount()) {
3825 contextEvictor
.swap(mContextEvictor
);
3829 StartRemovingTrash();
3834 nsresult
CacheFileIOManager::OpenNSPRHandle(CacheFileHandle
* aHandle
,
3836 LOG(("CacheFileIOManager::OpenNSPRHandle BEGIN, handle=%p", aHandle
));
3838 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3839 MOZ_ASSERT(!aHandle
->mFD
);
3840 MOZ_ASSERT(mHandlesByLastUsed
.IndexOf(aHandle
) == mHandlesByLastUsed
.NoIndex
);
3841 MOZ_ASSERT(mHandlesByLastUsed
.Length() <= kOpenHandlesLimit
);
3842 MOZ_ASSERT((aCreate
&& !aHandle
->mFileExists
) ||
3843 (!aCreate
&& aHandle
->mFileExists
));
3847 if (mHandlesByLastUsed
.Length() == kOpenHandlesLimit
) {
3848 // close handle that hasn't been used for the longest time
3849 rv
= MaybeReleaseNSPRHandleInternal(mHandlesByLastUsed
[0], true);
3850 NS_ENSURE_SUCCESS(rv
, rv
);
3854 rv
= aHandle
->mFile
->OpenNSPRFileDesc(
3855 PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
, 0600, &aHandle
->mFD
);
3856 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
|| // error from nsLocalFileWin
3857 rv
== NS_ERROR_FILE_NO_DEVICE_SPACE
) { // error from nsLocalFileUnix
3859 ("CacheFileIOManager::OpenNSPRHandle() - Cannot create a new file, we"
3860 " might reached a limit on FAT32. Will evict a single entry and try "
3861 "again. [hash=%08x%08x%08x%08x%08x]",
3862 LOGSHA1(aHandle
->Hash())));
3867 rv
= CacheIndex::GetEntryForEviction(true, &hash
, &cnt
);
3868 if (NS_SUCCEEDED(rv
)) {
3869 rv
= DoomFileByKeyInternal(&hash
);
3871 if (NS_SUCCEEDED(rv
)) {
3872 rv
= aHandle
->mFile
->OpenNSPRFileDesc(
3873 PR_RDWR
| PR_CREATE_FILE
| PR_TRUNCATE
, 0600, &aHandle
->mFD
);
3875 ("CacheFileIOManager::OpenNSPRHandle() - Successfully evicted entry"
3876 " with hash %08x%08x%08x%08x%08x. %s to create the new file.",
3877 LOGSHA1(&hash
), NS_SUCCEEDED(rv
) ? "Succeeded" : "Failed"));
3879 // Report the full size only once per session
3880 static bool sSizeReported
= false;
3881 if (!sSizeReported
) {
3882 uint32_t cacheUsage
;
3883 if (NS_SUCCEEDED(CacheIndex::GetCacheSize(&cacheUsage
))) {
3885 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE_FULL_FAT
,
3887 sSizeReported
= true;
3892 ("CacheFileIOManager::OpenNSPRHandle() - Couldn't evict an existing"
3894 rv
= NS_ERROR_FILE_NO_DEVICE_SPACE
;
3897 if (NS_FAILED(rv
)) {
3899 ("CacheFileIOManager::OpenNSPRHandle() Create failed with "
3901 static_cast<uint32_t>(rv
)));
3903 NS_ENSURE_SUCCESS(rv
, rv
);
3905 aHandle
->mFileExists
= true;
3907 rv
= aHandle
->mFile
->OpenNSPRFileDesc(PR_RDWR
, 0600, &aHandle
->mFD
);
3908 if (NS_ERROR_FILE_NOT_FOUND
== rv
) {
3909 LOG((" file doesn't exists"));
3910 aHandle
->mFileExists
= false;
3911 return DoomFileInternal(aHandle
);
3913 if (NS_FAILED(rv
)) {
3914 LOG(("CacheFileIOManager::OpenNSPRHandle() Open failed with 0x%08" PRIx32
,
3915 static_cast<uint32_t>(rv
)));
3917 NS_ENSURE_SUCCESS(rv
, rv
);
3920 mHandlesByLastUsed
.AppendElement(aHandle
);
3922 LOG(("CacheFileIOManager::OpenNSPRHandle END, handle=%p", aHandle
));
3927 void CacheFileIOManager::NSPRHandleUsed(CacheFileHandle
* aHandle
) {
3928 MOZ_ASSERT(CacheFileIOManager::IsOnIOThreadOrCeased());
3929 MOZ_ASSERT(aHandle
->mFD
);
3931 DebugOnly
<bool> found
;
3932 found
= mHandlesByLastUsed
.RemoveElement(aHandle
);
3935 mHandlesByLastUsed
.AppendElement(aHandle
);
3938 nsresult
CacheFileIOManager::SyncRemoveDir(nsIFile
* aFile
, const char* aDir
) {
3940 nsCOMPtr
<nsIFile
> file
;
3945 rv
= aFile
->Clone(getter_AddRefs(file
));
3946 if (NS_WARN_IF(NS_FAILED(rv
))) {
3950 rv
= file
->AppendNative(nsDependentCString(aDir
));
3951 if (NS_WARN_IF(NS_FAILED(rv
))) {
3956 if (LOG_ENABLED()) {
3957 LOG(("CacheFileIOManager::SyncRemoveDir() - Removing directory %s",
3958 file
->HumanReadablePath().get()));
3961 rv
= file
->Remove(true);
3962 if (NS_WARN_IF(NS_FAILED(rv
))) {
3964 ("CacheFileIOManager::SyncRemoveDir() - Removing failed! "
3965 "[rv=0x%08" PRIx32
"]",
3966 static_cast<uint32_t>(rv
)));
3972 void CacheFileIOManager::SyncRemoveAllCacheFiles() {
3973 LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
3977 SyncRemoveDir(mCacheDirectory
, ENTRIES_DIR
);
3978 SyncRemoveDir(mCacheDirectory
, DOOMED_DIR
);
3980 // Clear any intermediate state of trash dir enumeration.
3981 mFailedTrashDirs
.Clear();
3982 mTrashDir
= nullptr;
3985 // FindTrashDirToRemove() fills mTrashDir if there is any trash directory.
3986 rv
= FindTrashDirToRemove();
3987 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
3989 ("CacheFileIOManager::SyncRemoveAllCacheFiles() - No trash directory "
3993 if (NS_WARN_IF(NS_FAILED(rv
))) {
3995 ("CacheFileIOManager::SyncRemoveAllCacheFiles() - "
3996 "FindTrashDirToRemove() returned an unexpected error. "
3997 "[rv=0x%08" PRIx32
"]",
3998 static_cast<uint32_t>(rv
)));
4002 rv
= SyncRemoveDir(mTrashDir
, nullptr);
4003 if (NS_FAILED(rv
)) {
4004 nsAutoCString leafName
;
4005 mTrashDir
->GetNativeLeafName(leafName
);
4006 mFailedTrashDirs
.AppendElement(leafName
);
4011 // Returns default ("smart") size (in KB) of cache, given available disk space
4013 static uint32_t SmartCacheSize(const int64_t availKB
) {
4016 if (CacheObserver::ClearCacheOnShutdown()) {
4017 maxSize
= kMaxClearOnShutdownCacheSizeKB
;
4019 maxSize
= kMaxCacheSizeKB
;
4022 if (availKB
> 25 * 1024 * 1024) {
4023 return maxSize
; // skip computing if we're over 25 GB
4026 // Grow/shrink in 10 MB units, deliberately, so that in the common case we
4027 // don't shrink cache and evict items every time we startup (it's important
4028 // that we don't slow down startup benchmarks).
4029 uint32_t sz10MBs
= 0;
4030 uint32_t avail10MBs
= availKB
/ (1024 * 10);
4032 // 2.5% of space above 7GB
4033 if (avail10MBs
> 700) {
4034 sz10MBs
+= static_cast<uint32_t>((avail10MBs
- 700) * .025);
4037 // 7.5% of space between 500 MB -> 7 GB
4038 if (avail10MBs
> 50) {
4039 sz10MBs
+= static_cast<uint32_t>((avail10MBs
- 50) * .075);
4044 // On Android, smaller/older devices may have very little storage and
4045 // device owners may be sensitive to storage footprint: Use a smaller
4046 // percentage of available space and a smaller minimum.
4048 // 16% of space up to 500 MB (10 MB min)
4049 sz10MBs
+= std::max
<uint32_t>(1, static_cast<uint32_t>(avail10MBs
* .16));
4051 // 30% of space up to 500 MB (50 MB min)
4052 sz10MBs
+= std::max
<uint32_t>(5, static_cast<uint32_t>(avail10MBs
* .3));
4055 return std::min
<uint32_t>(maxSize
, sz10MBs
* 10 * 1024);
4058 nsresult
CacheFileIOManager::UpdateSmartCacheSize(int64_t aFreeSpace
) {
4059 MOZ_ASSERT(mIOThread
->IsCurrentThread());
4063 if (!CacheObserver::SmartCacheSizeEnabled()) {
4064 return NS_ERROR_NOT_AVAILABLE
;
4067 // Wait at least kSmartSizeUpdateInterval before recomputing smart size.
4068 static const TimeDuration kUpdateLimit
=
4069 TimeDuration::FromMilliseconds(kSmartSizeUpdateInterval
);
4070 if (!mLastSmartSizeTime
.IsNull() &&
4071 (TimeStamp::NowLoRes() - mLastSmartSizeTime
) < kUpdateLimit
) {
4075 // Do not compute smart size when cache size is not reliable.
4076 bool isUpToDate
= false;
4077 CacheIndex::IsUpToDate(&isUpToDate
);
4079 return NS_ERROR_NOT_AVAILABLE
;
4082 uint32_t cacheUsage
;
4083 rv
= CacheIndex::GetCacheSize(&cacheUsage
);
4084 if (NS_WARN_IF(NS_FAILED(rv
))) {
4086 ("CacheFileIOManager::UpdateSmartCacheSize() - Cannot get cacheUsage! "
4087 "[rv=0x%08" PRIx32
"]",
4088 static_cast<uint32_t>(rv
)));
4092 mLastSmartSizeTime
= TimeStamp::NowLoRes();
4094 uint32_t smartSize
= SmartCacheSize(aFreeSpace
+ cacheUsage
);
4096 if (smartSize
== CacheObserver::DiskCacheCapacity()) {
4097 // Smart size has not changed.
4101 CacheObserver::SetSmartDiskCacheCapacity(smartSize
);
4110 // A helper class that dispatches and waits for an event that gets result of
4111 // CacheFileIOManager->mHandles.SizeOfExcludingThis() on the I/O thread
4112 // to safely get handles memory report.
4113 // We must do this, since the handle list is only accessed and managed w/o
4114 // locking on the I/O thread. That is by design.
4115 class SizeOfHandlesRunnable
: public Runnable
{
4117 SizeOfHandlesRunnable(mozilla::MallocSizeOf mallocSizeOf
,
4118 CacheFileHandles
const& handles
,
4119 nsTArray
<CacheFileHandle
*> const& specialHandles
)
4120 : Runnable("net::SizeOfHandlesRunnable"),
4121 mMonitor("SizeOfHandlesRunnable.mMonitor"),
4122 mMonitorNotified(false),
4123 mMallocSizeOf(mallocSizeOf
),
4125 mSpecialHandles(specialHandles
),
4128 size_t Get(CacheIOThread
* thread
) {
4129 nsCOMPtr
<nsIEventTarget
> target
= thread
->Target();
4131 NS_ERROR("If we have the I/O thread we also must have the I/O target");
4135 mozilla::MonitorAutoLock
mon(mMonitor
);
4136 mMonitorNotified
= false;
4137 nsresult rv
= target
->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL
);
4138 if (NS_FAILED(rv
)) {
4139 NS_ERROR("Dispatch failed, cannot do memory report of CacheFileHandles");
4143 while (!mMonitorNotified
) {
4149 NS_IMETHOD
Run() override
{
4150 mozilla::MonitorAutoLock
mon(mMonitor
);
4151 // Excluding this since the object itself is a member of CacheFileIOManager
4152 // reported in CacheFileIOManager::SizeOfIncludingThis as part of |this|.
4153 mSize
= mHandles
.SizeOfExcludingThis(mMallocSizeOf
);
4154 for (uint32_t i
= 0; i
< mSpecialHandles
.Length(); ++i
) {
4155 mSize
+= mSpecialHandles
[i
]->SizeOfIncludingThis(mMallocSizeOf
);
4158 mMonitorNotified
= true;
4164 mozilla::Monitor mMonitor
;
4165 bool mMonitorNotified
;
4166 mozilla::MallocSizeOf mMallocSizeOf
;
4167 CacheFileHandles
const& mHandles
;
4168 nsTArray
<CacheFileHandle
*> const& mSpecialHandles
;
4174 size_t CacheFileIOManager::SizeOfExcludingThisInternal(
4175 mozilla::MallocSizeOf mallocSizeOf
) const {
4177 nsCOMPtr
<nsISizeOf
> sizeOf
;
4180 n
+= mIOThread
->SizeOfIncludingThis(mallocSizeOf
);
4182 // mHandles and mSpecialHandles must be accessed only on the I/O thread,
4183 // must sync dispatch.
4184 RefPtr
<SizeOfHandlesRunnable
> sizeOfHandlesRunnable
=
4185 new SizeOfHandlesRunnable(mallocSizeOf
, mHandles
, mSpecialHandles
);
4186 n
+= sizeOfHandlesRunnable
->Get(mIOThread
);
4189 // mHandlesByLastUsed just refers handles reported by mHandles.
4191 sizeOf
= do_QueryInterface(mCacheDirectory
);
4192 if (sizeOf
) n
+= sizeOf
->SizeOfIncludingThis(mallocSizeOf
);
4194 sizeOf
= do_QueryInterface(mMetadataWritesTimer
);
4195 if (sizeOf
) n
+= sizeOf
->SizeOfIncludingThis(mallocSizeOf
);
4197 sizeOf
= do_QueryInterface(mTrashTimer
);
4198 if (sizeOf
) n
+= sizeOf
->SizeOfIncludingThis(mallocSizeOf
);
4200 sizeOf
= do_QueryInterface(mTrashDir
);
4201 if (sizeOf
) n
+= sizeOf
->SizeOfIncludingThis(mallocSizeOf
);
4203 for (uint32_t i
= 0; i
< mFailedTrashDirs
.Length(); ++i
) {
4204 n
+= mFailedTrashDirs
[i
].SizeOfExcludingThisIfUnshared(mallocSizeOf
);
4211 size_t CacheFileIOManager::SizeOfExcludingThis(
4212 mozilla::MallocSizeOf mallocSizeOf
) {
4213 if (!gInstance
) return 0;
4215 return gInstance
->SizeOfExcludingThisInternal(mallocSizeOf
);
4219 size_t CacheFileIOManager::SizeOfIncludingThis(
4220 mozilla::MallocSizeOf mallocSizeOf
) {
4221 return mallocSizeOf(gInstance
) + SizeOfExcludingThis(mallocSizeOf
);
4225 } // namespace mozilla