diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp index a81ee1a0f4a7..01dcfe018294 100644 --- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -6052,7 +6052,26 @@ void QuotaManager::FlushDirtyOriginInfos() { const uint32_t maxOriginsToSaveInOneBatch = StaticPrefs::dom_quotaManager_maxOriginsToSaveInOneBatch(); - MutexAutoLock lock(mQuotaMutex); + // Use TryLock to avoid deadlocking with DirtyTrackingAutoLock, which holds + // mQuotaMutex while dispatching FlagOriginInfoAsDirtyOnDisk to the IO thread + // and blocking on a CondVar. If the IO thread tried to acquire mQuotaMutex + // while a DTAL holder was waiting for its IO dispatch, both would block. + // + // FlushDirtyOriginInfos is the only IO-thread mQuotaMutex user that can run + // concurrently with a DTAL holder (it fires on a timer). Other IO-thread + // mQuotaMutex users are safe because all DTAL call sites are normal quota + // operations that require temporary storage to be initialized: + // - ClearOrigins, CleanupTemporaryStorage DEBUG checkers: only called + // during initialization, before clients can use temporary storage. + // - UnloadQuota, RemoveQuota: only called during shutdown, after all + // client operations have completed. + // + // Dirty origins are retained in the queue and will be flushed on the next + // timer cycle. + MutexAutoTryLock lock(mQuotaMutex); + if (!lock) { + return; + } const auto now = TimeStamp::Now(); uint32_t flushed = 0; @@ -7681,7 +7700,7 @@ RefPtr QuotaManager::InitializeAllTemporaryOrigins() { nsresult QuotaManager::FlagOriginInfoAsDirtyOnDisk( DirtyTrackingAutoLock& aProofOfLock, const OriginStateMetadata& aStateMetadata) { - AssertNotCurrentThreadOwnsQuotaMutex(); + AssertCurrentThreadOwnsQuotaMutex(); struct SharedState { mozilla::Mutex mMutex; @@ -8911,7 +8930,10 @@ void QuotaManager::CleanupTemporaryStorage() { // Verify that origins being cleared meet our criteria: // non-persisted, zero usage, and outside cutoff window auto checker = [&self = *this, cutoffTime](const auto& doomedOriginInfo) { - MutexAutoLock lock(self.mQuotaMutex); + MutexAutoTryLock lock(self.mQuotaMutex); + if (!lock) { + return; + } MOZ_ASSERT(!doomedOriginInfo->LockedPersisted()); MOZ_ASSERT(doomedOriginInfo->LockedUsage() == 0); MOZ_ASSERT(doomedOriginInfo->LockedAccessTime() < cutoffTime); @@ -8933,7 +8955,10 @@ void QuotaManager::CleanupTemporaryStorage() { #ifdef DEBUG auto nonPersistedChecker = [&self = *this](const auto& doomedOriginInfo) { - MutexAutoLock lock(self.mQuotaMutex); + MutexAutoTryLock lock(self.mQuotaMutex); + if (!lock) { + return; + } MOZ_ASSERT(!doomedOriginInfo->LockedPersisted()); }; #else diff --git a/dom/quota/CanonicalQuotaObject.cpp b/dom/quota/CanonicalQuotaObject.cpp index e8d41757bff3..0b4674758f33 100644 --- a/dom/quota/CanonicalQuotaObject.cpp +++ b/dom/quota/CanonicalQuotaObject.cpp @@ -229,6 +229,15 @@ bool CanonicalQuotaObject::LockedMaybeUpdateSize( // We unlocked and relocked several times so we need to recompute all the // essential variables and recheck the group limit. + if (!originInfo->mGroupInfo) { + // During the pause, the origin got removed. We can skip updating the + // values of the in-memory representation because the origin is no + // longer in memory. + DirtyTrackingAutoLock::PauseLock pauseLock(aProofOfLock); + quotaManager->FinalizeOriginEviction(std::move(locks)); + return false; + } + QM_ASSERT_NO_UNDERFLOW(aSize, mSize); const uint64_t increase = aSize - mSize; diff --git a/dom/quota/DirtyTrackingAutoLock.cpp b/dom/quota/DirtyTrackingAutoLock.cpp index 6637c2936e17..ded02d8aae11 100644 --- a/dom/quota/DirtyTrackingAutoLock.cpp +++ b/dom/quota/DirtyTrackingAutoLock.cpp @@ -47,15 +47,10 @@ void DirtyTrackingAutoLock::EagerMarkAsDirty() { auto stateMetadata = mOriginInfo->LockedFlattenToOriginStateMetadata(); stateMetadata.mDirty = true; - // The lock was acquired to set the dirty flag and timestamp above. - // Release it for the disk write that persists the flag, then re-acquire - // in RAII manner so the caller can proceed with the actual metadata - // modifications. - PauseLock pausedLock(*this); auto* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); - quotaManager->AssertNotCurrentThreadOwnsQuotaMutex(); + quotaManager->AssertCurrentThreadOwnsQuotaMutex(); quotaManager->FlagOriginInfoAsDirtyOnDisk(*this, stateMetadata); } diff --git a/dom/quota/DirtyTrackingAutoLock.h b/dom/quota/DirtyTrackingAutoLock.h index 7facbfb61200..bf9abc2f47fc 100644 --- a/dom/quota/DirtyTrackingAutoLock.h +++ b/dom/quota/DirtyTrackingAutoLock.h @@ -25,13 +25,17 @@ class OriginInfo; // on destruction. In addition, on construction it eagerly writes the dirty // flag to the storage database so that a crash mid-operation leaves the // origin marked as needing a full metadata resync on next startup. +// +// The mutex is held throughout the entire lifetime of this object, including +// during the eager disk write. FlushDirtyOriginInfos uses TryLock to avoid +// deadlocking with this path. class MOZ_RAII MOZ_SCOPED_CAPABILITY MOZ_CAPABILITY("dirty_tracking_autolock") DirtyTrackingAutoLock { public: // RAII helper that temporarily releases the quota mutex held by a - // DirtyTrackingAutoLock, allowing file I/O to proceed without holding - // the lock. The mutex is re-acquired when the PauseLock is destroyed. - // Analogous to MutexAutoUnlock. + // DirtyTrackingAutoLock. Used by the eviction path in LockedMaybeUpdateSize + // to release the lock during blocking I/O. The mutex is re-acquired when + // the PauseLock is destroyed. class MOZ_RAII MOZ_SCOPED_CAPABILITY PauseLock { public: explicit PauseLock(DirtyTrackingAutoLock& aAutoLock)