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 Nullable
<Client::Type
>& aClientType
,
20 const bool aExclusive
, const bool aInternal
,
21 const ShouldUpdateLockIdTableFlag aShouldUpdateLockIdTableFlag
)
22 : mQuotaManager(std::move(aQuotaManager
)),
23 mPersistenceType(aPersistenceType
),
26 mOriginScope(aOriginScope
),
27 mClientType(aClientType
),
28 mId(mQuotaManager
->GenerateDirectoryLockId()),
29 mExclusive(aExclusive
),
31 mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag
==
32 ShouldUpdateLockIdTableFlag::Yes
),
34 AssertIsOnOwningThread();
35 MOZ_ASSERT_IF(aOriginScope
.IsOrigin(), !aOriginScope
.GetOrigin().IsEmpty());
36 MOZ_ASSERT_IF(!aInternal
, !aPersistenceType
.IsNull());
37 MOZ_ASSERT_IF(!aInternal
,
38 aPersistenceType
.Value() != PERSISTENCE_TYPE_INVALID
);
39 MOZ_ASSERT_IF(!aInternal
, !aGroup
.IsEmpty());
40 MOZ_ASSERT_IF(!aInternal
, aOriginScope
.IsOrigin());
41 MOZ_ASSERT_IF(!aInternal
, !aClientType
.IsNull());
42 MOZ_ASSERT_IF(!aInternal
, aClientType
.Value() < Client::TypeMax());
45 DirectoryLockImpl::~DirectoryLockImpl() {
46 AssertIsOnOwningThread();
48 // We must call UnregisterDirectoryLock before unblocking other locks because
49 // UnregisterDirectoryLock also updates the origin last access time and the
50 // access flag (if the last lock for given origin is unregistered). One of the
51 // blocked locks could be requested by the clear/reset operation which stores
52 // cached information about origins in storage.sqlite. So if the access flag
53 // is not updated before unblocking the lock for reset/clear, we might store
54 // invalid information which can lead to omitting origin initialization during
55 // next temporary storage initialization.
57 mQuotaManager
->UnregisterDirectoryLock(*this);
60 MOZ_ASSERT(!mRegistered
);
62 for (NotNull
<RefPtr
<DirectoryLockImpl
>> blockingLock
: mBlocking
) {
63 blockingLock
->MaybeUnblock(*this);
71 void DirectoryLockImpl::AssertIsOnOwningThread() const {
72 mQuotaManager
->AssertIsOnOwningThread();
77 bool DirectoryLockImpl::Overlaps(const DirectoryLockImpl
& aLock
) const {
78 AssertIsOnOwningThread();
80 // If the persistence types don't overlap, the op can proceed.
81 if (!aLock
.mPersistenceType
.IsNull() && !mPersistenceType
.IsNull() &&
82 aLock
.mPersistenceType
.Value() != mPersistenceType
.Value()) {
86 // If the origin scopes don't overlap, the op can proceed.
87 bool match
= aLock
.mOriginScope
.Matches(mOriginScope
);
92 // If the client types don't overlap, the op can proceed.
93 if (!aLock
.mClientType
.IsNull() && !mClientType
.IsNull() &&
94 aLock
.mClientType
.Value() != mClientType
.Value()) {
98 // Otherwise, when all attributes overlap (persistence type, origin scope and
99 // client type) the op must wait.
103 bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl
& aLock
) const {
104 AssertIsOnOwningThread();
106 // Waiting is never required if the ops in comparison represent shared locks.
107 if (!aLock
.mExclusive
&& !mExclusive
) {
111 // Wait if the ops overlap.
112 return Overlaps(aLock
);
115 void DirectoryLockImpl::NotifyOpenListener() {
116 AssertIsOnOwningThread();
119 (*mOpenListener
)->DirectoryLockFailed();
122 ->DirectoryLockAcquired(static_cast<UniversalDirectoryLock
*>(this));
125 mOpenListener
.destroy();
127 mQuotaManager
->RemovePendingDirectoryLock(*this);
132 void DirectoryLockImpl::Acquire(RefPtr
<OpenDirectoryListener
> aOpenListener
) {
133 AssertIsOnOwningThread();
134 MOZ_ASSERT(aOpenListener
);
136 mOpenListener
.init(WrapNotNullUnchecked(std::move(aOpenListener
)));
138 mQuotaManager
->AddPendingDirectoryLock(*this);
140 // See if this lock needs to wait.
141 bool blocked
= false;
143 // XXX It is probably unnecessary to iterate this in reverse order.
144 for (DirectoryLockImpl
* const existingLock
:
145 Reversed(mQuotaManager
->mDirectoryLocks
)) {
146 if (MustWaitFor(*existingLock
)) {
147 existingLock
->AddBlockingLock(*this);
148 AddBlockedOnLock(*existingLock
);
153 mQuotaManager
->RegisterDirectoryLock(*this);
155 // Otherwise, notify the open listener immediately.
157 NotifyOpenListener();
161 if (!mExclusive
|| !mInternal
) {
165 // All the locks that block this new exclusive internal lock need to be
166 // invalidated. We also need to notify clients to abort operations for them.
167 QuotaManager::DirectoryLockIdTableArray lockIds
;
168 lockIds
.SetLength(Client::TypeMax());
170 const auto& blockedOnLocks
= GetBlockedOnLocks();
171 MOZ_ASSERT(!blockedOnLocks
.IsEmpty());
173 for (DirectoryLockImpl
* blockedOnLock
: blockedOnLocks
) {
174 if (!blockedOnLock
->IsInternal()) {
175 blockedOnLock
->Invalidate();
177 // Clients don't have to handle pending locks. Invalidation is sufficient
178 // in that case (once a lock is ready and the listener needs to be
179 // notified, we will call DirectoryLockFailed instead of
180 // DirectoryLockAcquired which should release any remaining references to
182 if (!blockedOnLock
->IsPending()) {
183 lockIds
[blockedOnLock
->ClientType()].Put(blockedOnLock
->Id());
188 mQuotaManager
->AbortOperationsForLocks(lockIds
);
191 void DirectoryLockImpl::AcquireImmediately() {
192 AssertIsOnOwningThread();
195 for (const DirectoryLockImpl
* const existingLock
:
196 mQuotaManager
->mDirectoryLocks
) {
197 MOZ_ASSERT(!MustWaitFor(*existingLock
));
201 mQuotaManager
->RegisterDirectoryLock(*this);
204 RefPtr
<ClientDirectoryLock
> DirectoryLockImpl::Specialize(
205 PersistenceType aPersistenceType
,
206 const quota::OriginMetadata
& aOriginMetadata
,
207 Client::Type aClientType
) const {
208 AssertIsOnOwningThread();
209 MOZ_ASSERT(aPersistenceType
!= PERSISTENCE_TYPE_INVALID
);
210 MOZ_ASSERT(!aOriginMetadata
.mGroup
.IsEmpty());
211 MOZ_ASSERT(!aOriginMetadata
.mOrigin
.IsEmpty());
212 MOZ_ASSERT(aClientType
< Client::TypeMax());
213 MOZ_ASSERT(!mOpenListener
);
214 MOZ_ASSERT(mBlockedOn
.IsEmpty());
216 if (NS_WARN_IF(mExclusive
)) {
220 RefPtr
<DirectoryLockImpl
> lock
= Create(
221 mQuotaManager
, Nullable
<PersistenceType
>(aPersistenceType
),
222 aOriginMetadata
.mSuffix
, aOriginMetadata
.mGroup
,
223 OriginScope::FromOrigin(aOriginMetadata
.mOrigin
),
224 Nullable
<Client::Type
>(aClientType
),
225 /* aExclusive */ false, mInternal
, ShouldUpdateLockIdTableFlag::Yes
);
226 if (NS_WARN_IF(!Overlaps(*lock
))) {
231 for (DirectoryLockImpl
* const existingLock
:
232 Reversed(mQuotaManager
->mDirectoryLocks
)) {
233 if (existingLock
!= this && !existingLock
->MustWaitFor(*this)) {
234 MOZ_ASSERT(!existingLock
->MustWaitFor(*lock
));
239 for (const auto& blockedLock
: mBlocking
) {
240 if (blockedLock
->MustWaitFor(*lock
)) {
241 lock
->AddBlockingLock(*blockedLock
);
242 blockedLock
->AddBlockedOnLock(*lock
);
246 mQuotaManager
->RegisterDirectoryLock(*lock
);
255 void DirectoryLockImpl::Log() const {
256 AssertIsOnOwningThread();
258 if (!QM_LOG_TEST()) {
262 QM_LOG(("DirectoryLockImpl [%p]", this));
264 nsCString persistenceType
;
265 if (mPersistenceType
.IsNull()) {
266 persistenceType
.AssignLiteral("null");
268 persistenceType
.Assign(PersistenceTypeToString(mPersistenceType
.Value()));
270 QM_LOG((" mPersistenceType: %s", persistenceType
.get()));
272 QM_LOG((" mGroup: %s", mGroup
.get()));
274 nsCString originScope
;
275 if (mOriginScope
.IsOrigin()) {
276 originScope
.AssignLiteral("origin:");
277 originScope
.Append(mOriginScope
.GetOrigin());
278 } else if (mOriginScope
.IsPrefix()) {
279 originScope
.AssignLiteral("prefix:");
280 originScope
.Append(mOriginScope
.GetOriginNoSuffix());
281 } else if (mOriginScope
.IsPattern()) {
282 originScope
.AssignLiteral("pattern:");
283 // Can't call GetJSONPattern since it only works on the main thread.
285 MOZ_ASSERT(mOriginScope
.IsNull());
286 originScope
.AssignLiteral("null");
288 QM_LOG((" mOriginScope: %s", originScope
.get()));
290 const auto clientType
= mClientType
.IsNull()
291 ? nsAutoCString
{"null"_ns
}
292 : Client::TypeToText(mClientType
.Value());
293 QM_LOG((" mClientType: %s", clientType
.get()));
295 nsCString blockedOnString
;
296 for (auto blockedOn
: mBlockedOn
) {
297 blockedOnString
.Append(
298 nsPrintfCString(" [%p]", static_cast<void*>(blockedOn
)));
300 QM_LOG((" mBlockedOn:%s", blockedOnString
.get()));
302 QM_LOG((" mExclusive: %d", mExclusive
));
304 QM_LOG((" mInternal: %d", mInternal
));
306 QM_LOG((" mInvalidated: %d", static_cast<bool>(mInvalidated
)));
308 for (auto blockedOn
: mBlockedOn
) {
313 } // namespace mozilla::dom::quota