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
),
28 mOriginScope(aOriginScope
),
29 mStorageOrigin(aStorageOrigin
),
30 mClientType(aClientType
),
31 mId(mQuotaManager
->GenerateDirectoryLockId()),
32 mIsPrivate(aIsPrivate
),
33 mExclusive(aExclusive
),
35 mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag
==
36 ShouldUpdateLockIdTableFlag::Yes
),
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();
65 void DirectoryLockImpl::AssertIsOnOwningThread() const {
66 mQuotaManager
->AssertIsOnOwningThread();
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()) {
80 // If the origin scopes don't overlap, the op can proceed.
81 bool match
= aLock
.mOriginScope
.Matches(mOriginScope
);
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()) {
92 // Otherwise, when all attributes overlap (persistence type, origin scope and
93 // client type) the op must wait.
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
) {
105 // Wait if the ops overlap.
106 return Overlaps(aLock
);
109 void DirectoryLockImpl::NotifyOpenListener() {
110 AssertIsOnOwningThread();
113 mAcquirePromiseHolder
.Reject(NS_ERROR_FAILURE
, __func__
);
117 mAcquirePromiseHolder
.Resolve(true, __func__
);
120 MOZ_ASSERT(mAcquirePromiseHolder
.IsEmpty());
122 mQuotaManager
->RemovePendingDirectoryLock(*this);
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);
171 bool DirectoryLockImpl::MustWait() const {
172 AssertIsOnOwningThread();
173 MOZ_ASSERT(!mRegistered
);
175 for (const DirectoryLockImpl
* const existingLock
:
176 mQuotaManager
->mDirectoryLocks
) {
177 if (MustWaitFor(*existingLock
)) {
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
));
200 RefPtr
<BoolPromise
> DirectoryLockImpl::Acquire() {
201 AssertIsOnOwningThread();
203 RefPtr
<BoolPromise
> result
= mAcquirePromiseHolder
.Ensure(__func__
);
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
);
228 mQuotaManager
->RegisterDirectoryLock(*this);
230 // Otherwise, notify the open listener immediately.
232 NotifyOpenListener();
236 if (!mExclusive
|| !mInternal
) {
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
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);
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
);
287 for (const DirectoryLockImpl
* const existingLock
:
288 mQuotaManager
->mDirectoryLocks
) {
289 if (existingLock
== this) {
292 } else if (existingLock
->mAcquired
) {
301 void DirectoryLockImpl::Drop() {
302 AssertIsOnOwningThread();
303 MOZ_ASSERT_IF(!mRegistered
, mBlocking
.IsEmpty());
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
)) {
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
))) {
345 for (DirectoryLockImpl
* const existingLock
:
346 Reversed(mQuotaManager
->mDirectoryLocks
)) {
347 if (existingLock
!= this && !existingLock
->MustWaitFor(*this)) {
348 MOZ_ASSERT(!existingLock
->MustWaitFor(*lock
));
353 for (const auto& blockedLock
: mBlocking
) {
354 if (blockedLock
->MustWaitFor(*lock
)) {
355 lock
->AddBlockingLock(*blockedLock
);
356 blockedLock
->AddBlockedOnLock(*lock
);
360 mQuotaManager
->RegisterDirectoryLock(*lock
);
369 void DirectoryLockImpl::Log() const {
370 AssertIsOnOwningThread();
372 if (!QM_LOG_TEST()) {
376 QM_LOG(("DirectoryLockImpl [%p]", this));
378 nsCString persistenceType
;
379 if (mPersistenceType
.IsNull()) {
380 persistenceType
.AssignLiteral("null");
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.
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
) {
427 } // namespace mozilla::dom::quota