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"
11 #include "nsThreadUtils.h"
12 #include "mozilla/ReverseIterator.h"
13 #include "mozilla/dom/quota/QuotaCommon.h"
14 #include "mozilla/dom/quota/QuotaManager.h"
16 namespace mozilla::dom::quota
{
21 * Automatically log information about a directory lock if acquiring of the
22 * directory lock takes this long. We've chosen a value that is long enough
23 * that it is unlikely for the problem to be falsely triggered by slow system
24 * I/O. We've also chosen a value long enough so that testers can notice the
25 * timeout; we want to know about the timeouts, not hide them. On the other
26 * hand this value is less than 45 seconds which is used by quota manager to
27 * crash a hung quota manager shutdown.
29 const uint32_t kAcquireTimeoutMs
= 30000;
33 DirectoryLockImpl::DirectoryLockImpl(
34 MovingNotNull
<RefPtr
<QuotaManager
>> aQuotaManager
,
35 const PersistenceScope
& aPersistenceScope
, const OriginScope
& aOriginScope
,
36 const Nullable
<Client::Type
>& aClientType
, const bool aExclusive
,
38 const ShouldUpdateLockIdTableFlag aShouldUpdateLockIdTableFlag
,
39 const DirectoryLockCategory aCategory
)
40 : mQuotaManager(std::move(aQuotaManager
)),
41 mPersistenceScope(aPersistenceScope
),
42 mOriginScope(aOriginScope
),
43 mClientType(aClientType
),
44 mId(mQuotaManager
->GenerateDirectoryLockId()),
45 mExclusive(aExclusive
),
47 mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag
==
48 ShouldUpdateLockIdTableFlag::Yes
),
51 AssertIsOnOwningThread();
52 MOZ_ASSERT_IF(aOriginScope
.IsOrigin(), !aOriginScope
.GetOrigin().IsEmpty());
53 MOZ_ASSERT_IF(!aInternal
, aPersistenceScope
.IsValue());
54 MOZ_ASSERT_IF(!aInternal
,
55 aPersistenceScope
.GetValue() != PERSISTENCE_TYPE_INVALID
);
56 MOZ_ASSERT_IF(!aInternal
, aOriginScope
.IsOrigin());
57 MOZ_ASSERT_IF(!aInternal
, !aClientType
.IsNull());
58 MOZ_ASSERT_IF(!aInternal
, aClientType
.Value() < Client::TypeMax());
61 DirectoryLockImpl::~DirectoryLockImpl() {
62 AssertIsOnOwningThread();
63 MOZ_DIAGNOSTIC_ASSERT(!mRegistered
);
66 bool DirectoryLockImpl::MustWait() const {
67 AssertIsOnOwningThread();
68 MOZ_ASSERT(!mRegistered
);
70 for (const DirectoryLockImpl
* const existingLock
:
71 mQuotaManager
->mDirectoryLocks
) {
72 if (MustWaitFor(*existingLock
)) {
80 nsTArray
<RefPtr
<DirectoryLockImpl
>> DirectoryLockImpl::LocksMustWaitFor()
82 AssertIsOnOwningThread();
84 return LocksMustWaitForInternal
<RefPtr
<DirectoryLockImpl
>>();
87 DirectoryLockImpl::PrepareInfo
DirectoryLockImpl::Prepare() const {
88 return PrepareInfo
{*this};
91 RefPtr
<BoolPromise
> DirectoryLockImpl::Acquire() {
92 auto prepareInfo
= Prepare();
94 return Acquire(std::move(prepareInfo
));
97 RefPtr
<BoolPromise
> DirectoryLockImpl::Acquire(PrepareInfo
&& aPrepareInfo
) {
98 AssertIsOnOwningThread();
100 RefPtr
<BoolPromise
> result
= mAcquirePromiseHolder
.Ensure(__func__
);
102 AcquireInternal(std::move(aPrepareInfo
));
107 void DirectoryLockImpl::AcquireImmediately() {
108 AssertIsOnOwningThread();
109 MOZ_ASSERT(!MustWait());
111 mQuotaManager
->RegisterDirectoryLock(*this);
117 void DirectoryLockImpl::AssertIsAcquiredExclusively() {
118 AssertIsOnOwningThread();
119 MOZ_ASSERT(mBlockedOn
.IsEmpty());
120 MOZ_ASSERT(mExclusive
);
121 MOZ_ASSERT(mInternal
);
122 MOZ_ASSERT(mRegistered
);
123 MOZ_ASSERT(!mInvalidated
);
124 MOZ_ASSERT(mAcquired
);
128 for (const DirectoryLockImpl
* const existingLock
:
129 mQuotaManager
->mDirectoryLocks
) {
130 if (existingLock
== this) {
133 } else if (existingLock
->mAcquired
) {
142 RefPtr
<BoolPromise
> DirectoryLockImpl::Drop() {
143 AssertIsOnOwningThread();
144 MOZ_ASSERT_IF(!mRegistered
, mBlocking
.IsEmpty());
148 return InvokeAsync(GetCurrentSerialEventTarget(), __func__
,
149 [self
= RefPtr(this)]() {
150 if (self
->mRegistered
) {
154 return BoolPromise::CreateAndResolve(true, __func__
);
158 void DirectoryLockImpl::OnInvalidate(std::function
<void()>&& aCallback
) {
159 mInvalidateCallback
= std::move(aCallback
);
162 void DirectoryLockImpl::Log() const {
163 AssertIsOnOwningThread();
165 if (!QM_LOG_TEST()) {
169 QM_LOG(("DirectoryLockImpl [%p]", this));
171 nsCString persistenceScope
;
172 if (mPersistenceScope
.IsNull()) {
173 persistenceScope
.AssignLiteral("null");
174 } else if (mPersistenceScope
.IsValue()) {
175 persistenceScope
.Assign(
176 PersistenceTypeToString(mPersistenceScope
.GetValue()));
178 MOZ_ASSERT(mPersistenceScope
.IsSet());
179 for (auto persistenceType
: mPersistenceScope
.GetSet()) {
180 persistenceScope
.Append(PersistenceTypeToString(persistenceType
) +
184 QM_LOG((" mPersistenceScope: %s", persistenceScope
.get()));
186 nsCString originScope
;
187 if (mOriginScope
.IsOrigin()) {
188 originScope
.AssignLiteral("origin:");
189 originScope
.Append(mOriginScope
.GetOrigin());
190 } else if (mOriginScope
.IsPrefix()) {
191 originScope
.AssignLiteral("prefix:");
192 originScope
.Append(mOriginScope
.GetOriginNoSuffix());
193 } else if (mOriginScope
.IsPattern()) {
194 originScope
.AssignLiteral("pattern:");
195 // Can't call GetJSONPattern since it only works on the main thread.
197 MOZ_ASSERT(mOriginScope
.IsNull());
198 originScope
.AssignLiteral("null");
200 QM_LOG((" mOriginScope: %s", originScope
.get()));
202 const auto clientType
= mClientType
.IsNull()
203 ? nsAutoCString
{"null"_ns
}
204 : Client::TypeToText(mClientType
.Value());
205 QM_LOG((" mClientType: %s", clientType
.get()));
207 nsCString blockedOnString
;
208 for (auto blockedOn
: mBlockedOn
) {
209 blockedOnString
.Append(
210 nsPrintfCString(" [%p]", static_cast<void*>(blockedOn
)));
212 QM_LOG((" mBlockedOn:%s", blockedOnString
.get()));
214 QM_LOG((" mExclusive: %d", mExclusive
));
216 QM_LOG((" mInternal: %d", mInternal
));
218 QM_LOG((" mInvalidated: %d", static_cast<bool>(mInvalidated
)));
220 for (auto blockedOn
: mBlockedOn
) {
227 void DirectoryLockImpl::AssertIsOnOwningThread() const {
228 mQuotaManager
->AssertIsOnOwningThread();
233 bool DirectoryLockImpl::Overlaps(const DirectoryLockImpl
& aLock
) const {
234 AssertIsOnOwningThread();
236 // If the persistence types don't overlap, the op can proceed.
237 bool match
= aLock
.mPersistenceScope
.Matches(mPersistenceScope
);
242 // If the origin scopes don't overlap, the op can proceed.
243 match
= aLock
.mOriginScope
.Matches(mOriginScope
);
248 // If the client types don't overlap, the op can proceed.
249 if (!aLock
.mClientType
.IsNull() && !mClientType
.IsNull() &&
250 aLock
.mClientType
.Value() != mClientType
.Value()) {
254 // Otherwise, when all attributes overlap (persistence type, origin scope and
255 // client type) the op must wait.
259 bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl
& aLock
) const {
260 AssertIsOnOwningThread();
262 // Waiting is never required if the ops in comparison represent shared locks.
263 if (!aLock
.mExclusive
&& !mExclusive
) {
267 // Wait if the ops overlap.
268 return Overlaps(aLock
);
271 void DirectoryLockImpl::NotifyOpenListener() {
272 AssertIsOnOwningThread();
275 mAcquireTimer
->Cancel();
276 mAcquireTimer
= nullptr;
280 mAcquirePromiseHolder
.Reject(NS_ERROR_FAILURE
, __func__
);
284 mAcquirePromiseHolder
.Resolve(true, __func__
);
287 MOZ_ASSERT(mAcquirePromiseHolder
.IsEmpty());
289 mQuotaManager
->RemovePendingDirectoryLock(*this);
300 template <typename T
>
301 nsTArray
<T
> DirectoryLockImpl::LocksMustWaitForInternal() const {
302 AssertIsOnOwningThread();
303 MOZ_ASSERT(!mRegistered
);
307 // XXX It is probably unnecessary to iterate this in reverse order.
308 for (DirectoryLockImpl
* const existingLock
:
309 Reversed(mQuotaManager
->mDirectoryLocks
)) {
310 if (MustWaitFor(*existingLock
)) {
311 if constexpr (std::is_same_v
<T
, NotNull
<DirectoryLockImpl
*>>) {
312 locks
.AppendElement(WrapNotNull(existingLock
));
314 locks
.AppendElement(existingLock
);
322 void DirectoryLockImpl::AcquireInternal(PrepareInfo
&& aPrepareInfo
) {
323 AssertIsOnOwningThread();
325 mQuotaManager
->AddPendingDirectoryLock(*this);
327 // See if this lock needs to wait. This has to be done before the lock is
328 // registered, we would be comparing the lock against itself otherwise.
329 mBlockedOn
= std::move(aPrepareInfo
.mBlockedOn
);
331 // After the traversal of existing locks is done, this lock can be
332 // registered and will become an existing lock as well.
333 mQuotaManager
->RegisterDirectoryLock(*this);
335 // If this lock is not blocked by some other existing lock, notify the open
336 // listener immediately and return.
337 if (mBlockedOn
.IsEmpty()) {
338 NotifyOpenListener();
342 // Add this lock as a blocking lock to all locks which block it, so the
343 // locks can update this lock when they are unregistered and eventually
344 // unblock this lock.
345 for (auto& blockedOnLock
: mBlockedOn
) {
346 blockedOnLock
->AddBlockingLock(*this);
349 mAcquireTimer
= NS_NewTimer();
351 MOZ_ALWAYS_SUCCEEDS(mAcquireTimer
->InitWithNamedFuncCallback(
352 [](nsITimer
* aTimer
, void* aClosure
) {
353 if (!QM_LOG_TEST()) {
357 auto* const lock
= static_cast<DirectoryLockImpl
*>(aClosure
);
359 QM_LOG(("Directory lock [%p] is taking too long to be acquired", lock
));
363 this, kAcquireTimeoutMs
, nsITimer::TYPE_ONE_SHOT
,
364 "quota::DirectoryLockImpl::AcquireInternal"));
366 if (!mExclusive
|| !mInternal
) {
370 // All the locks that block this new exclusive internal lock need to be
371 // invalidated. We also need to notify clients to abort operations for them.
372 QuotaManager::DirectoryLockIdTableArray lockIds
;
373 lockIds
.SetLength(Client::TypeMax());
375 const auto& blockedOnLocks
= GetBlockedOnLocks();
376 MOZ_ASSERT(!blockedOnLocks
.IsEmpty());
378 for (DirectoryLockImpl
* blockedOnLock
: blockedOnLocks
) {
379 if (!blockedOnLock
->IsInternal()) {
380 blockedOnLock
->Invalidate();
382 // Clients don't have to handle pending locks. Invalidation is sufficient
383 // in that case (once a lock is ready and the listener needs to be
384 // notified, we will call DirectoryLockFailed instead of
385 // DirectoryLockAcquired which should release any remaining references to
387 if (!blockedOnLock
->IsPending()) {
388 lockIds
[blockedOnLock
->ClientType()].Put(blockedOnLock
->Id());
393 mQuotaManager
->AbortOperationsForLocks(lockIds
);
396 void DirectoryLockImpl::Invalidate() {
397 AssertIsOnOwningThread();
399 mInvalidated
.EnsureFlipped();
401 if (mInvalidateCallback
) {
402 MOZ_ALWAYS_SUCCEEDS(GetCurrentSerialEventTarget()->Dispatch(
403 NS_NewRunnableFunction("DirectoryLockImpl::Invalidate",
404 [invalidateCallback
= mInvalidateCallback
]() {
405 invalidateCallback();
407 NS_DISPATCH_NORMAL
));
411 void DirectoryLockImpl::Unregister() {
412 AssertIsOnOwningThread();
413 MOZ_ASSERT(mRegistered
);
415 // We must call UnregisterDirectoryLock before unblocking other locks because
416 // UnregisterDirectoryLock also updates the origin last access time and the
417 // access flag (if the last lock for given origin is unregistered). One of the
418 // blocked locks could be requested by the clear/reset operation which stores
419 // cached information about origins in storage.sqlite. So if the access flag
420 // is not updated before unblocking the lock for reset/clear, we might store
421 // invalid information which can lead to omitting origin initialization during
422 // next temporary storage initialization.
423 mQuotaManager
->UnregisterDirectoryLock(*this);
425 MOZ_ASSERT(!mRegistered
);
427 for (NotNull
<RefPtr
<DirectoryLockImpl
>> blockingLock
: mBlocking
) {
428 blockingLock
->MaybeUnblock(*this);
434 } // namespace mozilla::dom::quota