1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ServiceWorkerShutdownBlocker.h"
12 #include "MainThreadUtils.h"
15 #include "nsIWritablePropertyBag2.h"
16 #include "nsThreadUtils.h"
18 #include "mozilla/Assertions.h"
19 #include "mozilla/RefPtr.h"
24 NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker
, nsIAsyncShutdownBlocker
)
26 NS_IMETHODIMP
ServiceWorkerShutdownBlocker::GetName(nsAString
& aNameOut
) {
27 aNameOut
= nsLiteralString(
28 u
"ServiceWorkerShutdownBlocker: shutting down Service Workers");
33 ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient
* aClient
) {
34 AssertIsOnMainThread();
35 MOZ_ASSERT(!mShutdownClient
);
37 mShutdownClient
= aClient
;
39 MaybeUnblockShutdown();
40 MaybeInitUnblockShutdownTimer();
45 NS_IMETHODIMP
ServiceWorkerShutdownBlocker::GetState(nsIPropertyBag
** aBagOut
) {
46 AssertIsOnMainThread();
49 nsCOMPtr
<nsIWritablePropertyBag2
> propertyBag
=
50 do_CreateInstance("@mozilla.org/hash-property-bag;1");
52 if (NS_WARN_IF(!propertyBag
)) {
53 return NS_ERROR_OUT_OF_MEMORY
;
56 nsresult rv
= propertyBag
->SetPropertyAsBool(u
"acceptingPromises"_ns
,
57 IsAcceptingPromises());
59 if (NS_WARN_IF(NS_FAILED(rv
))) {
63 rv
= propertyBag
->SetPropertyAsUint32(u
"pendingPromises"_ns
,
64 GetPendingPromises());
66 if (NS_WARN_IF(NS_FAILED(rv
))) {
70 nsAutoCString shutdownStates
;
72 for (auto iter
= mShutdownStates
.iter(); !iter
.done(); iter
.next()) {
73 shutdownStates
.Append(iter
.get().value().GetProgressString());
74 shutdownStates
.Append(", ");
77 rv
= propertyBag
->SetPropertyAsACString(u
"shutdownStates"_ns
, shutdownStates
);
79 if (NS_WARN_IF(NS_FAILED(rv
))) {
83 propertyBag
.forget(aBagOut
);
88 /* static */ already_AddRefed
<ServiceWorkerShutdownBlocker
>
89 ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
90 nsIAsyncShutdownClient
* aShutdownBarrier
) {
91 AssertIsOnMainThread();
92 MOZ_ASSERT(aShutdownBarrier
);
94 RefPtr
<ServiceWorkerShutdownBlocker
> blocker
=
95 new ServiceWorkerShutdownBlocker();
97 nsresult rv
= aShutdownBarrier
->AddBlocker(
98 blocker
.get(), NS_LITERAL_STRING_FROM_CSTRING(__FILE__
), __LINE__
,
99 u
"Service Workers shutdown"_ns
);
101 if (NS_WARN_IF(NS_FAILED(rv
))) {
105 return blocker
.forget();
108 void ServiceWorkerShutdownBlocker::WaitOnPromise(
109 GenericNonExclusivePromise
* aPromise
, uint32_t aShutdownStateId
) {
110 AssertIsOnMainThread();
111 MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises());
112 MOZ_ASSERT(aPromise
);
113 MOZ_ASSERT(mShutdownStates
.has(aShutdownStateId
));
115 ++mState
.as
<AcceptingPromises
>().mPendingPromises
;
117 RefPtr
<ServiceWorkerShutdownBlocker
> self
= this;
119 aPromise
->Then(GetCurrentSerialEventTarget(), __func__
,
120 [self
= std::move(self
), shutdownStateId
= aShutdownStateId
](
121 const GenericNonExclusivePromise::ResolveOrRejectValue
&) {
122 // Progress reporting might race with aPromise settling.
123 self
->mShutdownStates
.remove(shutdownStateId
);
125 if (!self
->PromiseSettled()) {
126 self
->MaybeUnblockShutdown();
131 void ServiceWorkerShutdownBlocker::StopAcceptingPromises() {
132 AssertIsOnMainThread();
133 MOZ_ASSERT(IsAcceptingPromises());
135 mState
= AsVariant(NotAcceptingPromises(mState
.as
<AcceptingPromises
>()));
137 MaybeUnblockShutdown();
138 MaybeInitUnblockShutdownTimer();
141 uint32_t ServiceWorkerShutdownBlocker::CreateShutdownState() {
142 AssertIsOnMainThread();
144 static uint32_t nextShutdownStateId
= 1;
146 MOZ_ALWAYS_TRUE(mShutdownStates
.putNew(nextShutdownStateId
,
147 ServiceWorkerShutdownState()));
149 return nextShutdownStateId
++;
152 void ServiceWorkerShutdownBlocker::ReportShutdownProgress(
153 uint32_t aShutdownStateId
, Progress aProgress
) {
154 AssertIsOnMainThread();
155 MOZ_RELEASE_ASSERT(aShutdownStateId
!= kInvalidShutdownStateId
);
157 auto lookup
= mShutdownStates
.lookup(aShutdownStateId
);
159 // Progress reporting might race with the promise that WaitOnPromise is called
165 // This will check for a valid progress transition with assertions.
166 lookup
->value().SetProgress(aProgress
);
168 if (aProgress
== Progress::ShutdownCompleted
) {
169 mShutdownStates
.remove(lookup
);
173 ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker()
174 : mState(VariantType
<AcceptingPromises
>()) {
175 AssertIsOnMainThread();
178 ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() {
179 MOZ_ASSERT(!IsAcceptingPromises());
180 MOZ_ASSERT(!GetPendingPromises());
181 MOZ_ASSERT(!mShutdownClient
);
184 void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() {
185 AssertIsOnMainThread();
187 if (!mShutdownClient
|| IsAcceptingPromises() || GetPendingPromises()) {
194 void ServiceWorkerShutdownBlocker::UnblockShutdown() {
195 MOZ_ASSERT(mShutdownClient
);
197 mShutdownClient
->RemoveBlocker(this);
198 mShutdownClient
= nullptr;
205 uint32_t ServiceWorkerShutdownBlocker::PromiseSettled() {
206 AssertIsOnMainThread();
207 MOZ_ASSERT(GetPendingPromises());
209 if (IsAcceptingPromises()) {
210 return --mState
.as
<AcceptingPromises
>().mPendingPromises
;
213 return --mState
.as
<NotAcceptingPromises
>().mPendingPromises
;
216 bool ServiceWorkerShutdownBlocker::IsAcceptingPromises() const {
217 AssertIsOnMainThread();
219 return mState
.is
<AcceptingPromises
>();
222 uint32_t ServiceWorkerShutdownBlocker::GetPendingPromises() const {
223 AssertIsOnMainThread();
225 if (IsAcceptingPromises()) {
226 return mState
.as
<AcceptingPromises
>().mPendingPromises
;
229 return mState
.as
<NotAcceptingPromises
>().mPendingPromises
;
232 ServiceWorkerShutdownBlocker::NotAcceptingPromises::NotAcceptingPromises(
233 AcceptingPromises aPreviousState
)
234 : mPendingPromises(aPreviousState
.mPendingPromises
) {
235 AssertIsOnMainThread();
238 NS_IMETHODIMP
ServiceWorkerShutdownBlocker::Notify(nsITimer
*) {
239 // TODO: this method being called indicates that there are ServiceWorkers
240 // that did not complete shutdown before the timer expired - there should be
241 // a telemetry ping or some other way of recording the state of when this
242 // happens (e.g. what's returned by GetState()).
247 void ServiceWorkerShutdownBlocker::MaybeInitUnblockShutdownTimer() {
248 #ifdef RELEASE_OR_BETA
249 AssertIsOnMainThread();
252 if (!mShutdownClient
|| IsAcceptingPromises()) {
256 MOZ_ASSERT(GetPendingPromises(),
257 "Shouldn't be blocking shutdown with zero pending promises.");
259 using namespace std::chrono_literals
;
261 static constexpr auto delay
=
262 std::chrono::duration_cast
<std::chrono::milliseconds
>(10s
);
264 mTimer
= NS_NewTimer();
266 mTimer
->InitWithCallback(this, delay
.count(), nsITimer::TYPE_ONE_SHOT
);
271 } // namespace mozilla