Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <chrono>
#include <utility>
#include "MainThreadUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIWritablePropertyBag2.h"
#include "nsThreadUtils.h"
#include "ServiceWorkerManager.h"
#include "mozilla/Assertions.h"
#include "mozilla/RefPtr.h"
namespace mozilla::dom {
NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker, nsIAsyncShutdownBlocker,
nsITimerCallback, nsINamed)
NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsAString& aNameOut) {
aNameOut = nsLiteralString(
u"ServiceWorkerShutdownBlocker: shutting down Service Workers");
return NS_OK;
}
// nsINamed implementation
NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsACString& aNameOut) {
aNameOut.AssignLiteral("ServiceWorkerShutdownBlocker");
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aClient) {
AssertIsOnMainThread();
MOZ_ASSERT(!mShutdownClient);
MOZ_ASSERT(mServiceWorkerManager);
mShutdownClient = aClient;
(*mServiceWorkerManager)->MaybeStartShutdown();
mServiceWorkerManager.destroy();
MaybeUnblockShutdown();
MaybeInitUnblockShutdownTimer();
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(u"acceptingPromises"_ns,
IsAcceptingPromises());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = propertyBag->SetPropertyAsUint32(u"pendingPromises"_ns,
GetPendingPromises());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoCString shutdownStates;
for (auto iter = mShutdownStates.iter(); !iter.done(); iter.next()) {
shutdownStates.Append(iter.get().value().GetProgressString());
shutdownStates.Append(", ");
}
rv = propertyBag->SetPropertyAsACString(u"shutdownStates"_ns, shutdownStates);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
propertyBag.forget(aBagOut);
return NS_OK;
}
/* static */ already_AddRefed<ServiceWorkerShutdownBlocker>
ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
nsIAsyncShutdownClient& aShutdownBarrier,
ServiceWorkerManager& aServiceWorkerManager) {
AssertIsOnMainThread();
RefPtr<ServiceWorkerShutdownBlocker> blocker =
new ServiceWorkerShutdownBlocker(aServiceWorkerManager);
nsresult rv = aShutdownBarrier.AddBlocker(
blocker.get(), NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
u"Service Workers shutdown"_ns);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
return blocker.forget();
}
void ServiceWorkerShutdownBlocker::WaitOnPromise(
GenericNonExclusivePromise* aPromise, uint32_t aShutdownStateId) {
AssertIsOnMainThread();
MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises());
MOZ_ASSERT(aPromise);
MOZ_ASSERT(mShutdownStates.has(aShutdownStateId));
++mState.as<AcceptingPromises>().mPendingPromises;
RefPtr<ServiceWorkerShutdownBlocker> self = this;
aPromise->Then(GetCurrentSerialEventTarget(), __func__,
[self = std::move(self), shutdownStateId = aShutdownStateId](
const GenericNonExclusivePromise::ResolveOrRejectValue&) {
// Progress reporting might race with aPromise settling.
self->mShutdownStates.remove(shutdownStateId);
if (!self->PromiseSettled()) {
self->MaybeUnblockShutdown();
}
});
}
void ServiceWorkerShutdownBlocker::StopAcceptingPromises() {
AssertIsOnMainThread();
MOZ_ASSERT(IsAcceptingPromises());
mState = AsVariant(NotAcceptingPromises(mState.as<AcceptingPromises>()));
MaybeUnblockShutdown();
MaybeInitUnblockShutdownTimer();
}
uint32_t ServiceWorkerShutdownBlocker::CreateShutdownState() {
AssertIsOnMainThread();
static uint32_t nextShutdownStateId = 1;
MOZ_ALWAYS_TRUE(mShutdownStates.putNew(nextShutdownStateId,
ServiceWorkerShutdownState()));
return nextShutdownStateId++;
}
void ServiceWorkerShutdownBlocker::ReportShutdownProgress(
uint32_t aShutdownStateId, Progress aProgress) {
AssertIsOnMainThread();
MOZ_RELEASE_ASSERT(aShutdownStateId != kInvalidShutdownStateId);
auto lookup = mShutdownStates.lookup(aShutdownStateId);
// Progress reporting might race with the promise that WaitOnPromise is called
// with settling.
if (!lookup) {
return;
}
// This will check for a valid progress transition with assertions.
lookup->value().SetProgress(aProgress);
if (aProgress == Progress::ShutdownCompleted) {
mShutdownStates.remove(lookup);
}
}
ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker(
ServiceWorkerManager& aServiceWorkerManager)
: mState(VariantType<AcceptingPromises>()),
mServiceWorkerManager(WrapNotNull(&aServiceWorkerManager)) {
AssertIsOnMainThread();
}
ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() {
MOZ_ASSERT(!IsAcceptingPromises());
MOZ_ASSERT(!GetPendingPromises());
MOZ_ASSERT(!mShutdownClient);
MOZ_ASSERT(!mServiceWorkerManager);
}
void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() {
AssertIsOnMainThread();
if (!mShutdownClient || IsAcceptingPromises() || GetPendingPromises()) {
return;
}
UnblockShutdown();
}
void ServiceWorkerShutdownBlocker::UnblockShutdown() {
MOZ_ASSERT(mShutdownClient);
mShutdownClient->RemoveBlocker(this);
mShutdownClient = nullptr;
if (mTimer) {
mTimer->Cancel();
}
}
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();
}
NS_IMETHODIMP ServiceWorkerShutdownBlocker::Notify(nsITimer*) {
// TODO: this method being called indicates that there are ServiceWorkers
// that did not complete shutdown before the timer expired - there should be
// a telemetry ping or some other way of recording the state of when this
// happens (e.g. what's returned by GetState()).
UnblockShutdown();
return NS_OK;
}
#ifdef RELEASE_OR_BETA
# define SW_UNBLOCK_SHUTDOWN_TIMER_DURATION 10s
#else
// In Nightly, we do want a shutdown hang to be reported so we pick a value
// notably longer than the 60s of the RunWatchDog timeout.
# define SW_UNBLOCK_SHUTDOWN_TIMER_DURATION 200s
#endif
void ServiceWorkerShutdownBlocker::MaybeInitUnblockShutdownTimer() {
AssertIsOnMainThread();
if (mTimer || !mShutdownClient || IsAcceptingPromises()) {
return;
}
MOZ_ASSERT(GetPendingPromises(),
"Shouldn't be blocking shutdown with zero pending promises.");
using namespace std::chrono_literals;
static constexpr auto delay =
std::chrono::duration_cast<std::chrono::milliseconds>(
SW_UNBLOCK_SHUTDOWN_TIMER_DURATION);
mTimer = NS_NewTimer();
mTimer->InitWithCallback(this, delay.count(), nsITimer::TYPE_ONE_SHOT);
}
} // namespace mozilla::dom