diff options
Diffstat (limited to 'dom/quota/DirectoryLockImpl.cpp')
-rw-r--r-- | dom/quota/DirectoryLockImpl.cpp | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/dom/quota/DirectoryLockImpl.cpp b/dom/quota/DirectoryLockImpl.cpp new file mode 100644 index 0000000000..2993971c7a --- /dev/null +++ b/dom/quota/DirectoryLockImpl.cpp @@ -0,0 +1,381 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DirectoryLockImpl.h" + +#include "mozilla/ReverseIterator.h" +#include "mozilla/dom/quota/Client.h" +#include "mozilla/dom/quota/QuotaManager.h" + +namespace mozilla::dom::quota { + +DirectoryLockImpl::DirectoryLockImpl( + MovingNotNull<RefPtr<QuotaManager>> aQuotaManager, + const Nullable<PersistenceType>& aPersistenceType, + const nsACString& aSuffix, const nsACString& aGroup, + const OriginScope& aOriginScope, const nsACString& aStorageOrigin, + bool aIsPrivate, const Nullable<Client::Type>& aClientType, + const bool aExclusive, const bool aInternal, + const ShouldUpdateLockIdTableFlag aShouldUpdateLockIdTableFlag) + : mQuotaManager(std::move(aQuotaManager)), + mPersistenceType(aPersistenceType), + mSuffix(aSuffix), + mGroup(aGroup), + mOriginScope(aOriginScope), + mStorageOrigin(aStorageOrigin), + mClientType(aClientType), + mId(mQuotaManager->GenerateDirectoryLockId()), + mIsPrivate(aIsPrivate), + mExclusive(aExclusive), + mInternal(aInternal), + mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag == + ShouldUpdateLockIdTableFlag::Yes), + mRegistered(false) { + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty()); + MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull()); + MOZ_ASSERT_IF(!aInternal, + aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID); + MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty()); + MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin()); + MOZ_ASSERT_IF(!aInternal, !aStorageOrigin.IsEmpty()); + MOZ_ASSERT_IF(!aInternal && !aIsPrivate, + aOriginScope.GetOrigin() == aStorageOrigin); + MOZ_ASSERT_IF(!aInternal && aIsPrivate, + aOriginScope.GetOrigin() != aStorageOrigin); + MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull()); + MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax()); +} + +DirectoryLockImpl::~DirectoryLockImpl() { + AssertIsOnOwningThread(); + + // We must call UnregisterDirectoryLock before unblocking other locks because + // UnregisterDirectoryLock also updates the origin last access time and the + // access flag (if the last lock for given origin is unregistered). One of the + // blocked locks could be requested by the clear/reset operation which stores + // cached information about origins in storage.sqlite. So if the access flag + // is not updated before unblocking the lock for reset/clear, we might store + // invalid information which can lead to omitting origin initialization during + // next temporary storage initialization. + if (mRegistered) { + mQuotaManager->UnregisterDirectoryLock(*this); + } + + MOZ_ASSERT(!mRegistered); + + for (NotNull<RefPtr<DirectoryLockImpl>> blockingLock : mBlocking) { + blockingLock->MaybeUnblock(*this); + } + + mBlocking.Clear(); +} + +#ifdef DEBUG + +void DirectoryLockImpl::AssertIsOnOwningThread() const { + mQuotaManager->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +bool DirectoryLockImpl::Overlaps(const DirectoryLockImpl& aLock) const { + AssertIsOnOwningThread(); + + // If the persistence types don't overlap, the op can proceed. + if (!aLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() && + aLock.mPersistenceType.Value() != mPersistenceType.Value()) { + return false; + } + + // If the origin scopes don't overlap, the op can proceed. + bool match = aLock.mOriginScope.Matches(mOriginScope); + if (!match) { + return false; + } + + // If the client types don't overlap, the op can proceed. + if (!aLock.mClientType.IsNull() && !mClientType.IsNull() && + aLock.mClientType.Value() != mClientType.Value()) { + return false; + } + + // Otherwise, when all attributes overlap (persistence type, origin scope and + // client type) the op must wait. + return true; +} + +bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aLock) const { + AssertIsOnOwningThread(); + + // Waiting is never required if the ops in comparison represent shared locks. + if (!aLock.mExclusive && !mExclusive) { + return false; + } + + // Wait if the ops overlap. + return Overlaps(aLock); +} + +void DirectoryLockImpl::NotifyOpenListener() { + AssertIsOnOwningThread(); + + if (mInvalidated) { + if (mOpenListener) { + (*mOpenListener)->DirectoryLockFailed(); + } else { + mAcquirePromiseHolder.Reject(NS_ERROR_FAILURE, __func__); + } + } else { +#ifdef DEBUG + mAcquired.Flip(); +#endif + + if (mOpenListener) { + (*mOpenListener) + ->DirectoryLockAcquired(static_cast<UniversalDirectoryLock*>(this)); + } else { + mAcquirePromiseHolder.Resolve(true, __func__); + } + } + + if (mOpenListener) { + mOpenListener.destroy(); + } else { + MOZ_ASSERT(mAcquirePromiseHolder.IsEmpty()); + } + + mQuotaManager->RemovePendingDirectoryLock(*this); + + mPending.Flip(); +} + +void DirectoryLockImpl::Acquire(RefPtr<OpenDirectoryListener> aOpenListener) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aOpenListener); + + mOpenListener.init(WrapNotNullUnchecked(std::move(aOpenListener))); + + AcquireInternal(); +} + +RefPtr<BoolPromise> DirectoryLockImpl::Acquire() { + AssertIsOnOwningThread(); + + RefPtr<BoolPromise> result = mAcquirePromiseHolder.Ensure(__func__); + + AcquireInternal(); + + return result; +} + +void DirectoryLockImpl::AcquireInternal() { + AssertIsOnOwningThread(); + + mQuotaManager->AddPendingDirectoryLock(*this); + + // See if this lock needs to wait. + bool blocked = false; + + // XXX It is probably unnecessary to iterate this in reverse order. + for (DirectoryLockImpl* const existingLock : + Reversed(mQuotaManager->mDirectoryLocks)) { + if (MustWaitFor(*existingLock)) { + existingLock->AddBlockingLock(*this); + AddBlockedOnLock(*existingLock); + blocked = true; + } + } + + mQuotaManager->RegisterDirectoryLock(*this); + + // Otherwise, notify the open listener immediately. + if (!blocked) { + NotifyOpenListener(); + return; + } + + if (!mExclusive || !mInternal) { + return; + } + + // All the locks that block this new exclusive internal lock need to be + // invalidated. We also need to notify clients to abort operations for them. + QuotaManager::DirectoryLockIdTableArray lockIds; + lockIds.SetLength(Client::TypeMax()); + + const auto& blockedOnLocks = GetBlockedOnLocks(); + MOZ_ASSERT(!blockedOnLocks.IsEmpty()); + + for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) { + if (!blockedOnLock->IsInternal()) { + blockedOnLock->Invalidate(); + + // Clients don't have to handle pending locks. Invalidation is sufficient + // in that case (once a lock is ready and the listener needs to be + // notified, we will call DirectoryLockFailed instead of + // DirectoryLockAcquired which should release any remaining references to + // the lock). + if (!blockedOnLock->IsPending()) { + lockIds[blockedOnLock->ClientType()].Put(blockedOnLock->Id()); + } + } + } + + mQuotaManager->AbortOperationsForLocks(lockIds); +} + +void DirectoryLockImpl::AcquireImmediately() { + AssertIsOnOwningThread(); + +#ifdef DEBUG + for (const DirectoryLockImpl* const existingLock : + mQuotaManager->mDirectoryLocks) { + MOZ_ASSERT(!MustWaitFor(*existingLock)); + } +#endif + + mQuotaManager->RegisterDirectoryLock(*this); +} + +#ifdef DEBUG +void DirectoryLockImpl::AssertIsAcquiredExclusively() { + AssertIsOnOwningThread(); + MOZ_ASSERT(mBlockedOn.IsEmpty()); + MOZ_ASSERT(mExclusive); + MOZ_ASSERT(mInternal); + MOZ_ASSERT(mRegistered); + MOZ_ASSERT(!mInvalidated); + MOZ_ASSERT(mAcquired); + + bool found = false; + + for (const DirectoryLockImpl* const existingLock : + mQuotaManager->mDirectoryLocks) { + if (existingLock == this) { + MOZ_ASSERT(!found); + found = true; + } else if (existingLock->mAcquired) { + MOZ_ASSERT(false); + } + } + + MOZ_ASSERT(found); +} +#endif + +RefPtr<ClientDirectoryLock> DirectoryLockImpl::SpecializeForClient( + PersistenceType aPersistenceType, + const quota::OriginMetadata& aOriginMetadata, + Client::Type aClientType) const { + AssertIsOnOwningThread(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID); + MOZ_ASSERT(!aOriginMetadata.mGroup.IsEmpty()); + MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty()); + MOZ_ASSERT(aClientType < Client::TypeMax()); + MOZ_ASSERT(!mOpenListener); + MOZ_ASSERT(mAcquirePromiseHolder.IsEmpty()); + MOZ_ASSERT(mBlockedOn.IsEmpty()); + + if (NS_WARN_IF(mExclusive)) { + return nullptr; + } + + RefPtr<DirectoryLockImpl> lock = Create( + mQuotaManager, Nullable<PersistenceType>(aPersistenceType), + aOriginMetadata.mSuffix, aOriginMetadata.mGroup, + OriginScope::FromOrigin(aOriginMetadata.mOrigin), + aOriginMetadata.mStorageOrigin, aOriginMetadata.mIsPrivate, + Nullable<Client::Type>(aClientType), + /* aExclusive */ false, mInternal, ShouldUpdateLockIdTableFlag::Yes); + if (NS_WARN_IF(!Overlaps(*lock))) { + return nullptr; + } + +#ifdef DEBUG + for (DirectoryLockImpl* const existingLock : + Reversed(mQuotaManager->mDirectoryLocks)) { + if (existingLock != this && !existingLock->MustWaitFor(*this)) { + MOZ_ASSERT(!existingLock->MustWaitFor(*lock)); + } + } +#endif + + for (const auto& blockedLock : mBlocking) { + if (blockedLock->MustWaitFor(*lock)) { + lock->AddBlockingLock(*blockedLock); + blockedLock->AddBlockedOnLock(*lock); + } + } + + mQuotaManager->RegisterDirectoryLock(*lock); + + if (mInvalidated) { + lock->Invalidate(); + } + + return lock; +} + +void DirectoryLockImpl::Log() const { + AssertIsOnOwningThread(); + + if (!QM_LOG_TEST()) { + return; + } + + QM_LOG(("DirectoryLockImpl [%p]", this)); + + nsCString persistenceType; + if (mPersistenceType.IsNull()) { + persistenceType.AssignLiteral("null"); + } else { + persistenceType.Assign(PersistenceTypeToString(mPersistenceType.Value())); + } + QM_LOG((" mPersistenceType: %s", persistenceType.get())); + + QM_LOG((" mGroup: %s", mGroup.get())); + + nsCString originScope; + if (mOriginScope.IsOrigin()) { + originScope.AssignLiteral("origin:"); + originScope.Append(mOriginScope.GetOrigin()); + } else if (mOriginScope.IsPrefix()) { + originScope.AssignLiteral("prefix:"); + originScope.Append(mOriginScope.GetOriginNoSuffix()); + } else if (mOriginScope.IsPattern()) { + originScope.AssignLiteral("pattern:"); + // Can't call GetJSONPattern since it only works on the main thread. + } else { + MOZ_ASSERT(mOriginScope.IsNull()); + originScope.AssignLiteral("null"); + } + QM_LOG((" mOriginScope: %s", originScope.get())); + + const auto clientType = mClientType.IsNull() + ? nsAutoCString{"null"_ns} + : Client::TypeToText(mClientType.Value()); + QM_LOG((" mClientType: %s", clientType.get())); + + nsCString blockedOnString; + for (auto blockedOn : mBlockedOn) { + blockedOnString.Append( + nsPrintfCString(" [%p]", static_cast<void*>(blockedOn))); + } + QM_LOG((" mBlockedOn:%s", blockedOnString.get())); + + QM_LOG((" mExclusive: %d", mExclusive)); + + QM_LOG((" mInternal: %d", mInternal)); + + QM_LOG((" mInvalidated: %d", static_cast<bool>(mInvalidated))); + + for (auto blockedOn : mBlockedOn) { + blockedOn->Log(); + } +} + +} // namespace mozilla::dom::quota |