Bug 1493204 - Add pushed streams to the priority tree. r=dragana
authorNicholas Hurley <hurley@mozilla.com>
Wed, 03 Oct 2018 09:40:23 +0000
changeset 495143 328a8076c760f5d152b2fdf4607530e32fc09349
parent 495142 c5b037dfba7e9863efea887e6e9bb970ce3102e3
child 495144 347e93fd4d032db27bd760140cee7dfd3ab5e6d4
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs1493204
milestone64.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 1493204 - Add pushed streams to the priority tree. r=dragana Previously, we had not put pushed streams in the priority tree, we just let them be top-level items in the tree. With this change, we will put them into the tree initially based on the priority of the associated stream. The only exception is if the associated stream is either a Leader or Urgent Start (in which case, we will turn the pushed streams into followers). Once the pushed stream is matched with a request generated by gecko, that pushed stream will be re-prioritized based on the priority gecko has for the request, just like a regular pulled stream. This also allows us to re-prioritize pushed streams into the background on tab switch (we assume that, before they are matched, they belong to the same window as the associated stream). Differential Revision: https://phabricator.services.mozilla.com/D7223
netwerk/protocol/http/Http2Push.cpp
netwerk/protocol/http/Http2Push.h
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Stream.cpp
netwerk/protocol/http/Http2Stream.h
--- a/netwerk/protocol/http/Http2Push.cpp
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -76,17 +76,28 @@ Http2PushedStream::Http2PushedStream(Htt
   , mOnPushFailed(false)
 {
   LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
   mStreamID = aID;
   MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream
   mBufferedPush->SetPushStream(this);
   mRequestContext = aAssociatedStream->RequestContext();
   mLastRead = TimeStamp::Now();
-  SetPriority(aAssociatedStream->Priority() + 1);
+  mPriorityDependency = aAssociatedStream->PriorityDependency();
+  if (mPriorityDependency == Http2Session::kUrgentStartGroupID ||
+      mPriorityDependency == Http2Session::kLeaderGroupID) {
+    mPriorityDependency = Http2Session::kFollowerGroupID;
+  }
+  // Cache this for later use in case of tab switch.
+  mDefaultPriorityDependency = mPriorityDependency;
+  SetPriorityDependency(aAssociatedStream->Priority() + 1, mPriorityDependency);
+  // Assume we are on the same tab as our associated stream, for priority purposes.
+  // It's possible this could change when we get paired with a sink, but it's unlikely
+  // and doesn't much matter anyway.
+  mTransactionTabId = aAssociatedStream->TransactionTabId();
 }
 
 bool
 Http2PushedStream::GetPushComplete()
 {
   return mPushCompleted;
 }
 
@@ -305,16 +316,46 @@ Http2PushedStream::GetBufferedData(char 
     return rv;
 
   if (!*countWritten)
     rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
 
   return rv;
 }
 
+void
+Http2PushedStream::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
+{
+  if (mConsumerStream) {
+    // Pass through to our sink, who will handle things appropriately.
+    mConsumerStream->TopLevelOuterContentWindowIdChangedInternal(windowId);
+    return;
+  }
+
+  MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+  mCurrentForegroundTabOuterContentWindowId = windowId;
+
+  if (!mSession->UseH2Deps()) {
+    return;
+  }
+
+  uint32_t oldDependency = mPriorityDependency;
+  if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) {
+    mPriorityDependency = Http2Session::kBackgroundGroupID;
+    nsHttp::NotifyActiveTabLoadOptimization();
+  } else {
+    mPriorityDependency = mDefaultPriorityDependency;
+  }
+
+  if (mPriorityDependency != oldDependency) {
+    mSession->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
+  }
+}
+
 //////////////////////////////////////////
 // Http2PushTransactionBuffer
 // This is the nsAHttpTransction owned by the stream when the pushed
 // stream has not yet been matched with a pull request
 //////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)
 
--- a/netwerk/protocol/http/Http2Push.h
+++ b/netwerk/protocol/http/Http2Push.h
@@ -64,16 +64,17 @@ public:
   void OnPushFailed() { mDeferCleanupOnPush = false; mOnPushFailed = true; }
 
   MOZ_MUST_USE nsresult GetBufferedData(char *buf, uint32_t count,
                                         uint32_t *countWritten);
 
   // overload of Http2Stream
   virtual bool HasSink() override { return !!mConsumerStream; }
   virtual void SetPushComplete() override { mPushCompleted = true; }
+  virtual void TopLevelOuterContentWindowIdChanged(uint64_t) override;
 
   nsCString &GetRequestString() { return mRequestString; }
 
 private:
 
   Http2Stream *mConsumerStream; // paired request stream that consumes from
                                 // real http/2 one.. null until a match is made.
 
@@ -94,16 +95,17 @@ private:
   // when we need to do OnPush() on another thread and the time it takes
   // for that event to create a synthetic pull stream attached to this
   // object. That synthetic pull will become mConsuemerStream.
   // Ths is essentially a delete protecting reference.
   bool mDeferCleanupOnPush;
   bool mOnPushFailed;
   nsCString mRequestString;
 
+  uint32_t mDefaultPriorityDependency;
 };
 
 class Http2PushTransactionBuffer final : public nsAHttpTransaction
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSAHTTPTRANSACTION
 
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -1528,21 +1528,21 @@ Http2Session::RecvPriority(Http2Session 
   if (NS_FAILED(rv))
     return rv;
 
   uint32_t newPriorityDependency = NetworkEndian::readUint32(
       self->mInputFrameBuffer.get() + kFrameHeaderBytes);
   bool exclusive = !!(newPriorityDependency & 0x80000000);
   newPriorityDependency &= 0x7fffffff;
   uint8_t newPriorityWeight = *(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4);
-  if (self->mInputFrameDataStream) {
-    self->mInputFrameDataStream->SetPriorityDependency(newPriorityDependency,
-                                                       newPriorityWeight,
-                                                       exclusive);
-  }
+
+  // undefined what it means when the server sends a priority frame. ignore it.
+  LOG3(("Http2Session::RecvPriority %p 0x%X received dependency=0x%X "
+        "weight=%u exclusive=%d", self->mInputFrameDataStream, self->mInputFrameID,
+        newPriorityDependency, newPriorityWeight, exclusive));
 
   self->ResetDownstreamState();
   return NS_OK;
 }
 
 nsresult
 Http2Session::RecvRstStream(Http2Session *self)
 {
@@ -1977,20 +1977,19 @@ Http2Session::RecvPushPromise(Http2Sessi
         LOG3(("Http2Session::RecvPushPromise %p failed to open cache entry for push check", self));
       }
     }
   }
 
   pushedStream->SetHTTPState(Http2Stream::RESERVED_BY_REMOTE);
   static_assert(Http2Stream::kWorstPriority >= 0,
                 "kWorstPriority out of range");
-  uint8_t priorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
-    (Http2Stream::kWorstPriority - Http2Stream::kNormalPriority);
-  pushedStream->SetPriority(Http2Stream::kWorstPriority);
-  self->GeneratePriority(promisedID, priorityWeight);
+  uint32_t priorityDependency = pushedStream->PriorityDependency();
+  uint8_t priorityWeight = pushedStream->PriorityWeight();
+  self->SendPriorityFrame(promisedID, priorityDependency, priorityWeight);
   self->ResetDownstreamState();
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(Http2Session::CachePushCheckCallback, nsICacheEntryOpenCallback);
 
 Http2Session::CachePushCheckCallback::CachePushCheckCallback(Http2Session *session, uint32_t promisedID, const nsACString &requestString)
   :mPromisedID(promisedID)
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -46,16 +46,18 @@ Http2Stream::Http2Stream(nsAHttpTransact
   , mSegmentWriter(nullptr)
   , mUpstreamState(GENERATING_HEADERS)
   , mState(IDLE)
   , mRequestHeadersDone(0)
   , mOpenGenerated(0)
   , mAllHeadersReceived(0)
   , mQueued(0)
   , mSocketTransport(session->SocketTransport())
+  , mCurrentForegroundTabOuterContentWindowId(windowId)
+  , mTransactionTabId(0)
   , mTransaction(httpTransaction)
   , mChunkSize(session->SendingChunkSize())
   , mRequestBlockedOnRead(0)
   , mRecvdFin(0)
   , mReceivedData(0)
   , mRecvdReset(0)
   , mSentReset(0)
   , mCountAsActive(0)
@@ -68,18 +70,16 @@ Http2Stream::Http2Stream(nsAHttpTransact
   , mTxStreamFrameSize(0)
   , mRequestBodyLenRemaining(0)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
   , mPushSource(nullptr)
   , mAttempting0RTT(false)
-  , mCurrentForegroundTabOuterContentWindowId(windowId)
-  , mTransactionTabId(0)
   , mIsTunnel(false)
   , mPlainTextTunnel(false)
 {
   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
   nsHttpTransaction *trans = mTransaction->QueryHttpTransaction();
   LOG3(("Http2Stream::Http2Stream %p trans=%p atrans=%p", this, trans, httpTransaction));
 
@@ -784,31 +784,35 @@ Http2Stream::AdjustPushedPriority()
 
   MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1));
 
   // If the pushed stream has recvd a FIN, there is no reason to update
   // the window
   if (mPushSource->RecvdFin() || mPushSource->RecvdReset())
     return;
 
+  // Ensure we pick up the right dependency to place the pushed stream under.
+  UpdatePriorityDependency();
+
   EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + Http2Session::kFrameHeaderBytes + 5,
                mTxInlineFrameUsed, mTxInlineFrameSize);
   uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
   mTxInlineFrameUsed += Http2Session::kFrameHeaderBytes + 5;
 
   mSession->CreateFrameHeader(packet, 5,
                               Http2Session::FRAME_TYPE_PRIORITY, 0,
                               mPushSource->mStreamID);
 
-  mPushSource->SetPriority(mPriority);
-  memset(packet + Http2Session::kFrameHeaderBytes, 0, 4);
+  mPushSource->SetPriorityDependency(mPriority, mPriorityDependency);
+  uint32_t wireDep = PR_htonl(mPriorityDependency);
+  memcpy(packet + Http2Session::kFrameHeaderBytes, &wireDep, 4);
   memcpy(packet + Http2Session::kFrameHeaderBytes + 4, &mPriorityWeight, 1);
 
-  LOG3(("AdjustPushedPriority %p id 0x%X to weight %X\n", this, mPushSource->mStreamID,
-        mPriorityWeight));
+  LOG3(("AdjustPushedPriority %p id 0x%X to dep %X weight %X\n", this, mPushSource->mStreamID,
+        mPriorityDependency, mPriorityWeight));
 }
 
 void
 Http2Stream::UpdateTransportReadEvents(uint32_t count)
 {
   mTotalRead += count;
   if (!mSocketTransport) {
     return;
@@ -1236,23 +1240,20 @@ Http2Stream::SetPriority(uint32_t newPri
   mPriority = static_cast<uint32_t>(httpPriority);
   mPriorityWeight = (nsISupportsPriority::PRIORITY_LOWEST + 1) -
     (httpPriority - kNormalPriority);
 
   mPriorityDependency = 0; // maybe adjusted later
 }
 
 void
-Http2Stream::SetPriorityDependency(uint32_t newDependency, uint8_t newWeight,
-                                   bool exclusive)
+Http2Stream::SetPriorityDependency(uint32_t newPriority, uint32_t newDependency)
 {
-  // undefined what it means when the server sends a priority frame. ignore it.
-  LOG3(("Http2Stream::SetPriorityDependency %p 0x%X received dependency=0x%X "
-        "weight=%u exclusive=%d", this, mStreamID, newDependency, newWeight,
-        exclusive));
+  SetPriority(newPriority);
+  mPriorityDependency = newDependency;
 }
 
 static uint32_t
 GetPriorityDependencyFromTransaction(nsHttpTransaction *trans)
 {
   MOZ_ASSERT(trans);
 
   uint32_t classFlags = trans->ClassOfService();
@@ -1328,16 +1329,29 @@ Http2Stream::UpdatePriorityDependency()
   LOG3(("Http2Stream::UpdatePriorityDependency %p "
         "depends on stream 0x%X\n",
         this, mPriorityDependency));
 }
 
 void
 Http2Stream::TopLevelOuterContentWindowIdChanged(uint64_t windowId)
 {
+  if (!mStreamID) {
+    // For pushed streams, we ignore the direct call from the session and
+    // instead let it come to the internal function from the pushed stream, so
+    // we don't accidentally send two PRIORITY frames for the same stream.
+    return;
+  }
+
+  TopLevelOuterContentWindowIdChangedInternal(windowId);
+}
+
+void
+Http2Stream::TopLevelOuterContentWindowIdChangedInternal(uint64_t windowId)
+{
   MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
 
   LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged "
         "%p windowId=%" PRIx64 "\n",
         this, windowId));
 
   mCurrentForegroundTabOuterContentWindowId = windowId;
 
@@ -1363,18 +1377,22 @@ Http2Stream::TopLevelOuterContentWindowI
       return;
     }
 
     mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
     LOG3(("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
           "depends on stream 0x%X\n", this, mPriorityDependency));
   }
 
-  if (mStreamID) {
-    mSession->SendPriorityFrame(mStreamID, mPriorityDependency, mPriorityWeight);
+  uint32_t modifyStreamID = mStreamID;
+  if (!modifyStreamID && mPushSource) {
+    modifyStreamID = mPushSource->StreamID();
+  }
+  if (modifyStreamID) {
+    mSession->SendPriorityFrame(modifyStreamID, mPriorityDependency, mPriorityWeight);
   }
 }
 
 void
 Http2Stream::SetRecvdFin(bool aStatus)
 {
   mRecvdFin = aStatus ? 1 : 0;
   if (!aStatus)
--- a/netwerk/protocol/http/Http2Stream.h
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -143,20 +143,24 @@ public:
   }
 
   uint64_t LocalUnAcked();
   int64_t  ClientReceiveWindow()  { return mClientReceiveWindow; }
 
   bool     BlockedOnRwin() { return mBlockedOnRwin; }
 
   uint32_t Priority() { return mPriority; }
+  uint32_t PriorityDependency() { return mPriorityDependency; }
+  uint8_t PriorityWeight() { return mPriorityWeight; }
   void SetPriority(uint32_t);
-  void SetPriorityDependency(uint32_t, uint8_t, bool);
+  void SetPriorityDependency(uint32_t, uint32_t);
   void UpdatePriorityDependency();
 
+  uint64_t TransactionTabId() { return mTransactionTabId; }
+
   // A pull stream has an implicit sink, a pushed stream has a sink
   // once it is matched to a pull stream.
   virtual bool HasSink() { return true; }
 
   // This is a no-op on pull streams. Pushed streams override this.
   virtual void SetPushComplete() { };
 
   virtual ~Http2Stream();
@@ -171,17 +175,18 @@ public:
                                              nsCOMPtr<nsIURI> &url);
 
   // Mirrors nsAHttpTransaction
   bool Do0RTT();
   nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored);
 
   nsresult GetOriginAttributes(mozilla::OriginAttributes *oa);
 
-  void TopLevelOuterContentWindowIdChanged(uint64_t windowId);
+  virtual void TopLevelOuterContentWindowIdChanged(uint64_t windowId);
+  void TopLevelOuterContentWindowIdChangedInternal(uint64_t windowId); // For use by pushed streams only
 
 protected:
   static void CreatePushHashKey(const nsCString &scheme,
                                 const nsCString &hostHeader,
                                 const mozilla::OriginAttributes &originAttributes,
                                 uint64_t serial,
                                 const nsACString& pathInfo,
                                 nsCString &outOrigin,
@@ -236,16 +241,21 @@ protected:
   void     ChangeState(enum upstreamStateType);
 
   virtual void AdjustInitialWindow();
   MOZ_MUST_USE nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
 
   // The underlying socket transport object is needed to propogate some events
   nsISocketTransport         *mSocketTransport;
 
+  uint8_t mPriorityWeight; // h2 weight
+  uint32_t mPriorityDependency; // h2 stream id this one depends on
+  uint64_t mCurrentForegroundTabOuterContentWindowId;
+  uint64_t mTransactionTabId;
+
 private:
   friend class nsAutoPtr<Http2Stream>;
 
   MOZ_MUST_USE nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
   MOZ_MUST_USE nsresult GenerateOpen();
 
   void     AdjustPushedPriority();
   void     GenerateDataFrameHeader(uint32_t, bool);
@@ -313,18 +323,16 @@ private:
   // place the fin flag on the last data packet instead of waiting
   // for a stream closed indication. Relying on stream close results
   // in an extra 0-length runt packet and seems to have some interop
   // problems with the google servers. Connect does rely on stream
   // close by setting this to the max value.
   int64_t                      mRequestBodyLenRemaining;
 
   uint32_t                     mPriority; // geckoish weight
-  uint32_t                     mPriorityDependency; // h2 stream id 3 - 0xb
-  uint8_t                      mPriorityWeight; // h2 weight
 
   // mClientReceiveWindow, mServerReceiveWindow, and mLocalUnacked are for flow control.
   // *window are signed because the race conditions in asynchronous SETTINGS
   // messages can force them temporarily negative.
 
   // mClientReceiveWindow is how much data the server will send without getting a
   //   window update
   int64_t                      mClientReceiveWindow;
@@ -350,20 +358,16 @@ private:
   Http2PushedStream *mPushSource;
 
   // Used to store stream data when the transaction channel cannot keep up
   // and flow control has not yet kicked in.
   SimpleBuffer mSimpleBuffer;
 
   bool mAttempting0RTT;
 
-  uint64_t mCurrentForegroundTabOuterContentWindowId;
-
-  uint64_t mTransactionTabId;
-
 /// connect tunnels
 public:
   bool IsTunnel() { return mIsTunnel; }
 private:
   void ClearTransactionsBlockedOnTunnel();
   void MapStreamToPlainText();
   void MapStreamToHttpConnection();