зеркало из https://github.com/mozilla/gecko-dev.git
292 строки
8.1 KiB
C++
292 строки
8.1 KiB
C++
/* -*- 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
|