tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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