DirectoryLockImpl.cpp (14509B)
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/. */ 6 7 #include "DirectoryLockImpl.h" 8 9 #include "mozilla/ReverseIterator.h" 10 #include "mozilla/dom/quota/QuotaCommon.h" 11 #include "mozilla/dom/quota/QuotaManager.h" 12 #include "nsError.h" 13 #include "nsString.h" 14 #include "nsThreadUtils.h" 15 16 namespace mozilla::dom::quota { 17 18 namespace { 19 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. 28 */ 29 const uint32_t kAcquireTimeoutMs = 30000; 30 31 } // namespace 32 33 DirectoryLockImpl::DirectoryLockImpl( 34 MovingNotNull<RefPtr<QuotaManager>> aQuotaManager, 35 const PersistenceScope& aPersistenceScope, const OriginScope& aOriginScope, 36 const ClientStorageScope& aClientStorageScope, 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 mClientStorageScope(aClientStorageScope), 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, aClientStorageScope.IsClient()); 58 MOZ_ASSERT_IF(!aInternal, 59 aClientStorageScope.GetClientType() < Client::TypeMax()); 60 } 61 62 DirectoryLockImpl::~DirectoryLockImpl() { 63 AssertIsOnOwningThread(); 64 MOZ_DIAGNOSTIC_ASSERT(!mRegistered); 65 } 66 67 bool DirectoryLockImpl::MustWait() const { 68 AssertIsOnOwningThread(); 69 MOZ_ASSERT(!mRegistered); 70 71 // Shared locks never block other shared locks, so when acquiring a shared 72 // lock, we only need to consider existing exclusive locks. This reduces the 73 // cost of traversal when many locks are active. Exclusive locks must still 74 // consider all existing locks (both shared and exclusive). See also 75 // DirectoryLockImpl::MustWaitFor. 76 const auto& existingLocks = mExclusive 77 ? mQuotaManager->mDirectoryLocks 78 : mQuotaManager->mExclusiveDirectoryLocks; 79 80 for (const DirectoryLockImpl* const existingLock : existingLocks) { 81 if (MustWaitFor(*existingLock)) { 82 return true; 83 } 84 } 85 86 return false; 87 } 88 89 nsTArray<RefPtr<DirectoryLockImpl>> DirectoryLockImpl::LocksMustWaitFor() 90 const { 91 AssertIsOnOwningThread(); 92 93 return LocksMustWaitForInternal<RefPtr<DirectoryLockImpl>>(); 94 } 95 96 DirectoryLockImpl::PrepareInfo DirectoryLockImpl::Prepare() const { 97 return PrepareInfo{*this}; 98 } 99 100 RefPtr<BoolPromise> DirectoryLockImpl::Acquire() { 101 auto prepareInfo = Prepare(); 102 103 return Acquire(std::move(prepareInfo)); 104 } 105 106 RefPtr<BoolPromise> DirectoryLockImpl::Acquire(PrepareInfo&& aPrepareInfo) { 107 AssertIsOnOwningThread(); 108 109 RefPtr<BoolPromise> result = mAcquirePromiseHolder.Ensure(__func__); 110 111 AcquireInternal(std::move(aPrepareInfo)); 112 113 return result; 114 } 115 116 void DirectoryLockImpl::AcquireImmediately() { 117 AssertIsOnOwningThread(); 118 MOZ_ASSERT(!MustWait()); 119 120 mQuotaManager->RegisterDirectoryLock(*this); 121 122 mAcquired.Flip(); 123 } 124 125 #ifdef DEBUG 126 void DirectoryLockImpl::AssertIsAcquiredExclusively() { 127 AssertIsOnOwningThread(); 128 MOZ_ASSERT(mBlockedOn.IsEmpty()); 129 MOZ_ASSERT(mExclusive); 130 MOZ_ASSERT(mInternal); 131 MOZ_ASSERT(mRegistered); 132 MOZ_ASSERT(!mInvalidated); 133 MOZ_ASSERT(mAcquired); 134 135 bool found = false; 136 137 for (const DirectoryLockImpl* const existingLock : 138 mQuotaManager->mDirectoryLocks) { 139 if (existingLock == this) { 140 MOZ_ASSERT(!found); 141 found = true; 142 } else if (existingLock->mAcquired) { 143 MOZ_ASSERT(false); 144 } 145 } 146 147 MOZ_ASSERT(found); 148 } 149 #endif 150 151 RefPtr<BoolPromise> DirectoryLockImpl::Drop() { 152 AssertIsOnOwningThread(); 153 MOZ_ASSERT_IF(!mRegistered, mBlocking.IsEmpty()); 154 155 mDropped.Flip(); 156 157 return InvokeAsync(GetCurrentSerialEventTarget(), __func__, 158 [self = RefPtr(this)]() { 159 if (self->mRegistered) { 160 self->Unregister(); 161 } 162 163 return BoolPromise::CreateAndResolve(true, __func__); 164 }); 165 } 166 167 void DirectoryLockImpl::OnInvalidate(std::function<void()>&& aCallback) { 168 mInvalidateCallback = std::move(aCallback); 169 } 170 171 void DirectoryLockImpl::Log() const { 172 AssertIsOnOwningThread(); 173 174 if (!QM_LOG_TEST()) { 175 return; 176 } 177 178 QM_LOG(("DirectoryLockImpl [%p]", this)); 179 180 nsCString persistenceScope; 181 if (mPersistenceScope.IsNull()) { 182 persistenceScope.AssignLiteral("null"); 183 } else if (mPersistenceScope.IsValue()) { 184 persistenceScope.Assign( 185 PersistenceTypeToString(mPersistenceScope.GetValue())); 186 } else { 187 MOZ_ASSERT(mPersistenceScope.IsSet()); 188 for (auto persistenceType : mPersistenceScope.GetSet()) { 189 persistenceScope.Append(PersistenceTypeToString(persistenceType) + 190 " "_ns); 191 } 192 } 193 QM_LOG((" mPersistenceScope: %s", persistenceScope.get())); 194 195 nsCString originScope; 196 if (mOriginScope.IsOrigin()) { 197 originScope.AssignLiteral("origin:"); 198 originScope.Append(mOriginScope.GetOrigin()); 199 } else if (mOriginScope.IsPrefix()) { 200 originScope.AssignLiteral("prefix:"); 201 originScope.Append(mOriginScope.GetOriginNoSuffix()); 202 } else if (mOriginScope.IsPattern()) { 203 originScope.AssignLiteral("pattern:"); 204 // Can't call GetJSONPattern since it only works on the main thread. 205 } else { 206 MOZ_ASSERT(mOriginScope.IsNull()); 207 originScope.AssignLiteral("null"); 208 } 209 QM_LOG((" mOriginScope: %s", originScope.get())); 210 211 nsCString clientStorageScope; 212 if (mClientStorageScope.IsNull()) { 213 clientStorageScope.AssignLiteral("null"); 214 } else if (mClientStorageScope.IsClient()) { 215 clientStorageScope.Assign( 216 Client::TypeToText(mClientStorageScope.GetClientType())); 217 } else { 218 MOZ_ASSERT(mClientStorageScope.IsMetadata()); 219 clientStorageScope.AssignLiteral("metadata"); 220 } 221 QM_LOG((" mClientStorageScope: %s", clientStorageScope.get())); 222 223 nsCString blockedOnString; 224 for (auto blockedOn : mBlockedOn) { 225 blockedOnString.Append( 226 nsPrintfCString(" [%p]", static_cast<void*>(blockedOn))); 227 } 228 QM_LOG((" mBlockedOn:%s", blockedOnString.get())); 229 230 QM_LOG((" mExclusive: %d", mExclusive)); 231 232 QM_LOG((" mInternal: %d", mInternal)); 233 234 QM_LOG((" mInvalidated: %d", static_cast<bool>(mInvalidated))); 235 236 for (auto blockedOn : mBlockedOn) { 237 blockedOn->Log(); 238 } 239 } 240 241 #ifdef DEBUG 242 243 void DirectoryLockImpl::AssertIsOnOwningThread() const { 244 mQuotaManager->AssertIsOnOwningThread(); 245 } 246 247 #endif // DEBUG 248 249 bool DirectoryLockImpl::Overlaps(const DirectoryLockImpl& aLock) const { 250 AssertIsOnOwningThread(); 251 252 // If the persistence types don't overlap, the op can proceed. 253 bool match = aLock.mPersistenceScope.Matches(mPersistenceScope); 254 if (!match) { 255 return false; 256 } 257 258 // If the origin scopes don't overlap, the op can proceed. 259 match = aLock.mOriginScope.Matches(mOriginScope); 260 if (!match) { 261 return false; 262 } 263 264 // If the client storage scopes don't overlap, the op can proceed. 265 match = aLock.mClientStorageScope.Matches(mClientStorageScope); 266 if (!match) { 267 return false; 268 } 269 270 // Otherwise, when all attributes overlap (persistence type, origin scope and 271 // client type) the op must wait. 272 return true; 273 } 274 275 bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aLock) const { 276 AssertIsOnOwningThread(); 277 278 // Waiting is never required if the ops in comparison represent shared locks. 279 // Note that this condition is also used to optimize traversal in MustWait 280 // and LocksMustWaitForInternal. If this logic changes, that optimization 281 // must be revisited to ensure correctness. 282 if (!aLock.mExclusive && !mExclusive) { 283 return false; 284 } 285 286 // Wait if the ops overlap. 287 return Overlaps(aLock); 288 } 289 290 void DirectoryLockImpl::NotifyOpenListener() { 291 AssertIsOnOwningThread(); 292 293 if (mAcquireTimer) { 294 mAcquireTimer->Cancel(); 295 mAcquireTimer = nullptr; 296 } 297 298 if (mInvalidated) { 299 mAcquirePromiseHolder.Reject(NS_ERROR_FAILURE, __func__); 300 } else { 301 mAcquired.Flip(); 302 303 mAcquirePromiseHolder.Resolve(true, __func__); 304 } 305 306 MOZ_ASSERT(mAcquirePromiseHolder.IsEmpty()); 307 308 mQuotaManager->RemovePendingDirectoryLock(*this); 309 310 mPending.Flip(); 311 312 if (mInvalidated) { 313 mDropped.Flip(); 314 315 Unregister(); 316 } 317 } 318 319 template <typename T> 320 nsTArray<T> DirectoryLockImpl::LocksMustWaitForInternal() const { 321 AssertIsOnOwningThread(); 322 MOZ_ASSERT(!mRegistered); 323 324 nsTArray<T> locks; 325 326 // Shared locks never block other shared locks, so when acquiring a shared 327 // lock, we only need to consider existing exclusive locks. This reduces the 328 // cost of traversal when many locks are active. Exclusive locks must still 329 // consider all existing locks (both shared and exclusive). See also 330 // DirectoryLockImpl::MustWaitFor. 331 const auto& existingLocks = mExclusive 332 ? mQuotaManager->mDirectoryLocks 333 : mQuotaManager->mExclusiveDirectoryLocks; 334 335 // XXX It is probably unnecessary to iterate this in reverse order. 336 for (DirectoryLockImpl* const existingLock : Reversed(existingLocks)) { 337 if (MustWaitFor(*existingLock)) { 338 if constexpr (std::is_same_v<T, NotNull<DirectoryLockImpl*>>) { 339 locks.AppendElement(WrapNotNull(existingLock)); 340 } else { 341 locks.AppendElement(existingLock); 342 } 343 } 344 } 345 346 return locks; 347 } 348 349 void DirectoryLockImpl::AcquireInternal(PrepareInfo&& aPrepareInfo) { 350 AssertIsOnOwningThread(); 351 352 mQuotaManager->AddPendingDirectoryLock(*this); 353 354 // See if this lock needs to wait. This has to be done before the lock is 355 // registered, we would be comparing the lock against itself otherwise. 356 mBlockedOn = std::move(aPrepareInfo.mBlockedOn); 357 358 // After the traversal of existing locks is done, this lock can be 359 // registered and will become an existing lock as well. 360 mQuotaManager->RegisterDirectoryLock(*this); 361 362 // If this lock is not blocked by some other existing lock, notify the open 363 // listener immediately and return. 364 if (mBlockedOn.IsEmpty()) { 365 NotifyOpenListener(); 366 return; 367 } 368 369 // Add this lock as a blocking lock to all locks which block it, so the 370 // locks can update this lock when they are unregistered and eventually 371 // unblock this lock. 372 for (auto& blockedOnLock : mBlockedOn) { 373 blockedOnLock->AddBlockingLock(*this); 374 } 375 376 mAcquireTimer = NS_NewTimer(); 377 378 MOZ_ALWAYS_SUCCEEDS(mAcquireTimer->InitWithNamedFuncCallback( 379 [](nsITimer* aTimer, void* aClosure) { 380 if (!QM_LOG_TEST()) { 381 return; 382 } 383 384 auto* const lock = static_cast<DirectoryLockImpl*>(aClosure); 385 386 QM_LOG(("Directory lock [%p] is taking too long to be acquired", lock)); 387 388 lock->Log(); 389 }, 390 this, kAcquireTimeoutMs, nsITimer::TYPE_ONE_SHOT, 391 "quota::DirectoryLockImpl::AcquireInternal"_ns)); 392 393 if (!mExclusive || !mInternal) { 394 return; 395 } 396 397 // All the locks that block this new exclusive internal lock need to be 398 // invalidated. We also need to notify clients to abort operations for them. 399 QuotaManager::DirectoryLockIdTableArray lockIds; 400 lockIds.SetLength(Client::TypeMax()); 401 402 const auto& blockedOnLocks = GetBlockedOnLocks(); 403 MOZ_ASSERT(!blockedOnLocks.IsEmpty()); 404 405 for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) { 406 if (!blockedOnLock->IsInternal()) { 407 blockedOnLock->Invalidate(); 408 409 // Clients don't have to handle pending locks. Invalidation is sufficient 410 // in that case (once a lock is ready and the listener needs to be 411 // notified, we will call DirectoryLockFailed instead of 412 // DirectoryLockAcquired which should release any remaining references to 413 // the lock). 414 if (!blockedOnLock->IsPending()) { 415 lockIds[blockedOnLock->ClientType()].Put(blockedOnLock->Id()); 416 } 417 } 418 } 419 420 mQuotaManager->AbortOperationsForLocks(lockIds); 421 } 422 423 void DirectoryLockImpl::Invalidate() { 424 AssertIsOnOwningThread(); 425 426 mInvalidated.EnsureFlipped(); 427 428 if (mInvalidateCallback) { 429 MOZ_ALWAYS_SUCCEEDS(GetCurrentSerialEventTarget()->Dispatch( 430 NS_NewRunnableFunction("DirectoryLockImpl::Invalidate", 431 [invalidateCallback = mInvalidateCallback]() { 432 invalidateCallback(); 433 }), 434 NS_DISPATCH_NORMAL)); 435 } 436 } 437 438 void DirectoryLockImpl::Unregister() { 439 AssertIsOnOwningThread(); 440 MOZ_ASSERT(mRegistered); 441 442 // We must call UnregisterDirectoryLock before unblocking other locks because 443 // UnregisterDirectoryLock also updates the origin last access time and the 444 // access flag (if the last lock for given origin is unregistered). One of the 445 // blocked locks could be requested by the clear/reset operation which stores 446 // cached information about origins in storage.sqlite. So if the access flag 447 // is not updated before unblocking the lock for reset/clear, we might store 448 // invalid information which can lead to omitting origin initialization during 449 // next temporary storage initialization. 450 mQuotaManager->UnregisterDirectoryLock(*this); 451 452 MOZ_ASSERT(!mRegistered); 453 454 for (NotNull<RefPtr<DirectoryLockImpl>> blockingLock : mBlocking) { 455 blockingLock->MaybeUnblock(*this); 456 } 457 458 mBlocking.Clear(); 459 } 460 461 } // namespace mozilla::dom::quota