bug 822456 - spdy stream id out of order r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 18 Dec 2012 17:14:21 -0500
changeset 125556 e2e42afb0b86
parent 125555 1114846b3791
child 125557 2b374ff0f529
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs822456
milestone20.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 822456 - spdy stream id out of order r=honzab
netwerk/protocol/http/SpdySession2.cpp
netwerk/protocol/http/SpdySession2.h
netwerk/protocol/http/SpdySession3.cpp
netwerk/protocol/http/SpdySession3.h
netwerk/protocol/http/SpdyStream2.cpp
netwerk/protocol/http/SpdyStream2.h
netwerk/protocol/http/SpdyStream3.cpp
netwerk/protocol/http/SpdyStream3.h
netwerk/protocol/http/nsAHttpTransaction.h
--- a/netwerk/protocol/http/SpdySession2.cpp
+++ b/netwerk/protocol/http/SpdySession2.cpp
@@ -379,16 +379,26 @@ SpdySession2::NetworkRead(nsAHttpSegment
 void
 SpdySession2::SetWriteCallbacks()
 {
   if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
       mConnection->ResumeSend();
 }
 
 void
+SpdySession2::RealignOutputQueue()
+{
+  mOutputQueueUsed -= mOutputQueueSent;
+  memmove(mOutputQueueBuffer.get(),
+          mOutputQueueBuffer.get() + mOutputQueueSent,
+          mOutputQueueUsed);
+  mOutputQueueSent = 0;
+}
+
+void
 SpdySession2::FlushOutputQueue()
 {
   if (!mSegmentReader || !mOutputQueueUsed)
     return;
   
   nsresult rv;
   uint32_t countRead;
   uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
@@ -411,21 +421,17 @@ SpdySession2::FlushOutputQueue()
 
   mOutputQueueSent += countRead;
 
   // If the output queue is close to filling up and we have sent out a good
   // chunk of data from the beginning then realign it.
   
   if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
       ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
-    mOutputQueueUsed -= mOutputQueueSent;
-    memmove(mOutputQueueBuffer.get(),
-            mOutputQueueBuffer.get() + mOutputQueueSent,
-            mOutputQueueUsed);
-    mOutputQueueSent = 0;
+    RealignOutputQueue();
   }
 }
 
 void
 SpdySession2::DontReuse()
 {
   mShouldGoAway = true;
   if (!mStreamTransactionHash.Count())
@@ -1913,37 +1919,43 @@ SpdySession2::OnReadSegment(const char *
   *countRead = count;
 
   FlushOutputQueue();
 
   return NS_OK;
 }
 
 nsresult
-SpdySession2::CommitToSegmentSize(uint32_t count)
+SpdySession2::CommitToSegmentSize(uint32_t count, bool forceCommitment)
 {
   if (mOutputQueueUsed)
     FlushOutputQueue();
 
   // would there be enough room to buffer this if needed?
   if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
     return NS_OK;
-  
-  // if we are using part of our buffers already, try again later
-  if (mOutputQueueUsed)
+
+  // if we are using part of our buffers already, try again later unless
+  // forceCommitment is set.
+  if (mOutputQueueUsed && !forceCommitment)
     return NS_BASE_STREAM_WOULD_BLOCK;
 
-  // not enough room to buffer even with completely empty buffers.
-  // normal frames are max 4kb, so the only case this can really happen
-  // is a SYN_STREAM with technically unbounded headers. That is highly
-  // unlikely, but possible. Create enough room for it because the buffers
-  // will be necessary - SSL does not absorb writes of very large sizes
-  // in single sends.
+  if (mOutputQueueUsed) {
+    // normally we avoid the memmove of RealignOutputQueue, but we'll try
+    // it if forceCommitment is set before growing the buffer.
+    RealignOutputQueue();
 
-  EnsureBuffer(mOutputQueueBuffer, count + kQueueReserved, 0, mOutputQueueSize);
+    // is there enough room now?
+    if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
+      return NS_OK;
+  }
+
+  // resize the buffers as needed
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + count + kQueueReserved,
+               mOutputQueueUsed, mOutputQueueSize);
 
   NS_ABORT_IF_FALSE((mOutputQueueUsed + count) <=
                     (mOutputQueueSize - kQueueReserved),
                     "buffer not as large as expected");
 
   return NS_OK;
 }
 
--- a/netwerk/protocol/http/SpdySession2.h
+++ b/netwerk/protocol/http/SpdySession2.h
@@ -107,23 +107,23 @@ public:
     SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams
     SETTINGS_TYPE_CWND = 5, // packets
     SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage
     SETTINGS_TYPE_INITIAL_WINDOW = 7  // bytes. Not used in v2.
   };
 
   // This should be big enough to hold all of your control packets,
   // but if it needs to grow for huge headers it can do so dynamically.
-  // About 1% of requests to SPDY google services seem to be > 1000
-  // with all less than 2000.
+  // About 1% of responses from SPDY google services seem to be > 1000
+  // with all less than 2000 when compression is enabled.
   const static uint32_t kDefaultBufferSize = 2048;
 
   // kDefaultQueueSize must be >= other queue size constants
-  const static uint32_t kDefaultQueueSize =  16384;
-  const static uint32_t kQueueMinimumCleanup = 8192;
+  const static uint32_t kDefaultQueueSize =  32768;
+  const static uint32_t kQueueMinimumCleanup = 24576;
   const static uint32_t kQueueTailRoom    =  4096;
   const static uint32_t kQueueReserved    =  1024;
 
   const static uint32_t kDefaultMaxConcurrent = 100;
   const static uint32_t kMaxStreamID = 0x7800000;
   
   // This is a sentinel for a deleted stream. It is not a valid
   // 31 bit stream ID.
@@ -148,17 +148,17 @@ public:
 
   // an overload of nsAHttpConnection
   void TransactionHasDataToWrite(nsAHttpTransaction *);
 
   // a similar version for SpdyStream2
   void TransactionHasDataToWrite(SpdyStream2 *);
 
   // an overload of nsAHttpSegementReader
-  virtual nsresult CommitToSegmentSize(uint32_t size);
+  virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment);
   
   void     PrintDiagnostics (nsCString &log);
 
 private:
 
   enum stateType {
     BUFFERING_FRAME_HEADER,
     BUFFERING_CONTROL_FRAME,
@@ -180,16 +180,17 @@ private:
   void        GeneratePing(uint32_t);
   void        GenerateRstStream(uint32_t, uint32_t);
   void        GenerateGoAway();
   void        CleanupStream(SpdyStream2 *, nsresult, rstReason);
   void        CloseStream(SpdyStream2 *, nsresult);
 
   void        SetWriteCallbacks();
   void        FlushOutputQueue();
+  void        RealignOutputQueue();
 
   bool        RoomForMoreConcurrent();
   void        ActivateStream(SpdyStream2 *);
   void        ProcessPending();
   nsresult    SetInputFrameDataStream(uint32_t);
   bool        VerifyStream(SpdyStream2 *, uint32_t);
   void        SetNeedsCleanup();
 
--- a/netwerk/protocol/http/SpdySession3.cpp
+++ b/netwerk/protocol/http/SpdySession3.cpp
@@ -380,16 +380,26 @@ SpdySession3::NetworkRead(nsAHttpSegment
 void
 SpdySession3::SetWriteCallbacks()
 {
   if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
       mConnection->ResumeSend();
 }
 
 void
+SpdySession3::RealignOutputQueue()
+{
+  mOutputQueueUsed -= mOutputQueueSent;
+  memmove(mOutputQueueBuffer.get(),
+          mOutputQueueBuffer.get() + mOutputQueueSent,
+          mOutputQueueUsed);
+  mOutputQueueSent = 0;
+}
+
+void
 SpdySession3::FlushOutputQueue()
 {
   if (!mSegmentReader || !mOutputQueueUsed)
     return;
   
   nsresult rv;
   uint32_t countRead;
   uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
@@ -412,21 +422,17 @@ SpdySession3::FlushOutputQueue()
 
   mOutputQueueSent += countRead;
 
   // If the output queue is close to filling up and we have sent out a good
   // chunk of data from the beginning then realign it.
   
   if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
       ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
-    mOutputQueueUsed -= mOutputQueueSent;
-    memmove(mOutputQueueBuffer.get(),
-            mOutputQueueBuffer.get() + mOutputQueueSent,
-            mOutputQueueUsed);
-    mOutputQueueSent = 0;
+    RealignOutputQueue();
   }
 }
 
 void
 SpdySession3::DontReuse()
 {
   mShouldGoAway = true;
   if (!mStreamTransactionHash.Count())
@@ -1958,37 +1964,43 @@ SpdySession3::OnReadSegment(const char *
   *countRead = count;
 
   FlushOutputQueue();
 
   return NS_OK;
 }
 
 nsresult
-SpdySession3::CommitToSegmentSize(uint32_t count)
+SpdySession3::CommitToSegmentSize(uint32_t count, bool forceCommitment)
 {
   if (mOutputQueueUsed)
     FlushOutputQueue();
 
   // would there be enough room to buffer this if needed?
   if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
     return NS_OK;
-  
-  // if we are using part of our buffers already, try again later
-  if (mOutputQueueUsed)
+
+  // if we are using part of our buffers already, try again later unless
+  // forceCommitment is set.
+  if (mOutputQueueUsed && !forceCommitment)
     return NS_BASE_STREAM_WOULD_BLOCK;
 
-  // not enough room to buffer even with completely empty buffers.
-  // normal frames are max 4kb, so the only case this can really happen
-  // is a SYN_STREAM with technically unbounded headers. That is highly
-  // unlikely, but possible. Create enough room for it because the buffers
-  // will be necessary - SSL does not absorb writes of very large sizes
-  // in single sends.
+  if (mOutputQueueUsed) {
+    // normally we avoid the memmove of RealignOutputQueue, but we'll try
+    // it if forceCommitment is set before growing the buffer.
+    RealignOutputQueue();
 
-  EnsureBuffer(mOutputQueueBuffer, count + kQueueReserved, 0, mOutputQueueSize);
+    // is there enough room now?
+    if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
+      return NS_OK;
+  }
+
+  // resize the buffers as needed
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + count + kQueueReserved,
+               mOutputQueueUsed, mOutputQueueSize);
 
   NS_ABORT_IF_FALSE((mOutputQueueUsed + count) <=
                     (mOutputQueueSize - kQueueReserved),
                     "buffer not as large as expected");
 
   return NS_OK;
 }
 
--- a/netwerk/protocol/http/SpdySession3.h
+++ b/netwerk/protocol/http/SpdySession3.h
@@ -106,23 +106,23 @@ public:
     SETTINGS_TYPE_CWND = 5, // packets
     SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage
     SETTINGS_TYPE_INITIAL_WINDOW = 7,  // bytes
     SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8
   };
 
   // This should be big enough to hold all of your control packets,
   // but if it needs to grow for huge headers it can do so dynamically.
-  // About 1% of requests to SPDY google services seem to be > 1000
-  // with all less than 2000.
+  // About 1% of responses from SPDY google services seem to be > 1000
+  // with all less than 2000 when compression is enabled.
   const static uint32_t kDefaultBufferSize = 2048;
 
   // kDefaultQueueSize must be >= other queue size constants
-  const static uint32_t kDefaultQueueSize =  16384;
-  const static uint32_t kQueueMinimumCleanup = 8192;
+  const static uint32_t kDefaultQueueSize =  32768;
+  const static uint32_t kQueueMinimumCleanup = 24576;
   const static uint32_t kQueueTailRoom    =  4096;
   const static uint32_t kQueueReserved    =  1024;
 
   const static uint32_t kDefaultMaxConcurrent = 100;
   const static uint32_t kMaxStreamID = 0x7800000;
 
   // This is a sentinel for a deleted stream. It is not a valid
   // 31 bit stream ID.
@@ -157,17 +157,17 @@ public:
 
   // an overload of nsAHttpConnection
   void TransactionHasDataToWrite(nsAHttpTransaction *);
 
   // a similar version for SpdyStream3
   void TransactionHasDataToWrite(SpdyStream3 *);
 
   // an overload of nsAHttpSegementReader
-  virtual nsresult CommitToSegmentSize(uint32_t size);
+  virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment);
   
   uint32_t GetServerInitialWindow() { return mServerInitialWindow; }
 
   void     PrintDiagnostics (nsCString &log);
 
 private:
 
   enum stateType {
@@ -189,16 +189,17 @@ private:
   void        GenerateRstStream(uint32_t, uint32_t);
   void        GenerateGoAway(uint32_t);
   void        CleanupStream(SpdyStream3 *, nsresult, rstReason);
   void        CloseStream(SpdyStream3 *, nsresult);
   void        GenerateSettings();
 
   void        SetWriteCallbacks();
   void        FlushOutputQueue();
+  void        RealignOutputQueue();
 
   bool        RoomForMoreConcurrent();
   void        ActivateStream(SpdyStream3 *);
   void        ProcessPending();
   nsresult    SetInputFrameDataStream(uint32_t);
   bool        VerifyStream(SpdyStream3 *, uint32_t);
   void        SetNeedsCleanup();
 
--- a/netwerk/protocol/http/SpdyStream2.cpp
+++ b/netwerk/protocol/http/SpdyStream2.cpp
@@ -123,45 +123,22 @@ SpdyStream2::ReadSegments(nsAHttpSegment
         ChangeState(SENDING_FIN_STREAM);
         mSession->TransactionHasDataToWrite(this);
         rv = NS_BASE_STREAM_WOULD_BLOCK;
       }
     }
 
     break;
 
-  case SENDING_SYN_STREAM:
-    // We were trying to send the SYN-STREAM but were blocked from trying
-    // to transmit it the first time(s).
-    mSegmentReader = reader;
-    rv = TransmitFrame(nullptr, nullptr);
-    mSegmentReader = nullptr;
-    *countRead = 0;
-    if (NS_SUCCEEDED(rv)) {
-      NS_ABORT_IF_FALSE(!mTxInlineFrameUsed,
-                        "Transmit Frame should be all or nothing");
-    
-      if (mSentFinOnData) {
-        ChangeState(UPSTREAM_COMPLETE);
-        rv = NS_OK;
-      }
-      else {
-        rv = NS_BASE_STREAM_WOULD_BLOCK;
-        ChangeState(GENERATING_REQUEST_BODY);
-        mSession->TransactionHasDataToWrite(this);
-      }
-    }
-    break;
-
   case SENDING_FIN_STREAM:
     // We were trying to send the FIN-STREAM but were blocked from
     // sending it out - try again.
     if (!mSentFinOnData) {
       mSegmentReader = reader;
-      rv = TransmitFrame(nullptr, nullptr);
+      rv = TransmitFrame(nullptr, nullptr, false);
       mSegmentReader = nullptr;
       NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
                         "Transmit Frame should be all or nothing");
       if (NS_SUCCEEDED(rv))
         ChangeState(UPSTREAM_COMPLETE);
     }
     else {
       rv = NS_OK;
@@ -498,17 +475,18 @@ SpdyStream2::UpdateTransportSendEvents(u
     mTransaction->OnTransportStatus(mSocketTransport,
                                     NS_NET_STATUS_WAITING_FOR,
                                     0);
   }
 }
 
 nsresult
 SpdyStream2::TransmitFrame(const char *buf,
-                          uint32_t *countUsed)
+                           uint32_t *countUsed,
+                           bool forceCommitment)
 {
   // If TransmitFrame returns SUCCESS than all the data is sent (or at least
   // buffered at the session level), if it returns WOULD_BLOCK then none of
   // the data is sent.
 
   // You can call this function with no data and no out parameter in order to
   // flush internal buffers that were previously blocked on writing. You can
   // of course feed new data to it as well.
@@ -537,20 +515,24 @@ SpdyStream2::TransmitFrame(const char *b
     memcpy (mTxInlineFrame + mTxInlineFrameUsed,
             buf, mTxStreamFrameSize);
     if (countUsed)
       *countUsed += mTxStreamFrameSize;
     mTxInlineFrameUsed += mTxStreamFrameSize;
     mTxStreamFrameSize = 0;
   }
 
-  rv = mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize +
-                                           mTxInlineFrameUsed);
-  if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+  rv =
+    mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
+                                        forceCommitment);
+
+  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+    NS_ABORT_IF_FALSE(!forceCommitment, "forceCommitment with WOULD_BLOCK");
     mSession->TransactionHasDataToWrite(this);
+  }
   if (NS_FAILED(rv))     // this will include WOULD_BLOCK
     return rv;
 
   // This function calls mSegmentReader->OnReadSegment to report the actual SPDY
   // bytes through to the SpdySession2 and then the HttpConnection which calls
   // the socket write function. It will accept all of the inline and stream
   // data because of the above 'commitment' even if it has to buffer
   
@@ -805,38 +787,25 @@ SpdyStream2::OnReadSegment(const char *b
     rv = ParseHttpRequestHeaders(buf, count, countRead);
     if (NS_FAILED(rv))
       return rv;
     LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
           this, *countRead, count, mSynFrameComplete));
     if (mSynFrameComplete) {
       NS_ABORT_IF_FALSE(mTxInlineFrameUsed,
                         "OnReadSegment SynFrameComplete 0b");
-      rv = TransmitFrame(nullptr, nullptr);
-      NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
-                        "Transmit Frame should be all or nothing");
-
-      // normalize a blocked write into an ok one if we have consumed the data
-      // while parsing headers as some code will take WOULD_BLOCK to mean an
-      // error with nothing processed.
-      // (e.g. nsHttpTransaction::ReadRequestSegment())
-      if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
-        rv = NS_OK;
+      rv = TransmitFrame(nullptr, nullptr, true);
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+        // this can't happen
+        NS_ABORT_IF_FALSE(false,
+                          "Transmit Frame SYN_FRAME must at least buffer data");
+        rv = NS_ERROR_UNEXPECTED;
+      }
 
-      // mTxInlineFrameUsed > 0 means the current frame is in progress
-      // of sending.  mTxInlineFrameUsed is dropped to 0 after both the frame
-      // and its payload (if any) are completely sent out.  Here during
-      // GENERATING_SYN_STREAM state we are sending just the http headers.
-      // Only when the frame is completely sent out do we proceed to
-      // GENERATING_REQUEST_BODY state.
-
-      if (mTxInlineFrameUsed)
-        ChangeState(SENDING_SYN_STREAM);
-      else
-        ChangeState(GENERATING_REQUEST_BODY);
+      ChangeState(GENERATING_REQUEST_BODY);
       break;
     }
     NS_ABORT_IF_FALSE(*countRead == count,
                       "Header parsing not complete but unused data");
     break;
 
   case GENERATING_REQUEST_BODY:
     dataLength = NS_MIN(count, mChunkSize);
@@ -847,17 +816,17 @@ SpdyStream2::OnReadSegment(const char *b
       return NS_ERROR_UNEXPECTED;
     mRequestBodyLenRemaining -= dataLength;
     GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
     ChangeState(SENDING_REQUEST_BODY);
     // NO BREAK
 
   case SENDING_REQUEST_BODY:
     NS_ABORT_IF_FALSE(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
-    rv = TransmitFrame(buf, countRead);
+    rv = TransmitFrame(buf, countRead, false);
     NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
                       "Transmit Frame should be all or nothing");
 
     LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
           "Header is %d Body is %d.",
           rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
 
     // normalize a partial write with a WOULD_BLOCK into just a partial write
@@ -866,20 +835,16 @@ SpdyStream2::OnReadSegment(const char *b
     if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
       rv = NS_OK;
 
     // If that frame was all sent, look for another one
     if (!mTxInlineFrameUsed)
         ChangeState(GENERATING_REQUEST_BODY);
     break;
 
-  case SENDING_SYN_STREAM:
-    rv = NS_BASE_STREAM_WOULD_BLOCK;
-    break;
-
   case SENDING_FIN_STREAM:
     NS_ABORT_IF_FALSE(false,
                       "resuming partial fin stream out of OnReadSegment");
     break;
     
   default:
     NS_ABORT_IF_FALSE(false, "SpdyStream2::OnReadSegment non-write state");
     break;
--- a/netwerk/protocol/http/SpdyStream2.h
+++ b/netwerk/protocol/http/SpdyStream2.h
@@ -65,30 +65,29 @@ private:
   // a SpdyStream2 object is only destroyed by being removed from the
   // SpdySession2 mStreamTransactionHash - make the dtor private to
   // just the AutoPtr implementation needed for that hash.
   friend class nsAutoPtr<SpdyStream2>;
   ~SpdyStream2();
 
   enum stateType {
     GENERATING_SYN_STREAM,
-    SENDING_SYN_STREAM,
     GENERATING_REQUEST_BODY,
     SENDING_REQUEST_BODY,
     SENDING_FIN_STREAM,
     UPSTREAM_COMPLETE
   };
 
   static PLDHashOperator hdrHashEnumerate(const nsACString &,
                                           nsAutoPtr<nsCString> &,
                                           void *);
 
   void     ChangeState(enum stateType);
   nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
-  nsresult TransmitFrame(const char *, uint32_t *);
+  nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
   void     GenerateDataFrameHeader(uint32_t, bool);
 
   void     CompressToFrame(const nsACString &);
   void     CompressToFrame(const nsACString *);
   void     CompressToFrame(const char *, uint32_t);
   void     CompressToFrame(uint16_t);
   void     CompressFlushFrame();
   void     ExecuteCompress(uint32_t);
--- a/netwerk/protocol/http/SpdyStream3.cpp
+++ b/netwerk/protocol/http/SpdyStream3.cpp
@@ -134,45 +134,22 @@ SpdyStream3::ReadSegments(nsAHttpSegment
         GenerateDataFrameHeader(0, true);
         ChangeState(SENDING_FIN_STREAM);
         mSession->TransactionHasDataToWrite(this);
         rv = NS_BASE_STREAM_WOULD_BLOCK;
       }
     }
     break;
 
-  case SENDING_SYN_STREAM:
-    // We were trying to send the SYN-STREAM but were blocked from trying
-    // to transmit it the first time(s).
-    mSegmentReader = reader;
-    rv = TransmitFrame(nullptr, nullptr);
-    mSegmentReader = nullptr;
-    *countRead = 0;
-    if (NS_SUCCEEDED(rv)) {
-      NS_ABORT_IF_FALSE(!mTxInlineFrameUsed,
-                        "Transmit Frame should be all or nothing");
-    
-      if (mSentFinOnData) {
-        ChangeState(UPSTREAM_COMPLETE);
-        rv = NS_OK;
-      }
-      else {
-        rv = NS_BASE_STREAM_WOULD_BLOCK;
-        ChangeState(GENERATING_REQUEST_BODY);
-        mSession->TransactionHasDataToWrite(this);
-      }
-    }
-    break;
-
   case SENDING_FIN_STREAM:
     // We were trying to send the FIN-STREAM but were blocked from
     // sending it out - try again.
     if (!mSentFinOnData) {
       mSegmentReader = reader;
-      rv = TransmitFrame(nullptr, nullptr);
+      rv = TransmitFrame(nullptr, nullptr, false);
       mSegmentReader = nullptr;
       NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
                         "Transmit Frame should be all or nothing");
       if (NS_SUCCEEDED(rv))
         ChangeState(UPSTREAM_COMPLETE);
     }
     else {
       rv = NS_OK;
@@ -518,17 +495,18 @@ SpdyStream3::UpdateTransportSendEvents(u
     mTransaction->OnTransportStatus(mSocketTransport,
                                     NS_NET_STATUS_WAITING_FOR,
                                     0);
   }
 }
 
 nsresult
 SpdyStream3::TransmitFrame(const char *buf,
-                          uint32_t *countUsed)
+                           uint32_t *countUsed,
+                           bool forceCommitment)
 {
   // If TransmitFrame returns SUCCESS than all the data is sent (or at least
   // buffered at the session level), if it returns WOULD_BLOCK then none of
   // the data is sent.
 
   // You can call this function with no data and no out parameter in order to
   // flush internal buffers that were previously blocked on writing. You can
   // of course feed new data to it as well.
@@ -557,20 +535,24 @@ SpdyStream3::TransmitFrame(const char *b
     memcpy (mTxInlineFrame + mTxInlineFrameUsed,
             buf, mTxStreamFrameSize);
     if (countUsed)
       *countUsed += mTxStreamFrameSize;
     mTxInlineFrameUsed += mTxStreamFrameSize;
     mTxStreamFrameSize = 0;
   }
 
-  rv = mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize +
-                                           mTxInlineFrameUsed);
-  if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+  rv =
+    mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
+                                        forceCommitment);
+
+  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+    NS_ABORT_IF_FALSE(!forceCommitment, "forceCommitment with WOULD_BLOCK");
     mSession->TransactionHasDataToWrite(this);
+  }
   if (NS_FAILED(rv))     // this will include WOULD_BLOCK
     return rv;
 
   // This function calls mSegmentReader->OnReadSegment to report the actual SPDY
   // bytes through to the SpdySession3 and then the HttpConnection which calls
   // the socket write function. It will accept all of the inline and stream
   // data because of the above 'commitment' even if it has to buffer
   
@@ -1222,38 +1204,25 @@ SpdyStream3::OnReadSegment(const char *b
     rv = ParseHttpRequestHeaders(buf, count, countRead);
     if (NS_FAILED(rv))
       return rv;
     LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
           this, *countRead, count, mSynFrameComplete));
     if (mSynFrameComplete) {
       NS_ABORT_IF_FALSE(mTxInlineFrameUsed,
                         "OnReadSegment SynFrameComplete 0b");
-      rv = TransmitFrame(nullptr, nullptr);
-      NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
-                        "Transmit Frame should be all or nothing");
-
-      // normalize a blocked write into an ok one if we have consumed the data
-      // while parsing headers as some code will take WOULD_BLOCK to mean an
-      // error with nothing processed.
-      // (e.g. nsHttpTransaction::ReadRequestSegment())
-      if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
-        rv = NS_OK;
+      rv = TransmitFrame(nullptr, nullptr, true);
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+        // this can't happen
+        NS_ABORT_IF_FALSE(false,
+                          "Transmit Frame SYN_FRAME must at least buffer data");
+        rv = NS_ERROR_UNEXPECTED;
+      }
 
-      // mTxInlineFrameUsed > 0 means the current frame is in progress
-      // of sending.  mTxInlineFrameUsed is dropped to 0 after both the frame
-      // and its payload (if any) are completely sent out.  Here during
-      // GENERATING_SYN_STREAM state we are sending just the http headers.
-      // Only when the frame is completely sent out do we proceed to
-      // GENERATING_REQUEST_BODY state.
-
-      if (mTxInlineFrameUsed)
-        ChangeState(SENDING_SYN_STREAM);
-      else
-        ChangeState(GENERATING_REQUEST_BODY);
+      ChangeState(GENERATING_REQUEST_BODY);
       break;
     }
     NS_ABORT_IF_FALSE(*countRead == count,
                       "Header parsing not complete but unused data");
     break;
 
   case GENERATING_REQUEST_BODY:
     if (mRemoteWindow <= 0) {
@@ -1281,17 +1250,17 @@ SpdyStream3::OnReadSegment(const char *b
       return NS_ERROR_UNEXPECTED;
     mRequestBodyLenRemaining -= dataLength;
     GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
     ChangeState(SENDING_REQUEST_BODY);
     // NO BREAK
 
   case SENDING_REQUEST_BODY:
     NS_ABORT_IF_FALSE(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
-    rv = TransmitFrame(buf, countRead);
+    rv = TransmitFrame(buf, countRead, false);
     NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
                       "Transmit Frame should be all or nothing");
 
     LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
           "Header is %d Body is %d.",
           rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
 
     // normalize a partial write with a WOULD_BLOCK into just a partial write
@@ -1300,20 +1269,16 @@ SpdyStream3::OnReadSegment(const char *b
     if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
       rv = NS_OK;
 
     // If that frame was all sent, look for another one
     if (!mTxInlineFrameUsed)
         ChangeState(GENERATING_REQUEST_BODY);
     break;
 
-  case SENDING_SYN_STREAM:
-    rv = NS_BASE_STREAM_WOULD_BLOCK;
-    break;
-
   case SENDING_FIN_STREAM:
     NS_ABORT_IF_FALSE(false,
                       "resuming partial fin stream out of OnReadSegment");
     break;
     
   default:
     NS_ABORT_IF_FALSE(false, "SpdyStream3::OnReadSegment non-write state");
     break;
--- a/netwerk/protocol/http/SpdyStream3.h
+++ b/netwerk/protocol/http/SpdyStream3.h
@@ -86,30 +86,29 @@ private:
   // a SpdyStream3 object is only destroyed by being removed from the
   // SpdySession3 mStreamTransactionHash - make the dtor private to
   // just the AutoPtr implementation needed for that hash.
   friend class nsAutoPtr<SpdyStream3>;
   ~SpdyStream3();
 
   enum stateType {
     GENERATING_SYN_STREAM,
-    SENDING_SYN_STREAM,
     GENERATING_REQUEST_BODY,
     SENDING_REQUEST_BODY,
     SENDING_FIN_STREAM,
     UPSTREAM_COMPLETE
   };
 
   static PLDHashOperator hdrHashEnumerate(const nsACString &,
                                           nsAutoPtr<nsCString> &,
                                           void *);
 
   void     ChangeState(enum stateType);
   nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
-  nsresult TransmitFrame(const char *, uint32_t *);
+  nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
   void     GenerateDataFrameHeader(uint32_t, bool);
 
   void     CompressToFrame(const nsACString &);
   void     CompressToFrame(const nsACString *);
   void     CompressToFrame(const char *, uint32_t);
   void     CompressToFrame(uint32_t);
   void     CompressFlushFrame();
   void     ExecuteCompress(uint32_t);
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -167,22 +167,23 @@ public:
     virtual nsresult OnReadSegment(const char *segment,
                                    uint32_t count,
                                    uint32_t *countRead) = 0;
 
     // Ask the segment reader to commit to accepting size bytes of
     // data from subsequent OnReadSegment() calls or throw hard
     // (i.e. not wouldblock) exceptions. Implementations
     // can return NS_ERROR_FAILURE if they never make commitments of that size
-    // (the default), NS_BASE_STREAM_WOULD_BLOCK if they cannot make
-    // the commitment now but might in the future, or NS_OK
-    // if they make the commitment.
+    // (the default), NS_OK if they make the commitment, or
+    // NS_BASE_STREAM_WOULD_BLOCK if they cannot make the
+    // commitment now but might in the future and forceCommitment is not true .
+    // (forceCommitment requires a hard failure or OK at this moment.)
     //
     // Spdy uses this to make sure frames are atomic.
-    virtual nsresult CommitToSegmentSize(uint32_t size)
+    virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment)
     {
         return NS_ERROR_FAILURE;
     }
 };
 
 #define NS_DECL_NSAHTTPSEGMENTREADER \
     nsresult OnReadSegment(const char *, uint32_t, uint32_t *);