Bug 1226441 - Part 2: Delay functional event dispatch until service worker is activated; r=catalinb
authorBen Kelly <ben@wanderview.com>
Tue, 24 Nov 2015 10:47:59 -0500
changeset 273974 671bc38157d290576fa910484289be8f66bbccd6
parent 273973 d0823da696baf3a06db1c3c780cd6b40fa25a5fa
child 273975 f060b308ec40364c4236070cf933ef1368ce10c4
push id29715
push userkwierso@gmail.com
push dateTue, 24 Nov 2015 21:54:25 +0000
treeherdermozilla-central@d9243e369c22 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscatalinb
bugs1226441
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1226441 - Part 2: Delay functional event dispatch until service worker is activated; r=catalinb
dom/workers/ServiceWorkerManager.cpp
dom/workers/ServiceWorkerManager.h
dom/workers/ServiceWorkerPrivate.cpp
dom/workers/ServiceWorkerPrivate.h
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -4596,28 +4596,36 @@ private:
   ServiceWorkerState mState;
 };
 
 }
 
 void
 ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
 {
+  AssertIsOnMainThread();
 #ifdef DEBUG
   // Any state can directly transition to redundant, but everything else is
   // ordered.
   if (aState != ServiceWorkerState::Redundant) {
     MOZ_ASSERT_IF(mState == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating);
     MOZ_ASSERT_IF(mState == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated);
   }
   // Activated can only go to redundant.
   MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
 #endif
+  // Flush any pending functional events to the worker when it transitions to the
+  // activated state.
+  // TODO: Do we care that these events will race with the propagation of the
+  //       state change?
+  if (aState == ServiceWorkerState::Activated && mState != aState) {
+    mServiceWorkerPrivate->Activated();
+  }
   mState = aState;
   nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r.forget())));
 }
 
 ServiceWorkerInfo::ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,
                                      const nsACString& aScriptSpec,
                                      const nsAString& aCacheName)
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -278,16 +278,17 @@ public:
 
   void
   UpdateState(ServiceWorkerState aState);
 
   // Only used to set initial state when loading from disk!
   void
   SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
   {
+    AssertIsOnMainThread();
     mState = aState;
   }
 
   void
   AppendWorker(ServiceWorker* aWorker);
 
   void
   RemoveWorker(ServiceWorker* aWorker);
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -585,16 +585,24 @@ ServiceWorkerPrivate::SendPushEvent(cons
 
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
     new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
 
   RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
                                                          mKeepAliveToken,
                                                          aData,
                                                          regInfo);
+
+  if (mInfo->State() == ServiceWorkerState::Activating) {
+    mPendingFunctionalEvents.AppendElement(r.forget());
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
+
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 #endif // MOZ_SIMPLEPUSH
@@ -1052,16 +1060,27 @@ public:
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     return DispatchFetchEvent(aCx, aWorkerPrivate);
   }
 
+  NS_IMETHOD
+  Cancel() override
+  {
+    nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
+    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+      NS_WARNING("Failed to resume channel on FetchEventRunnable::Cancel()!\n");
+    }
+    WorkerRunnable::Cancel();
+    return NS_OK;
+  }
+
 private:
   ~FetchEventRunnable() {}
 
   class ResumeRequest final : public nsRunnable {
     nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   public:
     explicit ResumeRequest(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel)
       : mChannel(aChannel)
@@ -1241,16 +1260,18 @@ NS_IMPL_ISUPPORTS_INHERITED(FetchEventRu
 } // anonymous namespace
 
 nsresult
 ServiceWorkerPrivate::SendFetchEvent(nsIInterceptedChannel* aChannel,
                                      nsILoadGroup* aLoadGroup,
                                      UniquePtr<ServiceWorkerClientInfo>&& aClientInfo,
                                      bool aIsReload)
 {
+  AssertIsOnMainThread();
+
   // if the ServiceWorker script fails to load for some reason, just resume
   // the original channel.
   nsCOMPtr<nsIRunnable> failRunnable =
     NS_NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception);
 
   nsresult rv = SpawnWorkerIfNeeded(FetchEvent, failRunnable, aLoadGroup);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1274,16 +1295,23 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
     new FetchEventRunnable(mWorkerPrivate, mKeepAliveToken, handle,
                            mInfo->ScriptSpec(), regInfo,
                            Move(aClientInfo), aIsReload);
   rv = r->Init();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  if (mInfo->State() == ServiceWorkerState::Activating) {
+    mPendingFunctionalEvents.AppendElement(r.forget());
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
+
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
@@ -1420,16 +1448,25 @@ ServiceWorkerPrivate::TerminateWorker()
       }
     }
 
     AutoJSAPI jsapi;
     jsapi.Init();
     NS_WARN_IF(!mWorkerPrivate->Terminate(jsapi.cx()));
     mWorkerPrivate = nullptr;
     mSupportsArray.Clear();
+
+    // Any pending events are never going to fire on this worker.  Cancel
+    // them so that intercepted channels can be reset and other resources
+    // cleaned up.
+    nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
+    mPendingFunctionalEvents.SwapElements(pendingEvents);
+    for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
+      pendingEvents[i]->Cancel();
+    }
   }
 }
 
 void
 ServiceWorkerPrivate::NoteDeadServiceWorkerInfo()
 {
   AssertIsOnMainThread();
   mInfo = nullptr;
@@ -1442,16 +1479,39 @@ ServiceWorkerPrivate::NoteStoppedControl
   AssertIsOnMainThread();
   if (mIsPushWorker) {
     return;
   }
 
   TerminateWorker();
 }
 
+void
+ServiceWorkerPrivate::Activated()
+{
+  AssertIsOnMainThread();
+
+  // If we had to queue up events due to the worker activating, that means
+  // the worker must be currently running.  We should be called synchronously
+  // when the worker becomes activated.
+  MOZ_ASSERT_IF(!mPendingFunctionalEvents.IsEmpty(), mWorkerPrivate);
+
+  nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
+  mPendingFunctionalEvents.SwapElements(pendingEvents);
+
+  for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
+    RefPtr<WorkerRunnable> r = pendingEvents[i].forget();
+    AutoJSAPI jsapi;
+    jsapi.Init();
+    if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
+      NS_WARNING("Failed to dispatch pending functional event!");
+    }
+  }
+}
+
 /* static */ void
 ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aPrivate);
 
   RefPtr<ServiceWorkerPrivate> swp = static_cast<ServiceWorkerPrivate*>(aPrivate);
 
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -129,16 +129,19 @@ public:
   NoteStoppedControllingDocuments();
 
   WorkerPrivate*
   GetWorkerPrivate() const
   {
     return mWorkerPrivate;
   }
 
+  void
+  Activated();
+
 private:
   enum WakeUpReason {
     FetchEvent = 0,
     PushEvent,
     PushSubscriptionChangeEvent,
     MessageEvent,
     NotificationClickEvent,
     LifeCycleEvent
@@ -192,15 +195,19 @@ private:
 
   uint64_t mTokenCount;
 
   // Meant for keeping objects alive while handling requests from the worker
   // on the main thread. Access to this array is provided through
   // |StoreISupports| and |RemoveISupports|. Note that the array is also
   // cleared whenever the worker is terminated.
   nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
+
+  // Array of function event worker runnables that are pending due to
+  // the worker activating.  Main thread only.
+  nsTArray<RefPtr<WorkerRunnable>> mPendingFunctionalEvents;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkerprivate_h