tor-browser

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

ServiceWorkerShutdownBlocker.cpp (8340B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ServiceWorkerShutdownBlocker.h"
      8 
      9 #include <chrono>
     10 #include <utility>
     11 
     12 #include "MainThreadUtils.h"
     13 #include "ServiceWorkerManager.h"
     14 #include "mozilla/Assertions.h"
     15 #include "mozilla/RefPtr.h"
     16 #include "nsComponentManagerUtils.h"
     17 #include "nsDebug.h"
     18 #include "nsError.h"
     19 #include "nsIWritablePropertyBag2.h"
     20 #include "nsThreadUtils.h"
     21 
     22 namespace mozilla::dom {
     23 
     24 NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker, nsIAsyncShutdownBlocker,
     25                  nsITimerCallback, nsINamed)
     26 
     27 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsAString& aNameOut) {
     28  aNameOut = nsLiteralString(
     29      u"ServiceWorkerShutdownBlocker: shutting down Service Workers");
     30  return NS_OK;
     31 }
     32 
     33 // nsINamed implementation
     34 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsACString& aNameOut) {
     35  aNameOut.AssignLiteral("ServiceWorkerShutdownBlocker");
     36  return NS_OK;
     37 }
     38 
     39 NS_IMETHODIMP
     40 ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aClient) {
     41  AssertIsOnMainThread();
     42  MOZ_ASSERT(!mShutdownClient);
     43  MOZ_ASSERT(mServiceWorkerManager);
     44 
     45  mShutdownClient = aClient;
     46 
     47  (*mServiceWorkerManager)->MaybeStartShutdown();
     48  mServiceWorkerManager.destroy();
     49 
     50  MaybeUnblockShutdown();
     51  MaybeInitUnblockShutdownTimer();
     52 
     53  return NS_OK;
     54 }
     55 
     56 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetState(nsIPropertyBag** aBagOut) {
     57  AssertIsOnMainThread();
     58  MOZ_ASSERT(aBagOut);
     59 
     60  nsCOMPtr<nsIWritablePropertyBag2> propertyBag =
     61      do_CreateInstance("@mozilla.org/hash-property-bag;1");
     62 
     63  if (NS_WARN_IF(!propertyBag)) {
     64    return NS_ERROR_OUT_OF_MEMORY;
     65  }
     66 
     67  nsresult rv = propertyBag->SetPropertyAsBool(u"acceptingPromises"_ns,
     68                                               IsAcceptingPromises());
     69 
     70  if (NS_WARN_IF(NS_FAILED(rv))) {
     71    return rv;
     72  }
     73 
     74  rv = propertyBag->SetPropertyAsUint32(u"pendingPromises"_ns,
     75                                        GetPendingPromises());
     76 
     77  if (NS_WARN_IF(NS_FAILED(rv))) {
     78    return rv;
     79  }
     80 
     81  nsAutoCString shutdownStates;
     82 
     83  for (auto iter = mShutdownStates.iter(); !iter.done(); iter.next()) {
     84    shutdownStates.Append(iter.get().value().GetProgressString());
     85    shutdownStates.Append(", ");
     86  }
     87 
     88  rv = propertyBag->SetPropertyAsACString(u"shutdownStates"_ns, shutdownStates);
     89 
     90  if (NS_WARN_IF(NS_FAILED(rv))) {
     91    return rv;
     92  }
     93 
     94  propertyBag.forget(aBagOut);
     95 
     96  return NS_OK;
     97 }
     98 
     99 /* static */ already_AddRefed<ServiceWorkerShutdownBlocker>
    100 ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
    101    nsIAsyncShutdownClient& aShutdownBarrier,
    102    ServiceWorkerManager& aServiceWorkerManager) {
    103  AssertIsOnMainThread();
    104 
    105  RefPtr<ServiceWorkerShutdownBlocker> blocker =
    106      new ServiceWorkerShutdownBlocker(aServiceWorkerManager);
    107 
    108  nsresult rv = aShutdownBarrier.AddBlocker(
    109      blocker.get(), NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
    110      u"Service Workers shutdown"_ns);
    111 
    112  if (NS_WARN_IF(NS_FAILED(rv))) {
    113    return nullptr;
    114  }
    115 
    116  return blocker.forget();
    117 }
    118 
    119 void ServiceWorkerShutdownBlocker::WaitOnPromise(
    120    GenericNonExclusivePromise* aPromise, uint32_t aShutdownStateId) {
    121  AssertIsOnMainThread();
    122  MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises());
    123  MOZ_ASSERT(aPromise);
    124  MOZ_ASSERT(mShutdownStates.has(aShutdownStateId));
    125 
    126  ++mState.as<AcceptingPromises>().mPendingPromises;
    127 
    128  RefPtr<ServiceWorkerShutdownBlocker> self = this;
    129 
    130  aPromise->Then(GetCurrentSerialEventTarget(), __func__,
    131                 [self = std::move(self), shutdownStateId = aShutdownStateId](
    132                     const GenericNonExclusivePromise::ResolveOrRejectValue&) {
    133                   // Progress reporting might race with aPromise settling.
    134                   self->mShutdownStates.remove(shutdownStateId);
    135 
    136                   if (!self->PromiseSettled()) {
    137                     self->MaybeUnblockShutdown();
    138                   }
    139                 });
    140 }
    141 
    142 void ServiceWorkerShutdownBlocker::StopAcceptingPromises() {
    143  AssertIsOnMainThread();
    144  MOZ_ASSERT(IsAcceptingPromises());
    145 
    146  mState = AsVariant(NotAcceptingPromises(mState.as<AcceptingPromises>()));
    147 
    148  MaybeUnblockShutdown();
    149  MaybeInitUnblockShutdownTimer();
    150 }
    151 
    152 uint32_t ServiceWorkerShutdownBlocker::CreateShutdownState() {
    153  AssertIsOnMainThread();
    154 
    155  static uint32_t nextShutdownStateId = 1;
    156 
    157  MOZ_ALWAYS_TRUE(mShutdownStates.putNew(nextShutdownStateId,
    158                                         ServiceWorkerShutdownState()));
    159 
    160  return nextShutdownStateId++;
    161 }
    162 
    163 void ServiceWorkerShutdownBlocker::ReportShutdownProgress(
    164    uint32_t aShutdownStateId, Progress aProgress) {
    165  AssertIsOnMainThread();
    166  MOZ_RELEASE_ASSERT(aShutdownStateId != kInvalidShutdownStateId);
    167 
    168  auto lookup = mShutdownStates.lookup(aShutdownStateId);
    169 
    170  // Progress reporting might race with the promise that WaitOnPromise is called
    171  // with settling.
    172  if (!lookup) {
    173    return;
    174  }
    175 
    176  // This will check for a valid progress transition with assertions.
    177  lookup->value().SetProgress(aProgress);
    178 
    179  if (aProgress == Progress::ShutdownCompleted) {
    180    mShutdownStates.remove(lookup);
    181  }
    182 }
    183 
    184 ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker(
    185    ServiceWorkerManager& aServiceWorkerManager)
    186    : mState(VariantType<AcceptingPromises>()),
    187      mServiceWorkerManager(WrapNotNull(&aServiceWorkerManager)) {
    188  AssertIsOnMainThread();
    189 }
    190 
    191 ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() {
    192  MOZ_ASSERT(!IsAcceptingPromises());
    193  MOZ_ASSERT(!GetPendingPromises());
    194  MOZ_ASSERT(!mShutdownClient);
    195  MOZ_ASSERT(!mServiceWorkerManager);
    196 }
    197 
    198 void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() {
    199  AssertIsOnMainThread();
    200 
    201  if (!mShutdownClient || IsAcceptingPromises() || GetPendingPromises()) {
    202    return;
    203  }
    204 
    205  UnblockShutdown();
    206 }
    207 
    208 void ServiceWorkerShutdownBlocker::UnblockShutdown() {
    209  MOZ_ASSERT(mShutdownClient);
    210 
    211  mShutdownClient->RemoveBlocker(this);
    212  mShutdownClient = nullptr;
    213 
    214  if (mTimer) {
    215    mTimer->Cancel();
    216  }
    217 }
    218 
    219 uint32_t ServiceWorkerShutdownBlocker::PromiseSettled() {
    220  AssertIsOnMainThread();
    221  MOZ_ASSERT(GetPendingPromises());
    222 
    223  if (IsAcceptingPromises()) {
    224    return --mState.as<AcceptingPromises>().mPendingPromises;
    225  }
    226 
    227  return --mState.as<NotAcceptingPromises>().mPendingPromises;
    228 }
    229 
    230 bool ServiceWorkerShutdownBlocker::IsAcceptingPromises() const {
    231  AssertIsOnMainThread();
    232 
    233  return mState.is<AcceptingPromises>();
    234 }
    235 
    236 uint32_t ServiceWorkerShutdownBlocker::GetPendingPromises() const {
    237  AssertIsOnMainThread();
    238 
    239  if (IsAcceptingPromises()) {
    240    return mState.as<AcceptingPromises>().mPendingPromises;
    241  }
    242 
    243  return mState.as<NotAcceptingPromises>().mPendingPromises;
    244 }
    245 
    246 ServiceWorkerShutdownBlocker::NotAcceptingPromises::NotAcceptingPromises(
    247    AcceptingPromises aPreviousState)
    248    : mPendingPromises(aPreviousState.mPendingPromises) {
    249  AssertIsOnMainThread();
    250 }
    251 
    252 NS_IMETHODIMP ServiceWorkerShutdownBlocker::Notify(nsITimer*) {
    253  // TODO: this method being called indicates that there are ServiceWorkers
    254  // that did not complete shutdown before the timer expired - there should be
    255  // a telemetry ping or some other way of recording the state of when this
    256  // happens (e.g. what's returned by GetState()).
    257  UnblockShutdown();
    258  return NS_OK;
    259 }
    260 
    261 #ifdef RELEASE_OR_BETA
    262 #  define SW_UNBLOCK_SHUTDOWN_TIMER_DURATION 10s
    263 #else
    264 // In Nightly, we do want a shutdown hang to be reported so we pick a value
    265 // notably longer than the 60s of the RunWatchDog timeout.
    266 #  define SW_UNBLOCK_SHUTDOWN_TIMER_DURATION 200s
    267 #endif
    268 
    269 void ServiceWorkerShutdownBlocker::MaybeInitUnblockShutdownTimer() {
    270  AssertIsOnMainThread();
    271 
    272  if (mTimer || !mShutdownClient || IsAcceptingPromises()) {
    273    return;
    274  }
    275 
    276  MOZ_ASSERT(GetPendingPromises(),
    277             "Shouldn't be blocking shutdown with zero pending promises.");
    278 
    279  using namespace std::chrono_literals;
    280 
    281  static constexpr auto delay =
    282      std::chrono::duration_cast<std::chrono::milliseconds>(
    283          SW_UNBLOCK_SHUTDOWN_TIMER_DURATION);
    284 
    285  mTimer = NS_NewTimer();
    286 
    287  mTimer->InitWithCallback(this, delay.count(), nsITimer::TYPE_ONE_SHOT);
    288 }
    289 
    290 }  // namespace mozilla::dom