Bug 1231213 - Implement ServiceWorkerShutdownBlocker. r=asuth
☠☠ backed out by 3cf55b7f12f2 ☠ ☠
authorPerry Jiang <perry@mozilla.com>
Wed, 14 Aug 2019 16:19:44 +0000
changeset 487971 bacf8499ba7b2caf600a8fef352baf7df3a69d6d
parent 487970 bf5d60c7a85a2444df3a3740c5daeb230a87e248
child 487972 395062aef2ec1c0abd3b1426fb376f5fef3e0c11
push id36434
push usercbrindusan@mozilla.com
push dateThu, 15 Aug 2019 09:44:30 +0000
treeherdermozilla-central@144fbfb409b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1231213
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1231213 - Implement ServiceWorkerShutdownBlocker. r=asuth Differential Revision: https://phabricator.services.mozilla.com/D26165
dom/serviceworkers/ServiceWorkerShutdownBlocker.cpp
dom/serviceworkers/ServiceWorkerShutdownBlocker.h
dom/serviceworkers/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/ServiceWorkerShutdownBlocker.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ServiceWorkerShutdownBlocker.h"
+
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker, nsIAsyncShutdownBlocker)
+
+NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsAString& aNameOut) {
+  aNameOut = NS_LITERAL_STRING(
+      "ServiceWorkerShutdownBlocker: shutting down Service Workers");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aClient) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(!mShutdownClient);
+
+  mShutdownClient = aClient;
+  MaybeUnblockShutdown();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetState(nsIPropertyBag** aBagOut) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aBagOut);
+
+  nsCOMPtr<nsIWritablePropertyBag2> propertyBag =
+      do_CreateInstance("@mozilla.org/hash-property-bag;1");
+
+  if (NS_WARN_IF(!propertyBag)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsresult rv = propertyBag->SetPropertyAsBool(
+      NS_LITERAL_STRING("acceptingPromises"), IsAcceptingPromises());
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = propertyBag->SetPropertyAsUint32(NS_LITERAL_STRING("pendingPromises"),
+                                        GetPendingPromises());
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  propertyBag.forget(aBagOut);
+
+  return NS_OK;
+}
+
+/* static */ already_AddRefed<ServiceWorkerShutdownBlocker>
+ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
+    nsIAsyncShutdownClient* aShutdownBarrier) {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aShutdownBarrier);
+
+  RefPtr<ServiceWorkerShutdownBlocker> blocker =
+      new ServiceWorkerShutdownBlocker();
+
+  nsresult rv = aShutdownBarrier->AddBlocker(
+      blocker.get(), NS_LITERAL_STRING(__FILE__), __LINE__,
+      NS_LITERAL_STRING("Service Workers shutdown"));
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  return blocker.forget();
+}
+
+void ServiceWorkerShutdownBlocker::WaitOnPromise(
+    GenericNonExclusivePromise* aPromise) {
+  AssertIsOnMainThread();
+  MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises());
+  MOZ_ASSERT(aPromise);
+
+  ++mState.as<AcceptingPromises>().mPendingPromises;
+
+  RefPtr<ServiceWorkerShutdownBlocker> self = this;
+
+  aPromise->Then(GetCurrentThreadSerialEventTarget(), __func__,
+                 [self = std::move(self)](
+                     const GenericNonExclusivePromise::ResolveOrRejectValue&) {
+                   if (!self->PromiseSettled()) {
+                     self->MaybeUnblockShutdown();
+                   }
+                 });
+}
+
+void ServiceWorkerShutdownBlocker::StopAcceptingPromises() {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(IsAcceptingPromises());
+
+  mState = AsVariant(NotAcceptingPromises(mState.as<AcceptingPromises>()));
+}
+
+ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker()
+    : mState(VariantType<AcceptingPromises>()) {
+  AssertIsOnMainThread();
+}
+
+ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() {
+  MOZ_ASSERT(!IsAcceptingPromises());
+  MOZ_ASSERT(!GetPendingPromises());
+  MOZ_ASSERT(!mShutdownClient);
+}
+
+void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() {
+  AssertIsOnMainThread();
+
+  if (!mShutdownClient || IsAcceptingPromises() || GetPendingPromises()) {
+    return;
+  }
+
+  mShutdownClient->RemoveBlocker(this);
+  mShutdownClient = nullptr;
+}
+
+uint32_t ServiceWorkerShutdownBlocker::PromiseSettled() {
+  AssertIsOnMainThread();
+  MOZ_ASSERT(GetPendingPromises());
+
+  if (IsAcceptingPromises()) {
+    return --mState.as<AcceptingPromises>().mPendingPromises;
+  }
+
+  return --mState.as<NotAcceptingPromises>().mPendingPromises;
+}
+
+bool ServiceWorkerShutdownBlocker::IsAcceptingPromises() const {
+  AssertIsOnMainThread();
+
+  return mState.is<AcceptingPromises>();
+}
+
+uint32_t ServiceWorkerShutdownBlocker::GetPendingPromises() const {
+  AssertIsOnMainThread();
+
+  if (IsAcceptingPromises()) {
+    return mState.as<AcceptingPromises>().mPendingPromises;
+  }
+
+  return mState.as<NotAcceptingPromises>().mPendingPromises;
+}
+
+ServiceWorkerShutdownBlocker::NotAcceptingPromises::NotAcceptingPromises(
+    AcceptingPromises aPreviousState)
+    : mPendingPromises(aPreviousState.mPendingPromises) {
+  AssertIsOnMainThread();
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/serviceworkers/ServiceWorkerShutdownBlocker.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_serviceworkershutdownblocker_h__
+#define mozilla_dom_serviceworkershutdownblocker_h__
+
+#include "nsCOMPtr.h"
+#include "nsIAsyncShutdown.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/MozPromise.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Main thread only.
+ */
+class ServiceWorkerShutdownBlocker final : public nsIAsyncShutdownBlocker {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIASYNCSHUTDOWNBLOCKER
+
+  /**
+   * Returns the registered shutdown blocker if registration succeeded and
+   * nullptr otherwise.
+   */
+  static already_AddRefed<ServiceWorkerShutdownBlocker> CreateAndRegisterOn(
+      nsIAsyncShutdownClient* aShutdownBarrier);
+
+  /**
+   * Blocks shutdown until `aPromise` settles.
+   *
+   * Can be called multiple times, and shutdown will be blocked until all the
+   * calls' promises settle, but all of these calls must happen before
+   * `StopAcceptingPromises()` is called (assertions will enforce this).
+   */
+  void WaitOnPromise(GenericNonExclusivePromise* aPromise);
+
+  /**
+   * Once this is called, shutdown will be blocked until all promises
+   * passed to `WaitOnPromise()` settle, and there must be no more calls to
+   * `WaitOnPromise()` (assertions will enforce this).
+   */
+  void StopAcceptingPromises();
+
+ private:
+  ServiceWorkerShutdownBlocker();
+
+  ~ServiceWorkerShutdownBlocker();
+
+  /**
+   * No-op if any of the following are true:
+   * 1) `BlockShutdown()` hasn't been called yet, or
+   * 2) `StopAcceptingPromises()` hasn't been called yet, or
+   * 3) `StopAcceptingPromises()` HAS been called, but there are still pending
+   *    promises.
+   */
+  void MaybeUnblockShutdown();
+
+  /**
+   * Returns the remaining pending promise count (i.e. excluding the promise
+   * that just settled).
+   */
+  uint32_t PromiseSettled();
+
+  bool IsAcceptingPromises() const;
+
+  uint32_t GetPendingPromises() const;
+
+  struct AcceptingPromises {
+    uint32_t mPendingPromises = 0;
+  };
+
+  struct NotAcceptingPromises {
+    explicit NotAcceptingPromises(AcceptingPromises aPreviousState);
+
+    uint32_t mPendingPromises = 0;
+  };
+
+  Variant<AcceptingPromises, NotAcceptingPromises> mState;
+
+  nsCOMPtr<nsIAsyncShutdownClient> mShutdownClient;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_serviceworkershutdownblocker_h__
--- a/dom/serviceworkers/moz.build
+++ b/dom/serviceworkers/moz.build
@@ -60,16 +60,17 @@ UNIFIED_SOURCES += [
     'ServiceWorkerRegistration.cpp',
     'ServiceWorkerRegistrationChild.cpp',
     'ServiceWorkerRegistrationDescriptor.cpp',
     'ServiceWorkerRegistrationImpl.cpp',
     'ServiceWorkerRegistrationInfo.cpp',
     'ServiceWorkerRegistrationParent.cpp',
     'ServiceWorkerRegistrationProxy.cpp',
     'ServiceWorkerScriptCache.cpp',
+    'ServiceWorkerShutdownBlocker.cpp',
     'ServiceWorkerUnregisterCallback.cpp',
     'ServiceWorkerUnregisterJob.cpp',
     'ServiceWorkerUpdateJob.cpp',
     'ServiceWorkerUpdaterChild.cpp',
     'ServiceWorkerUpdaterParent.cpp',
     'ServiceWorkerUtils.cpp',
 ]