Bug 1941046 - Part 4: Send a callback request for impression and clicks of MARS Top...
[gecko.git] / dom / quota / DirectoryLockImpl.cpp
blob32935c0bfce09ba205e52de8f99e44269ac7026c
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 "nsError.h"
10 #include "nsString.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 {
18 namespace {
20 /**
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;
31 } // namespace
33 DirectoryLockImpl::DirectoryLockImpl(
34 MovingNotNull<RefPtr<QuotaManager>> aQuotaManager,
35 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope,
36 const Nullable<Client::Type>& aClientType, const bool aExclusive,
37 const bool aInternal,
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),
46 mInternal(aInternal),
47 mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag ==
48 ShouldUpdateLockIdTableFlag::Yes),
49 mCategory(aCategory),
50 mRegistered(false) {
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)) {
73 return true;
77 return false;
80 nsTArray<RefPtr<DirectoryLockImpl>> DirectoryLockImpl::LocksMustWaitFor()
81 const {
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));
104 return result;
107 void DirectoryLockImpl::AcquireImmediately() {
108 AssertIsOnOwningThread();
109 MOZ_ASSERT(!MustWait());
111 mQuotaManager->RegisterDirectoryLock(*this);
113 mAcquired.Flip();
116 #ifdef DEBUG
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);
126 bool found = false;
128 for (const DirectoryLockImpl* const existingLock :
129 mQuotaManager->mDirectoryLocks) {
130 if (existingLock == this) {
131 MOZ_ASSERT(!found);
132 found = true;
133 } else if (existingLock->mAcquired) {
134 MOZ_ASSERT(false);
138 MOZ_ASSERT(found);
140 #endif
142 RefPtr<BoolPromise> DirectoryLockImpl::Drop() {
143 AssertIsOnOwningThread();
144 MOZ_ASSERT_IF(!mRegistered, mBlocking.IsEmpty());
146 mDropped.Flip();
148 return InvokeAsync(GetCurrentSerialEventTarget(), __func__,
149 [self = RefPtr(this)]() {
150 if (self->mRegistered) {
151 self->Unregister();
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()) {
166 return;
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()));
177 } else {
178 MOZ_ASSERT(mPersistenceScope.IsSet());
179 for (auto persistenceType : mPersistenceScope.GetSet()) {
180 persistenceScope.Append(PersistenceTypeToString(persistenceType) +
181 " "_ns);
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.
196 } else {
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) {
221 blockedOn->Log();
225 #ifdef DEBUG
227 void DirectoryLockImpl::AssertIsOnOwningThread() const {
228 mQuotaManager->AssertIsOnOwningThread();
231 #endif // DEBUG
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);
238 if (!match) {
239 return false;
242 // If the origin scopes don't overlap, the op can proceed.
243 match = aLock.mOriginScope.Matches(mOriginScope);
244 if (!match) {
245 return false;
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()) {
251 return false;
254 // Otherwise, when all attributes overlap (persistence type, origin scope and
255 // client type) the op must wait.
256 return true;
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) {
264 return false;
267 // Wait if the ops overlap.
268 return Overlaps(aLock);
271 void DirectoryLockImpl::NotifyOpenListener() {
272 AssertIsOnOwningThread();
274 if (mAcquireTimer) {
275 mAcquireTimer->Cancel();
276 mAcquireTimer = nullptr;
279 if (mInvalidated) {
280 mAcquirePromiseHolder.Reject(NS_ERROR_FAILURE, __func__);
281 } else {
282 mAcquired.Flip();
284 mAcquirePromiseHolder.Resolve(true, __func__);
287 MOZ_ASSERT(mAcquirePromiseHolder.IsEmpty());
289 mQuotaManager->RemovePendingDirectoryLock(*this);
291 mPending.Flip();
293 if (mInvalidated) {
294 mDropped.Flip();
296 Unregister();
300 template <typename T>
301 nsTArray<T> DirectoryLockImpl::LocksMustWaitForInternal() const {
302 AssertIsOnOwningThread();
303 MOZ_ASSERT(!mRegistered);
305 nsTArray<T> locks;
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));
313 } else {
314 locks.AppendElement(existingLock);
319 return locks;
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();
339 return;
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()) {
354 return;
357 auto* const lock = static_cast<DirectoryLockImpl*>(aClosure);
359 QM_LOG(("Directory lock [%p] is taking too long to be acquired", lock));
361 lock->Log();
363 this, kAcquireTimeoutMs, nsITimer::TYPE_ONE_SHOT,
364 "quota::DirectoryLockImpl::AcquireInternal"));
366 if (!mExclusive || !mInternal) {
367 return;
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
386 // the lock).
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);
431 mBlocking.Clear();
434 } // namespace mozilla::dom::quota