forked from mirrors/gecko-dev
This patch adds a timer to ServiceWorkerShutdownBlocker in beta/release builds. The timer, arbitrarily set to 10 seconds, begins once the shutdown blocker "begins" to block shutdown. When the timer expires, shutdown will be forcefully unblocked (even if there's still ServiceWorkers that have not completed shutdown). If all ServiceWorkers shutdown before the timer expires, the timer callback will be canceled. All ServiceWorkers should (in theory) shutdown before the timer expires, but apparently this is not always the case (for currently unknown reasons). So, it is arguably better from a user-perspective to shutdown uncleanly than stall and crash when exiting. There also should not be anything unsafe happening as a result. Differential Revision: https://phabricator.services.mozilla.com/D63499 --HG-- extra : moz-landing-system : lando
148 lines
4.8 KiB
C++
148 lines
4.8 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/. */
|
|
|
|
#ifndef mozilla_dom_serviceworkershutdownblocker_h__
|
|
#define mozilla_dom_serviceworkershutdownblocker_h__
|
|
|
|
#include "nsCOMPtr.h"
|
|
#include "nsIAsyncShutdown.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsITimer.h"
|
|
|
|
#include "ServiceWorkerShutdownState.h"
|
|
#include "mozilla/MozPromise.h"
|
|
#include "mozilla/HashTable.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
/**
|
|
* Main thread only.
|
|
*
|
|
* A ServiceWorkerShutdownBlocker will "accept promises", and each of these
|
|
* promises will be a "pending promise" while it hasn't settled. At some point,
|
|
* `StopAcceptingPromises()` should be called and the state will change to "not
|
|
* accepting promises" (this is a one way state transition). The shutdown phase
|
|
* of the shutdown client the blocker is created with will be blocked until
|
|
* there are no more pending promises.
|
|
*
|
|
* It doesn't matter whether the state changes to "not accepting promises"
|
|
* before or during the associated shutdown phase.
|
|
*
|
|
* In beta/release builds there will be an additional timer that starts ticking
|
|
* once both the shutdown phase has been reached and the state is "not accepting
|
|
* promises". If when the timer expire there are still pending promises,
|
|
* shutdown will be forcefully unblocked.
|
|
*/
|
|
class ServiceWorkerShutdownBlocker final : public nsIAsyncShutdownBlocker,
|
|
public nsITimerCallback {
|
|
public:
|
|
using Progress = ServiceWorkerShutdownState::Progress;
|
|
static const uint32_t kInvalidShutdownStateId = 0;
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIASYNCSHUTDOWNBLOCKER
|
|
NS_DECL_NSITIMERCALLBACK
|
|
|
|
/**
|
|
* 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).
|
|
*
|
|
* See `CreateShutdownState` for aShutdownStateId, which is needed to clear
|
|
* the shutdown state if the shutdown process aborts for some reason.
|
|
*/
|
|
void WaitOnPromise(GenericNonExclusivePromise* aPromise,
|
|
uint32_t aShutdownStateId);
|
|
|
|
/**
|
|
* 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();
|
|
|
|
/**
|
|
* Start tracking the shutdown of an individual ServiceWorker for hang
|
|
* reporting purposes. Returns a "shutdown state ID" that should be used
|
|
* in subsequent calls to ReportShutdownProgress. The shutdown of an
|
|
* individual ServiceWorker is presumed to be completed when its `Progress`
|
|
* reaches `Progress::ShutdownCompleted`.
|
|
*/
|
|
uint32_t CreateShutdownState();
|
|
|
|
void ReportShutdownProgress(uint32_t aShutdownStateId, Progress aProgress);
|
|
|
|
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();
|
|
|
|
/**
|
|
* Requires `BlockShutdown()` to have been called.
|
|
*/
|
|
void UnblockShutdown();
|
|
|
|
/**
|
|
* Returns the remaining pending promise count (i.e. excluding the promise
|
|
* that just settled).
|
|
*/
|
|
uint32_t PromiseSettled();
|
|
|
|
bool IsAcceptingPromises() const;
|
|
|
|
uint32_t GetPendingPromises() const;
|
|
|
|
/**
|
|
* Initializes a timer that will unblock shutdown unconditionally once it's
|
|
* expired (even if there are still pending promises). No-op if:
|
|
* 1) not a beta or release build, or
|
|
* 2) shutdown is not being blocked or `StopAcceptingPromises()` has not been
|
|
* called.
|
|
*/
|
|
void MaybeInitUnblockShutdownTimer();
|
|
|
|
struct AcceptingPromises {
|
|
uint32_t mPendingPromises = 0;
|
|
};
|
|
|
|
struct NotAcceptingPromises {
|
|
explicit NotAcceptingPromises(AcceptingPromises aPreviousState);
|
|
|
|
uint32_t mPendingPromises = 0;
|
|
};
|
|
|
|
Variant<AcceptingPromises, NotAcceptingPromises> mState;
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> mShutdownClient;
|
|
|
|
HashMap<uint32_t, ServiceWorkerShutdownState> mShutdownStates;
|
|
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
};
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_dom_serviceworkershutdownblocker_h__
|