Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / quota / DirectoryLockImpl.cpp
blob186e3e62916457a80b83537c68fbb5c81ccb9303
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DirectoryLockImpl.h"
9 #include "mozilla/ReverseIterator.h"
10 #include "mozilla/dom/quota/Client.h"
11 #include "mozilla/dom/quota/QuotaManager.h"
13 namespace mozilla::dom::quota {
15 DirectoryLockImpl::DirectoryLockImpl(
16 MovingNotNull<RefPtr<QuotaManager>> aQuotaManager,
17 const Nullable<PersistenceType>& aPersistenceType,
18 const nsACString& aSuffix, const nsACString& aGroup,
19 const OriginScope& aOriginScope, const nsACString& aStorageOrigin,
20 bool aIsPrivate, const Nullable<Client::Type>& aClientType,
21 const bool aExclusive, const bool aInternal,
22 const ShouldUpdateLockIdTableFlag aShouldUpdateLockIdTableFlag,
23 const DirectoryLockCategory aCategory)
24 : mQuotaManager(std::move(aQuotaManager)),
25 mPersistenceType(aPersistenceType),
26 mSuffix(aSuffix),
27 mGroup(aGroup),
28 mOriginScope(aOriginScope),
29 mStorageOrigin(aStorageOrigin),
30 mClientType(aClientType),
31 mId(mQuotaManager->GenerateDirectoryLockId()),
32 mIsPrivate(aIsPrivate),
33 mExclusive(aExclusive),
34 mInternal(aInternal),
35 mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag ==
36 ShouldUpdateLockIdTableFlag::Yes),
37 mCategory(aCategory),
38 mRegistered(false) {
39 AssertIsOnOwningThread();
40 MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
41 MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
42 MOZ_ASSERT_IF(!aInternal,
43 aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
44 MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
45 MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
46 MOZ_ASSERT_IF(!aInternal, !aStorageOrigin.IsEmpty());
47 MOZ_ASSERT_IF(!aInternal && !aIsPrivate,
48 aOriginScope.GetOrigin() == aStorageOrigin);
49 MOZ_ASSERT_IF(!aInternal && aIsPrivate,
50 aOriginScope.GetOrigin() != aStorageOrigin);
51 MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
52 MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax());
55 DirectoryLockImpl::~DirectoryLockImpl() {
56 AssertIsOnOwningThread();
58 if (!mDropped) {
59 Drop();
63 #ifdef DEBUG
65 void DirectoryLockImpl::AssertIsOnOwningThread() const {
66 mQuotaManager->AssertIsOnOwningThread();
69 #endif // DEBUG
71 bool DirectoryLockImpl::Overlaps(const DirectoryLockImpl& aLock) const {
72 AssertIsOnOwningThread();
74 // If the persistence types don't overlap, the op can proceed.
75 if (!aLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
76 aLock.mPersistenceType.Value() != mPersistenceType.Value()) {
77 return false;
80 // If the origin scopes don't overlap, the op can proceed.
81 bool match = aLock.mOriginScope.Matches(mOriginScope);
82 if (!match) {
83 return false;
86 // If the client types don't overlap, the op can proceed.
87 if (!aLock.mClientType.IsNull() && !mClientType.IsNull() &&
88 aLock.mClientType.Value() != mClientType.Value()) {
89 return false;
92 // Otherwise, when all attributes overlap (persistence type, origin scope and
93 // client type) the op must wait.
94 return true;
97 bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aLock) const {
98 AssertIsOnOwningThread();
100 // Waiting is never required if the ops in comparison represent shared locks.
101 if (!aLock.mExclusive && !mExclusive) {
102 return false;
105 // Wait if the ops overlap.
106 return Overlaps(aLock);
109 void DirectoryLockImpl::NotifyOpenListener() {
110 AssertIsOnOwningThread();
112 if (mInvalidated) {
113 mAcquirePromiseHolder.Reject(NS_ERROR_FAILURE, __func__);
114 } else {
115 mAcquired.Flip();
117 mAcquirePromiseHolder.Resolve(true, __func__);
120 MOZ_ASSERT(mAcquirePromiseHolder.IsEmpty());
122 mQuotaManager->RemovePendingDirectoryLock(*this);
124 mPending.Flip();
126 if (mInvalidated) {
127 mDropped.Flip();
129 Unregister();
133 void DirectoryLockImpl::Invalidate() {
134 AssertIsOnOwningThread();
136 mInvalidated.EnsureFlipped();
138 if (mInvalidateCallback) {
139 MOZ_ALWAYS_SUCCEEDS(GetCurrentSerialEventTarget()->Dispatch(
140 NS_NewRunnableFunction("DirectoryLockImpl::Invalidate",
141 [invalidateCallback = mInvalidateCallback]() {
142 invalidateCallback();
144 NS_DISPATCH_NORMAL));
148 void DirectoryLockImpl::Unregister() {
149 AssertIsOnOwningThread();
150 MOZ_ASSERT(mRegistered);
152 // We must call UnregisterDirectoryLock before unblocking other locks because
153 // UnregisterDirectoryLock also updates the origin last access time and the
154 // access flag (if the last lock for given origin is unregistered). One of the
155 // blocked locks could be requested by the clear/reset operation which stores
156 // cached information about origins in storage.sqlite. So if the access flag
157 // is not updated before unblocking the lock for reset/clear, we might store
158 // invalid information which can lead to omitting origin initialization during
159 // next temporary storage initialization.
160 mQuotaManager->UnregisterDirectoryLock(*this);
162 MOZ_ASSERT(!mRegistered);
164 for (NotNull<RefPtr<DirectoryLockImpl>> blockingLock : mBlocking) {
165 blockingLock->MaybeUnblock(*this);
168 mBlocking.Clear();
171 bool DirectoryLockImpl::MustWait() const {
172 AssertIsOnOwningThread();
173 MOZ_ASSERT(!mRegistered);
175 for (const DirectoryLockImpl* const existingLock :
176 mQuotaManager->mDirectoryLocks) {
177 if (MustWaitFor(*existingLock)) {
178 return true;
182 return false;
185 nsTArray<RefPtr<DirectoryLock>> DirectoryLockImpl::LocksMustWaitFor() const {
186 AssertIsOnOwningThread();
187 MOZ_ASSERT(!mRegistered);
189 nsTArray<RefPtr<DirectoryLock>> locks;
191 for (DirectoryLockImpl* const existingLock : mQuotaManager->mDirectoryLocks) {
192 if (MustWaitFor(*existingLock)) {
193 locks.AppendElement(static_cast<UniversalDirectoryLock*>(existingLock));
197 return locks;
200 RefPtr<BoolPromise> DirectoryLockImpl::Acquire() {
201 AssertIsOnOwningThread();
203 RefPtr<BoolPromise> result = mAcquirePromiseHolder.Ensure(__func__);
205 AcquireInternal();
207 return result;
210 void DirectoryLockImpl::AcquireInternal() {
211 AssertIsOnOwningThread();
213 mQuotaManager->AddPendingDirectoryLock(*this);
215 // See if this lock needs to wait.
216 bool blocked = false;
218 // XXX It is probably unnecessary to iterate this in reverse order.
219 for (DirectoryLockImpl* const existingLock :
220 Reversed(mQuotaManager->mDirectoryLocks)) {
221 if (MustWaitFor(*existingLock)) {
222 existingLock->AddBlockingLock(*this);
223 AddBlockedOnLock(*existingLock);
224 blocked = true;
228 mQuotaManager->RegisterDirectoryLock(*this);
230 // Otherwise, notify the open listener immediately.
231 if (!blocked) {
232 NotifyOpenListener();
233 return;
236 if (!mExclusive || !mInternal) {
237 return;
240 // All the locks that block this new exclusive internal lock need to be
241 // invalidated. We also need to notify clients to abort operations for them.
242 QuotaManager::DirectoryLockIdTableArray lockIds;
243 lockIds.SetLength(Client::TypeMax());
245 const auto& blockedOnLocks = GetBlockedOnLocks();
246 MOZ_ASSERT(!blockedOnLocks.IsEmpty());
248 for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) {
249 if (!blockedOnLock->IsInternal()) {
250 blockedOnLock->Invalidate();
252 // Clients don't have to handle pending locks. Invalidation is sufficient
253 // in that case (once a lock is ready and the listener needs to be
254 // notified, we will call DirectoryLockFailed instead of
255 // DirectoryLockAcquired which should release any remaining references to
256 // the lock).
257 if (!blockedOnLock->IsPending()) {
258 lockIds[blockedOnLock->ClientType()].Put(blockedOnLock->Id());
263 mQuotaManager->AbortOperationsForLocks(lockIds);
266 void DirectoryLockImpl::AcquireImmediately() {
267 AssertIsOnOwningThread();
268 MOZ_ASSERT(!MustWait());
270 mQuotaManager->RegisterDirectoryLock(*this);
272 mAcquired.Flip();
275 #ifdef DEBUG
276 void DirectoryLockImpl::AssertIsAcquiredExclusively() {
277 AssertIsOnOwningThread();
278 MOZ_ASSERT(mBlockedOn.IsEmpty());
279 MOZ_ASSERT(mExclusive);
280 MOZ_ASSERT(mInternal);
281 MOZ_ASSERT(mRegistered);
282 MOZ_ASSERT(!mInvalidated);
283 MOZ_ASSERT(mAcquired);
285 bool found = false;
287 for (const DirectoryLockImpl* const existingLock :
288 mQuotaManager->mDirectoryLocks) {
289 if (existingLock == this) {
290 MOZ_ASSERT(!found);
291 found = true;
292 } else if (existingLock->mAcquired) {
293 MOZ_ASSERT(false);
297 MOZ_ASSERT(found);
299 #endif
301 void DirectoryLockImpl::Drop() {
302 AssertIsOnOwningThread();
303 MOZ_ASSERT_IF(!mRegistered, mBlocking.IsEmpty());
305 mDropped.Flip();
307 if (mRegistered) {
308 Unregister();
312 void DirectoryLockImpl::OnInvalidate(std::function<void()>&& aCallback) {
313 mInvalidateCallback = std::move(aCallback);
316 RefPtr<ClientDirectoryLock> DirectoryLockImpl::SpecializeForClient(
317 PersistenceType aPersistenceType,
318 const quota::OriginMetadata& aOriginMetadata,
319 Client::Type aClientType) const {
320 AssertIsOnOwningThread();
321 MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
322 MOZ_ASSERT(!aOriginMetadata.mGroup.IsEmpty());
323 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
324 MOZ_ASSERT(aClientType < Client::TypeMax());
325 MOZ_ASSERT(mAcquirePromiseHolder.IsEmpty());
326 MOZ_ASSERT(mBlockedOn.IsEmpty());
328 if (NS_WARN_IF(mExclusive)) {
329 return nullptr;
332 RefPtr<DirectoryLockImpl> lock =
333 Create(mQuotaManager, Nullable<PersistenceType>(aPersistenceType),
334 aOriginMetadata.mSuffix, aOriginMetadata.mGroup,
335 OriginScope::FromOrigin(aOriginMetadata.mOrigin),
336 aOriginMetadata.mStorageOrigin, aOriginMetadata.mIsPrivate,
337 Nullable<Client::Type>(aClientType),
338 /* aExclusive */ false, mInternal,
339 ShouldUpdateLockIdTableFlag::Yes, mCategory);
340 if (NS_WARN_IF(!Overlaps(*lock))) {
341 return nullptr;
344 #ifdef DEBUG
345 for (DirectoryLockImpl* const existingLock :
346 Reversed(mQuotaManager->mDirectoryLocks)) {
347 if (existingLock != this && !existingLock->MustWaitFor(*this)) {
348 MOZ_ASSERT(!existingLock->MustWaitFor(*lock));
351 #endif
353 for (const auto& blockedLock : mBlocking) {
354 if (blockedLock->MustWaitFor(*lock)) {
355 lock->AddBlockingLock(*blockedLock);
356 blockedLock->AddBlockedOnLock(*lock);
360 mQuotaManager->RegisterDirectoryLock(*lock);
362 if (mInvalidated) {
363 lock->Invalidate();
366 return lock;
369 void DirectoryLockImpl::Log() const {
370 AssertIsOnOwningThread();
372 if (!QM_LOG_TEST()) {
373 return;
376 QM_LOG(("DirectoryLockImpl [%p]", this));
378 nsCString persistenceType;
379 if (mPersistenceType.IsNull()) {
380 persistenceType.AssignLiteral("null");
381 } else {
382 persistenceType.Assign(PersistenceTypeToString(mPersistenceType.Value()));
384 QM_LOG((" mPersistenceType: %s", persistenceType.get()));
386 QM_LOG((" mGroup: %s", mGroup.get()));
388 nsCString originScope;
389 if (mOriginScope.IsOrigin()) {
390 originScope.AssignLiteral("origin:");
391 originScope.Append(mOriginScope.GetOrigin());
392 } else if (mOriginScope.IsPrefix()) {
393 originScope.AssignLiteral("prefix:");
394 originScope.Append(mOriginScope.GetOriginNoSuffix());
395 } else if (mOriginScope.IsPattern()) {
396 originScope.AssignLiteral("pattern:");
397 // Can't call GetJSONPattern since it only works on the main thread.
398 } else {
399 MOZ_ASSERT(mOriginScope.IsNull());
400 originScope.AssignLiteral("null");
402 QM_LOG((" mOriginScope: %s", originScope.get()));
404 const auto clientType = mClientType.IsNull()
405 ? nsAutoCString{"null"_ns}
406 : Client::TypeToText(mClientType.Value());
407 QM_LOG((" mClientType: %s", clientType.get()));
409 nsCString blockedOnString;
410 for (auto blockedOn : mBlockedOn) {
411 blockedOnString.Append(
412 nsPrintfCString(" [%p]", static_cast<void*>(blockedOn)));
414 QM_LOG((" mBlockedOn:%s", blockedOnString.get()));
416 QM_LOG((" mExclusive: %d", mExclusive));
418 QM_LOG((" mInternal: %d", mInternal));
420 QM_LOG((" mInvalidated: %d", static_cast<bool>(mInvalidated)));
422 for (auto blockedOn : mBlockedOn) {
423 blockedOn->Log();
427 } // namespace mozilla::dom::quota