ServiceWorkerShutdownBlocker.cpp (8340B)
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/. */ 6 7 #include "ServiceWorkerShutdownBlocker.h" 8 9 #include <chrono> 10 #include <utility> 11 12 #include "MainThreadUtils.h" 13 #include "ServiceWorkerManager.h" 14 #include "mozilla/Assertions.h" 15 #include "mozilla/RefPtr.h" 16 #include "nsComponentManagerUtils.h" 17 #include "nsDebug.h" 18 #include "nsError.h" 19 #include "nsIWritablePropertyBag2.h" 20 #include "nsThreadUtils.h" 21 22 namespace mozilla::dom { 23 24 NS_IMPL_ISUPPORTS(ServiceWorkerShutdownBlocker, nsIAsyncShutdownBlocker, 25 nsITimerCallback, nsINamed) 26 27 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsAString& aNameOut) { 28 aNameOut = nsLiteralString( 29 u"ServiceWorkerShutdownBlocker: shutting down Service Workers"); 30 return NS_OK; 31 } 32 33 // nsINamed implementation 34 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetName(nsACString& aNameOut) { 35 aNameOut.AssignLiteral("ServiceWorkerShutdownBlocker"); 36 return NS_OK; 37 } 38 39 NS_IMETHODIMP 40 ServiceWorkerShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aClient) { 41 AssertIsOnMainThread(); 42 MOZ_ASSERT(!mShutdownClient); 43 MOZ_ASSERT(mServiceWorkerManager); 44 45 mShutdownClient = aClient; 46 47 (*mServiceWorkerManager)->MaybeStartShutdown(); 48 mServiceWorkerManager.destroy(); 49 50 MaybeUnblockShutdown(); 51 MaybeInitUnblockShutdownTimer(); 52 53 return NS_OK; 54 } 55 56 NS_IMETHODIMP ServiceWorkerShutdownBlocker::GetState(nsIPropertyBag** aBagOut) { 57 AssertIsOnMainThread(); 58 MOZ_ASSERT(aBagOut); 59 60 nsCOMPtr<nsIWritablePropertyBag2> propertyBag = 61 do_CreateInstance("@mozilla.org/hash-property-bag;1"); 62 63 if (NS_WARN_IF(!propertyBag)) { 64 return NS_ERROR_OUT_OF_MEMORY; 65 } 66 67 nsresult rv = propertyBag->SetPropertyAsBool(u"acceptingPromises"_ns, 68 IsAcceptingPromises()); 69 70 if (NS_WARN_IF(NS_FAILED(rv))) { 71 return rv; 72 } 73 74 rv = propertyBag->SetPropertyAsUint32(u"pendingPromises"_ns, 75 GetPendingPromises()); 76 77 if (NS_WARN_IF(NS_FAILED(rv))) { 78 return rv; 79 } 80 81 nsAutoCString shutdownStates; 82 83 for (auto iter = mShutdownStates.iter(); !iter.done(); iter.next()) { 84 shutdownStates.Append(iter.get().value().GetProgressString()); 85 shutdownStates.Append(", "); 86 } 87 88 rv = propertyBag->SetPropertyAsACString(u"shutdownStates"_ns, shutdownStates); 89 90 if (NS_WARN_IF(NS_FAILED(rv))) { 91 return rv; 92 } 93 94 propertyBag.forget(aBagOut); 95 96 return NS_OK; 97 } 98 99 /* static */ already_AddRefed<ServiceWorkerShutdownBlocker> 100 ServiceWorkerShutdownBlocker::CreateAndRegisterOn( 101 nsIAsyncShutdownClient& aShutdownBarrier, 102 ServiceWorkerManager& aServiceWorkerManager) { 103 AssertIsOnMainThread(); 104 105 RefPtr<ServiceWorkerShutdownBlocker> blocker = 106 new ServiceWorkerShutdownBlocker(aServiceWorkerManager); 107 108 nsresult rv = aShutdownBarrier.AddBlocker( 109 blocker.get(), NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, 110 u"Service Workers shutdown"_ns); 111 112 if (NS_WARN_IF(NS_FAILED(rv))) { 113 return nullptr; 114 } 115 116 return blocker.forget(); 117 } 118 119 void ServiceWorkerShutdownBlocker::WaitOnPromise( 120 GenericNonExclusivePromise* aPromise, uint32_t aShutdownStateId) { 121 AssertIsOnMainThread(); 122 MOZ_DIAGNOSTIC_ASSERT(IsAcceptingPromises()); 123 MOZ_ASSERT(aPromise); 124 MOZ_ASSERT(mShutdownStates.has(aShutdownStateId)); 125 126 ++mState.as<AcceptingPromises>().mPendingPromises; 127 128 RefPtr<ServiceWorkerShutdownBlocker> self = this; 129 130 aPromise->Then(GetCurrentSerialEventTarget(), __func__, 131 [self = std::move(self), shutdownStateId = aShutdownStateId]( 132 const GenericNonExclusivePromise::ResolveOrRejectValue&) { 133 // Progress reporting might race with aPromise settling. 134 self->mShutdownStates.remove(shutdownStateId); 135 136 if (!self->PromiseSettled()) { 137 self->MaybeUnblockShutdown(); 138 } 139 }); 140 } 141 142 void ServiceWorkerShutdownBlocker::StopAcceptingPromises() { 143 AssertIsOnMainThread(); 144 MOZ_ASSERT(IsAcceptingPromises()); 145 146 mState = AsVariant(NotAcceptingPromises(mState.as<AcceptingPromises>())); 147 148 MaybeUnblockShutdown(); 149 MaybeInitUnblockShutdownTimer(); 150 } 151 152 uint32_t ServiceWorkerShutdownBlocker::CreateShutdownState() { 153 AssertIsOnMainThread(); 154 155 static uint32_t nextShutdownStateId = 1; 156 157 MOZ_ALWAYS_TRUE(mShutdownStates.putNew(nextShutdownStateId, 158 ServiceWorkerShutdownState())); 159 160 return nextShutdownStateId++; 161 } 162 163 void ServiceWorkerShutdownBlocker::ReportShutdownProgress( 164 uint32_t aShutdownStateId, Progress aProgress) { 165 AssertIsOnMainThread(); 166 MOZ_RELEASE_ASSERT(aShutdownStateId != kInvalidShutdownStateId); 167 168 auto lookup = mShutdownStates.lookup(aShutdownStateId); 169 170 // Progress reporting might race with the promise that WaitOnPromise is called 171 // with settling. 172 if (!lookup) { 173 return; 174 } 175 176 // This will check for a valid progress transition with assertions. 177 lookup->value().SetProgress(aProgress); 178 179 if (aProgress == Progress::ShutdownCompleted) { 180 mShutdownStates.remove(lookup); 181 } 182 } 183 184 ServiceWorkerShutdownBlocker::ServiceWorkerShutdownBlocker( 185 ServiceWorkerManager& aServiceWorkerManager) 186 : mState(VariantType<AcceptingPromises>()), 187 mServiceWorkerManager(WrapNotNull(&aServiceWorkerManager)) { 188 AssertIsOnMainThread(); 189 } 190 191 ServiceWorkerShutdownBlocker::~ServiceWorkerShutdownBlocker() { 192 MOZ_ASSERT(!IsAcceptingPromises()); 193 MOZ_ASSERT(!GetPendingPromises()); 194 MOZ_ASSERT(!mShutdownClient); 195 MOZ_ASSERT(!mServiceWorkerManager); 196 } 197 198 void ServiceWorkerShutdownBlocker::MaybeUnblockShutdown() { 199 AssertIsOnMainThread(); 200 201 if (!mShutdownClient || IsAcceptingPromises() || GetPendingPromises()) { 202 return; 203 } 204 205 UnblockShutdown(); 206 } 207 208 void ServiceWorkerShutdownBlocker::UnblockShutdown() { 209 MOZ_ASSERT(mShutdownClient); 210 211 mShutdownClient->RemoveBlocker(this); 212 mShutdownClient = nullptr; 213 214 if (mTimer) { 215 mTimer->Cancel(); 216 } 217 } 218 219 uint32_t ServiceWorkerShutdownBlocker::PromiseSettled() { 220 AssertIsOnMainThread(); 221 MOZ_ASSERT(GetPendingPromises()); 222 223 if (IsAcceptingPromises()) { 224 return --mState.as<AcceptingPromises>().mPendingPromises; 225 } 226 227 return --mState.as<NotAcceptingPromises>().mPendingPromises; 228 } 229 230 bool ServiceWorkerShutdownBlocker::IsAcceptingPromises() const { 231 AssertIsOnMainThread(); 232 233 return mState.is<AcceptingPromises>(); 234 } 235 236 uint32_t ServiceWorkerShutdownBlocker::GetPendingPromises() const { 237 AssertIsOnMainThread(); 238 239 if (IsAcceptingPromises()) { 240 return mState.as<AcceptingPromises>().mPendingPromises; 241 } 242 243 return mState.as<NotAcceptingPromises>().mPendingPromises; 244 } 245 246 ServiceWorkerShutdownBlocker::NotAcceptingPromises::NotAcceptingPromises( 247 AcceptingPromises aPreviousState) 248 : mPendingPromises(aPreviousState.mPendingPromises) { 249 AssertIsOnMainThread(); 250 } 251 252 NS_IMETHODIMP ServiceWorkerShutdownBlocker::Notify(nsITimer*) { 253 // TODO: this method being called indicates that there are ServiceWorkers 254 // that did not complete shutdown before the timer expired - there should be 255 // a telemetry ping or some other way of recording the state of when this 256 // happens (e.g. what's returned by GetState()). 257 UnblockShutdown(); 258 return NS_OK; 259 } 260 261 #ifdef RELEASE_OR_BETA 262 # define SW_UNBLOCK_SHUTDOWN_TIMER_DURATION 10s 263 #else 264 // In Nightly, we do want a shutdown hang to be reported so we pick a value 265 // notably longer than the 60s of the RunWatchDog timeout. 266 # define SW_UNBLOCK_SHUTDOWN_TIMER_DURATION 200s 267 #endif 268 269 void ServiceWorkerShutdownBlocker::MaybeInitUnblockShutdownTimer() { 270 AssertIsOnMainThread(); 271 272 if (mTimer || !mShutdownClient || IsAcceptingPromises()) { 273 return; 274 } 275 276 MOZ_ASSERT(GetPendingPromises(), 277 "Shouldn't be blocking shutdown with zero pending promises."); 278 279 using namespace std::chrono_literals; 280 281 static constexpr auto delay = 282 std::chrono::duration_cast<std::chrono::milliseconds>( 283 SW_UNBLOCK_SHUTDOWN_TIMER_DURATION); 284 285 mTimer = NS_NewTimer(); 286 287 mTimer->InitWithCallback(this, delay.count(), nsITimer::TYPE_ONE_SHOT); 288 } 289 290 } // namespace mozilla::dom