bug 822456 - spdy stream id out of order r=honzab

This commit is contained in:
Patrick McManus 2012-12-18 17:14:21 -05:00
parent dba76a0e62
commit 567ccc010f
9 changed files with 113 additions and 158 deletions

View File

@ -383,6 +383,16 @@ SpdySession2::SetWriteCallbacks()
mConnection->ResumeSend();
}
void
SpdySession2::RealignOutputQueue()
{
mOutputQueueUsed -= mOutputQueueSent;
memmove(mOutputQueueBuffer.get(),
mOutputQueueBuffer.get() + mOutputQueueSent,
mOutputQueueUsed);
mOutputQueueSent = 0;
}
void
SpdySession2::FlushOutputQueue()
{
@ -416,11 +426,7 @@ SpdySession2::FlushOutputQueue()
if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
mOutputQueueUsed -= mOutputQueueSent;
memmove(mOutputQueueBuffer.get(),
mOutputQueueBuffer.get() + mOutputQueueSent,
mOutputQueueUsed);
mOutputQueueSent = 0;
RealignOutputQueue();
}
}
@ -1918,7 +1924,7 @@ SpdySession2::OnReadSegment(const char *buf,
}
nsresult
SpdySession2::CommitToSegmentSize(uint32_t count)
SpdySession2::CommitToSegmentSize(uint32_t count, bool forceCommitment)
{
if (mOutputQueueUsed)
FlushOutputQueue();
@ -1926,19 +1932,25 @@ SpdySession2::CommitToSegmentSize(uint32_t count)
// 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),

View File

@ -112,13 +112,13 @@ public:
// 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;
@ -153,7 +153,7 @@ public:
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);
@ -185,6 +185,7 @@ private:
void SetWriteCallbacks();
void FlushOutputQueue();
void RealignOutputQueue();
bool RoomForMoreConcurrent();
void ActivateStream(SpdyStream2 *);

View File

@ -384,6 +384,16 @@ SpdySession3::SetWriteCallbacks()
mConnection->ResumeSend();
}
void
SpdySession3::RealignOutputQueue()
{
mOutputQueueUsed -= mOutputQueueSent;
memmove(mOutputQueueBuffer.get(),
mOutputQueueBuffer.get() + mOutputQueueSent,
mOutputQueueUsed);
mOutputQueueSent = 0;
}
void
SpdySession3::FlushOutputQueue()
{
@ -417,11 +427,7 @@ SpdySession3::FlushOutputQueue()
if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
mOutputQueueUsed -= mOutputQueueSent;
memmove(mOutputQueueBuffer.get(),
mOutputQueueBuffer.get() + mOutputQueueSent,
mOutputQueueUsed);
mOutputQueueSent = 0;
RealignOutputQueue();
}
}
@ -1963,7 +1969,7 @@ SpdySession3::OnReadSegment(const char *buf,
}
nsresult
SpdySession3::CommitToSegmentSize(uint32_t count)
SpdySession3::CommitToSegmentSize(uint32_t count, bool forceCommitment)
{
if (mOutputQueueUsed)
FlushOutputQueue();
@ -1971,19 +1977,25 @@ SpdySession3::CommitToSegmentSize(uint32_t count)
// 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),

View File

@ -111,13 +111,13 @@ public:
// 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;
@ -162,7 +162,7 @@ public:
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; }
@ -194,6 +194,7 @@ private:
void SetWriteCallbacks();
void FlushOutputQueue();
void RealignOutputQueue();
bool RoomForMoreConcurrent();
void ActivateStream(SpdyStream3 *);

View File

@ -128,35 +128,12 @@ SpdyStream2::ReadSegments(nsAHttpSegmentReader *reader,
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");
@ -503,7 +480,8 @@ SpdyStream2::UpdateTransportSendEvents(uint32_t count)
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
@ -542,10 +520,14 @@ SpdyStream2::TransmitFrame(const char *buf,
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;
@ -810,28 +792,15 @@ SpdyStream2::OnReadSegment(const char *buf,
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");
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;
}
// 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;
// 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,
@ -852,7 +821,7 @@ SpdyStream2::OnReadSegment(const char *buf,
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");
@ -871,10 +840,6 @@ SpdyStream2::OnReadSegment(const char *buf,
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");

View File

@ -70,7 +70,6 @@ private:
enum stateType {
GENERATING_SYN_STREAM,
SENDING_SYN_STREAM,
GENERATING_REQUEST_BODY,
SENDING_REQUEST_BODY,
SENDING_FIN_STREAM,
@ -83,7 +82,7 @@ private:
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 &);

View File

@ -139,35 +139,12 @@ SpdyStream3::ReadSegments(nsAHttpSegmentReader *reader,
}
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");
@ -523,7 +500,8 @@ SpdyStream3::UpdateTransportSendEvents(uint32_t count)
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
@ -562,10 +540,14 @@ SpdyStream3::TransmitFrame(const char *buf,
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;
@ -1227,28 +1209,15 @@ SpdyStream3::OnReadSegment(const char *buf,
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");
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;
}
// 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;
// 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,
@ -1286,7 +1255,7 @@ SpdyStream3::OnReadSegment(const char *buf,
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");
@ -1305,10 +1274,6 @@ SpdyStream3::OnReadSegment(const char *buf,
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");

View File

@ -91,7 +91,6 @@ private:
enum stateType {
GENERATING_SYN_STREAM,
SENDING_SYN_STREAM,
GENERATING_REQUEST_BODY,
SENDING_REQUEST_BODY,
SENDING_FIN_STREAM,
@ -104,7 +103,7 @@ private:
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 &);

View File

@ -172,12 +172,13 @@ public:
// 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;
}