Bug 1231213 - Implement ServiceWorkerShutdownBlocker. r=asuth
☠☠ backed out by ad33a9311b30 ☠ ☠
authorPerry Jiang <perry@mozilla.com>
Tue, 13 Aug 2019 19:55:43 +0000
changeset 487761 0fd1fd7c3f06e4db046c4b790e4e0326715bd94f
parent 487760 3d539eb7faff3ebe834ad18cca2971bb1d94e38b
child 487762 1b7169b75760aa3fc197e874f1a42372ce8d95f7
push id36430
push userdvarga@mozilla.com
push dateWed, 14 Aug 2019 04:09:17 +0000
treeherdermozilla-central@d3deef805f92 [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',
 ]