summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorrch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-29 01:03:05 +0000
committerrch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-29 01:03:05 +0000
commit06ff515129b3213db0a398a8ce164b6a19df1411 (patch)
treee33fa39c262cb6ad519da14bfb41e9ac9ba202b1 /net
parent3549cbf145bb165665d9a55ab9513ac14a77e55f (diff)
downloadchromium_src-06ff515129b3213db0a398a8ce164b6a19df1411.zip
chromium_src-06ff515129b3213db0a398a8ce164b6a19df1411.tar.gz
chromium_src-06ff515129b3213db0a398a8ce164b6a19df1411.tar.bz2
If the stream is being closed locally (for example in the case of a
client, the user may cancel the request before the response is received from the server) then we need to make sure that we keep the stream alive long enough to process any response or RST_STREAM that the server sends. This should dramatically reduce (possibly eliminate) the instances of STREAM_RST_BEFORE_HEADERS_DECOMPRESSED errors from Chrome. (This is currently the second most common cause of QUIC sessions closure, behind the expected TIMED_OUT) Merge internal change: 51442891 BUG= Review URL: https://chromiumcodereview.appspot.com/23587004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220153 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/quic/quic_client_session.cc9
-rw-r--r--net/quic/quic_client_session.h4
-rw-r--r--net/quic/quic_connection.cc1
-rw-r--r--net/quic/quic_http_stream.cc4
-rw-r--r--net/quic/quic_http_stream_test.cc24
-rw-r--r--net/quic/quic_protocol.h2
-rw-r--r--net/quic/quic_session.cc97
-rw-r--r--net/quic/quic_session.h24
-rw-r--r--net/quic/quic_session_test.cc85
-rw-r--r--net/quic/quic_stream_factory_test.cc10
-rw-r--r--net/quic/quic_utils.cc1
11 files changed, 232 insertions, 29 deletions
diff --git a/net/quic/quic_client_session.cc b/net/quic/quic_client_session.cc
index 8f85a0e..0fde5b9 100644
--- a/net/quic/quic_client_session.cc
+++ b/net/quic/quic_client_session.cc
@@ -238,7 +238,16 @@ ReliableQuicStream* QuicClientSession::CreateIncomingReliableStream(
void QuicClientSession::CloseStream(QuicStreamId stream_id) {
QuicSession::CloseStream(stream_id);
+ OnClosedStream();
+}
+
+void QuicClientSession::SendRstStream(QuicStreamId id,
+ QuicRstStreamErrorCode error) {
+ QuicSession::SendRstStream(id, error);
+ OnClosedStream();
+}
+void QuicClientSession::OnClosedStream() {
if (GetNumOpenStreams() < get_max_open_streams() &&
!stream_requests_.empty() &&
crypto_stream_->encryption_established() &&
diff --git a/net/quic/quic_client_session.h b/net/quic/quic_client_session.h
index bdfaf2fd..9c5b705 100644
--- a/net/quic/quic_client_session.h
+++ b/net/quic/quic_client_session.h
@@ -102,6 +102,8 @@ class NET_EXPORT_PRIVATE QuicClientSession : public QuicSession {
virtual QuicReliableClientStream* CreateOutgoingReliableStream() OVERRIDE;
virtual QuicCryptoClientStream* GetCryptoStream() OVERRIDE;
virtual void CloseStream(QuicStreamId stream_id) OVERRIDE;
+ virtual void SendRstStream(QuicStreamId id,
+ QuicRstStreamErrorCode error) OVERRIDE;
virtual void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) OVERRIDE;
virtual void OnCryptoHandshakeMessageSent(
const CryptoHandshakeMessage& message) OVERRIDE;
@@ -144,6 +146,8 @@ class NET_EXPORT_PRIVATE QuicClientSession : public QuicSession {
// A completion callback invoked when a read completes.
void OnReadComplete(int result);
+ void OnClosedStream();
+
void CloseSessionOnErrorInner(int error);
// Posts a task to notify the factory that this session has been closed.
diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc
index 8545b0a..6e329e0 100644
--- a/net/quic/quic_connection.cc
+++ b/net/quic/quic_connection.cc
@@ -814,6 +814,7 @@ QuicConsumedData QuicConnection::SendStreamData(QuicStreamId id,
void QuicConnection::SendRstStream(QuicStreamId id,
QuicRstStreamErrorCode error) {
+ LOG(INFO) << "Sending RST_STREAM: " << id << " code: " << error;
packet_generator_.AddControlFrame(
QuicFrame(new QuicRstStreamFrame(id, error)));
}
diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc
index 5d7d60d..85af67a 100644
--- a/net/quic/quic_http_stream.cc
+++ b/net/quic/quic_http_stream.cc
@@ -205,7 +205,9 @@ void QuicHttpStream::Close(bool not_reusable) {
// Note: the not_reusable flag has no meaning for SPDY streams.
if (stream_) {
stream_->SetDelegate(NULL);
- stream_->Close(QUIC_STREAM_NO_ERROR);
+ // TODO(rch): use new CANCELLED error code here once quic 11
+ // is everywhere.
+ stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM);
stream_ = NULL;
}
}
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index 2c49bcb..d59ce70 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -34,6 +34,8 @@
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
+using testing::AnyNumber;
+using testing::Return;
namespace net {
namespace test {
@@ -168,14 +170,17 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> {
runner_ = new TestTaskRunner(&clock_);
send_algorithm_ = new MockSendAlgorithm();
receive_algorithm_ = new TestReceiveAlgorithm(NULL);
+ EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _, _)).
+ Times(AnyNumber());
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber());
EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
- testing::Return(QuicTime::Delta::Zero()));
+ Return(QuicTime::Delta::Zero()));
EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)).
- WillRepeatedly(testing::Return(QuicTime::Delta::Zero()));
+ WillRepeatedly(Return(QuicTime::Delta::Zero()));
EXPECT_CALL(*send_algorithm_, SmoothedRtt()).WillRepeatedly(
- testing::Return(QuicTime::Delta::Zero()));
+ Return(QuicTime::Delta::Zero()));
EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillRepeatedly(
- testing::Return(QuicBandwidth::Zero()));
+ Return(QuicBandwidth::Zero()));
helper_ = new QuicConnectionHelper(runner_.get(), &clock_,
&random_generator_, socket);
connection_ = new TestQuicConnection(guid_, peer_addr_, helper_);
@@ -223,7 +228,7 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> {
return compressor.CompressHeaders(headers);
}
- // Returns a newly created packet to send kData on stream 1.
+ // Returns a newly created packet to send kData on stream 3.
QuicEncryptedPacket* ConstructDataPacket(
QuicPacketSequenceNumber sequence_number,
bool should_include_version,
@@ -235,6 +240,14 @@ class QuicHttpStreamTest : public ::testing::TestWithParam<bool> {
return ConstructPacket(header_, QuicFrame(&frame));
}
+ // Returns a newly created packet to RST_STREAM stream 3.
+ QuicEncryptedPacket* ConstructRstStreamPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ InitializeHeader(sequence_number, false);
+ QuicRstStreamFrame frame(3, QUIC_SERVER_ERROR_PROCESSING_STREAM);
+ return ConstructPacket(header_, QuicFrame(&frame));
+ }
+
// Returns a newly created packet to send ack data.
QuicEncryptedPacket* ConstructAckPacket(
QuicPacketSequenceNumber sequence_number,
@@ -537,6 +550,7 @@ TEST_F(QuicHttpStreamTest, SendChunkedPostRequest) {
TEST_F(QuicHttpStreamTest, DestroyedEarly) {
SetRequestString("GET", "/");
AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_));
+ AddWrite(SYNCHRONOUS, ConstructRstStreamPacket(2));
use_closing_stream_ = true;
Initialize();
diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h
index b6fd034..db902a0 100644
--- a/net/quic/quic_protocol.h
+++ b/net/quic/quic_protocol.h
@@ -279,6 +279,8 @@ enum QuicRstStreamErrorCode {
QUIC_STREAM_CONNECTION_ERROR,
// GoAway frame sent. No more stream can be created.
QUIC_STREAM_PEER_GOING_AWAY,
+ // The stream has been cancelled.
+ QUIC_STREAM_CANCELLED,
// No error. Used as bound while iterating.
QUIC_STREAM_LAST_ERROR,
diff --git a/net/quic/quic_session.cc b/net/quic/quic_session.cc
index aa83ab0..8f6403f 100644
--- a/net/quic/quic_session.cc
+++ b/net/quic/quic_session.cc
@@ -18,6 +18,7 @@ using std::vector;
namespace net {
const size_t kMaxPrematurelyClosedStreamsTracked = 20;
+const size_t kMaxZombieStreams = 20;
#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
@@ -133,9 +134,19 @@ bool QuicSession::OnPacket(const IPEndPoint& self_address,
}
for (size_t i = 0; i < frames.size(); ++i) {
- ReliableQuicStream* stream = GetStream(frames[i].stream_id);
- if (stream) {
- stream->OnStreamFrame(frames[i]);
+ QuicStreamId stream_id = frames[i].stream_id;
+ ReliableQuicStream* stream = GetStream(stream_id);
+ if (!stream) {
+ continue;
+ }
+ stream->OnStreamFrame(frames[i]);
+
+ // If the stream had been prematurely closed, and the
+ // headers are now decompressed, then we are finally finished
+ // with this stream.
+ if (ContainsKey(zombie_streams_, stream_id) &&
+ stream->headers_decompressed()) {
+ CloseZombieStream(stream_id);
}
}
@@ -162,6 +173,16 @@ void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
if (!stream) {
return; // Errors are handled by GetStream.
}
+ if (ContainsKey(zombie_streams_, stream->id())) {
+ // If this was a zombie stream then we close it out now.
+ CloseZombieStream(stream->id());
+ // However, since the headers still have not been decompressed, we want to
+ // mark it a prematurely closed so that if we ever receive frames
+ // for this stream we can close the connection.
+ DCHECK(!stream->headers_decompressed());
+ AddPrematurelyClosedStream(frame.stream_id);
+ return;
+ }
stream->OnStreamReset(frame.error_code);
}
@@ -171,6 +192,7 @@ void QuicSession::OnGoAway(const QuicGoAwayFrame& frame) {
}
void QuicSession::ConnectionClose(QuicErrorCode error, bool from_peer) {
+ DCHECK(!connection_->connected());
if (error_ == QUIC_NO_ERROR) {
error_ = error;
}
@@ -221,7 +243,7 @@ QuicConsumedData QuicSession::WriteData(QuicStreamId id,
void QuicSession::SendRstStream(QuicStreamId id,
QuicRstStreamErrorCode error) {
connection_->SendRstStream(id, error);
- CloseStream(id);
+ CloseStreamInner(id, true);
}
void QuicSession::SendGoAway(QuicErrorCode error_code, const string& reason) {
@@ -230,6 +252,11 @@ void QuicSession::SendGoAway(QuicErrorCode error_code, const string& reason) {
}
void QuicSession::CloseStream(QuicStreamId stream_id) {
+ CloseStreamInner(stream_id, false);
+}
+
+void QuicSession::CloseStreamInner(QuicStreamId stream_id,
+ bool locally_reset) {
DLOG(INFO) << ENDPOINT << "Closing stream " << stream_id;
ReliableStreamMap::iterator it = stream_map_.find(stream_id);
@@ -238,18 +265,62 @@ void QuicSession::CloseStream(QuicStreamId stream_id) {
return;
}
ReliableQuicStream* stream = it->second;
- if (!stream->headers_decompressed()) {
- if (prematurely_closed_streams_.size() ==
- kMaxPrematurelyClosedStreamsTracked) {
- prematurely_closed_streams_.erase(prematurely_closed_streams_.begin());
+ if (connection_->connected() && !stream->headers_decompressed()) {
+ // If the stream is being closed locally (for example a client cancelling
+ // a request before receiving the response) then we need to make sure that
+ // we keep the stream alive long enough to process any response or
+ // RST_STREAM frames.
+ if (locally_reset && !is_server_) {
+ AddZombieStream(stream_id);
+ return;
}
- prematurely_closed_streams_.insert(make_pair(stream->id(), true));
+
+ // This stream has been closed before the headers were decompressed.
+ // This might cause problems with head of line blocking of headers.
+ // If the peer sent headers which were lost but we now close the stream
+ // we will never be able to decompress headers for other streams.
+ // To deal with this, we keep track of streams which have been closed
+ // prematurely. If we ever receive data frames for this steam, then we
+ // know there actually has been a problem and we close the connection.
+ AddPrematurelyClosedStream(stream->id());
}
closed_streams_.push_back(it->second);
stream_map_.erase(it);
stream->OnClose();
}
+void QuicSession::AddZombieStream(QuicStreamId stream_id) {
+ if (zombie_streams_.size() == kMaxZombieStreams) {
+ QuicStreamId oldest_zombie_stream_id = zombie_streams_.begin()->first;
+ CloseZombieStream(oldest_zombie_stream_id);
+ // However, since the headers still have not been decompressed, we want to
+ // mark it a prematurely closed so that if we ever receive frames
+ // for this stream we can close the connection.
+ AddPrematurelyClosedStream(oldest_zombie_stream_id);
+ }
+ zombie_streams_.insert(make_pair(stream_id, true));
+}
+
+void QuicSession::CloseZombieStream(QuicStreamId stream_id) {
+ DCHECK(ContainsKey(zombie_streams_, stream_id));
+ zombie_streams_.erase(stream_id);
+ ReliableQuicStream* stream = GetStream(stream_id);
+ if (!stream) {
+ return;
+ }
+ stream_map_.erase(stream_id);
+ stream->OnClose();
+ closed_streams_.push_back(stream);
+}
+
+void QuicSession::AddPrematurelyClosedStream(QuicStreamId stream_id) {
+ if (prematurely_closed_streams_.size() ==
+ kMaxPrematurelyClosedStreamsTracked) {
+ prematurely_closed_streams_.erase(prematurely_closed_streams_.begin());
+ }
+ prematurely_closed_streams_.insert(make_pair(stream_id, true));
+}
+
bool QuicSession::IsEncryptionEstablished() {
return GetCryptoStream()->encryption_established();
}
@@ -376,7 +447,10 @@ bool QuicSession::IsClosedStream(QuicStreamId id) {
if (id == kCryptoStreamId) {
return false;
}
- if (stream_map_.count(id) != 0) {
+ if (ContainsKey(zombie_streams_, id)) {
+ return true;
+ }
+ if (ContainsKey(stream_map_, id)) {
// Stream is active
return false;
}
@@ -392,7 +466,8 @@ bool QuicSession::IsClosedStream(QuicStreamId id) {
}
size_t QuicSession::GetNumOpenStreams() const {
- return stream_map_.size() + implicitly_created_streams_.size();
+ return stream_map_.size() + implicitly_created_streams_.size() -
+ zombie_streams_.size();
}
void QuicSession::MarkWriteBlocked(QuicStreamId id) {
diff --git a/net/quic/quic_session.h b/net/quic/quic_session.h
index 75c3ea0..c37b6e1 100644
--- a/net/quic/quic_session.h
+++ b/net/quic/quic_session.h
@@ -211,6 +211,24 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
typedef base::hash_map<QuicStreamId, ReliableQuicStream*> ReliableStreamMap;
+ // Performs the work required to close |stream_id|. If |locally_reset|
+ // then the stream has been reset by this endpoint, not by the peer. This
+ // means the stream may become a zombie stream which needs to stay
+ // around until headers have been decompressed.
+ void CloseStreamInner(QuicStreamId stream_id, bool locally_reset);
+
+ // Adds |stream_id| to the zobmie stream map, closing the oldest
+ // zombie stream if the set is full.
+ void AddZombieStream(QuicStreamId stream_id);
+
+ // Closes the zombie stream |stream_id| and removes it from the zombie
+ // stream map.
+ void CloseZombieStream(QuicStreamId stream_id);
+
+ // Adds |stream_id| to the prematurely closed stream map, removing the
+ // oldest prematurely closed stream if the set is full.
+ void AddPrematurelyClosedStream(QuicStreamId stream_id);
+
scoped_ptr<QuicConnection> connection_;
// Tracks the last 20 streams which closed without decompressing headers.
@@ -218,6 +236,12 @@ class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
// Ideally this would be a linked_hash_set as the boolean is unused.
linked_hash_map<QuicStreamId, bool> prematurely_closed_streams_;
+ // Streams which have been locally reset before decompressing headers
+ // from the peer. These streams need to stay open long enough to
+ // process any headers from the peer.
+ // Ideally this would be a linked_hash_set as the boolean is unused.
+ linked_hash_map<QuicStreamId, bool> zombie_streams_;
+
// A shim to stand between the connection and the session, to handle stream
// deletions.
scoped_ptr<VisitorShim> visitor_shim_;
diff --git a/net/quic/quic_session_test.cc b/net/quic/quic_session_test.cc
index 4df7222..77234e1 100644
--- a/net/quic/quic_session_test.cc
+++ b/net/quic/quic_session_test.cc
@@ -13,6 +13,7 @@
#include "net/quic/quic_protocol.h"
#include "net/quic/test_tools/quic_connection_peer.h"
#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
#include "net/spdy/spdy_framer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -22,6 +23,7 @@ using std::set;
using std::vector;
using testing::_;
using testing::InSequence;
+using testing::StrictMock;
namespace net {
namespace test {
@@ -105,6 +107,33 @@ class QuicSessionTest : public ::testing::Test {
: guid_(1),
connection_(new MockConnection(guid_, IPEndPoint(), false)),
session_(connection_, true) {
+ headers_[":host"] = "www.google.com";
+ headers_[":path"] = "/index.hml";
+ headers_[":scheme"] = "http";
+ headers_["cookie"] =
+ "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+ "__utmc=160408618; "
+ "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+ "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+ "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+ "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+ "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+ "1zFMi5vzcns38-8_Sns; "
+ "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+ "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+ "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+ "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+ "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+ "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+ "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+ "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+ "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+ "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+ "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+ "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+ "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+ "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+ "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
}
void CheckClosedStreams() {
@@ -126,6 +155,7 @@ class QuicSessionTest : public ::testing::Test {
MockConnection* connection_;
TestSession session_;
set<QuicStreamId> closed_streams_;
+ SpdyHeaderBlock headers_;
};
TEST_F(QuicSessionTest, PeerAddress) {
@@ -149,8 +179,10 @@ TEST_F(QuicSessionTest, IsClosedStreamDefault) {
TEST_F(QuicSessionTest, IsClosedStreamLocallyCreated) {
TestStream* stream2 = session_.CreateOutgoingReliableStream();
EXPECT_EQ(2u, stream2->id());
+ ReliableQuicStreamPeer::SetHeadersDecompressed(stream2, true);
TestStream* stream4 = session_.CreateOutgoingReliableStream();
EXPECT_EQ(4u, stream4->id());
+ ReliableQuicStreamPeer::SetHeadersDecompressed(stream4, true);
CheckClosedStreams();
CloseStream(4);
@@ -160,15 +192,18 @@ TEST_F(QuicSessionTest, IsClosedStreamLocallyCreated) {
}
TEST_F(QuicSessionTest, IsClosedStreamPeerCreated) {
- session_.GetIncomingReliableStream(3);
- session_.GetIncomingReliableStream(5);
+ ReliableQuicStream* stream3 = session_.GetIncomingReliableStream(3);
+ ReliableQuicStreamPeer::SetHeadersDecompressed(stream3, true);
+ ReliableQuicStream* stream5 = session_.GetIncomingReliableStream(5);
+ ReliableQuicStreamPeer::SetHeadersDecompressed(stream5, true);
CheckClosedStreams();
CloseStream(3);
CheckClosedStreams();
CloseStream(5);
// Create stream id 9, and implicitly 7
- session_.GetIncomingReliableStream(9);
+ ReliableQuicStream* stream9 = session_.GetIncomingReliableStream(9);
+ ReliableQuicStreamPeer::SetHeadersDecompressed(stream9, true);
CheckClosedStreams();
// Close 9, but make sure 7 is still not closed
CloseStream(9);
@@ -229,10 +264,6 @@ TEST_F(QuicSessionTest, OnCanWriteWithClosedStream) {
// Regression test for http://crbug.com/248737
TEST_F(QuicSessionTest, OutOfOrderHeaders) {
QuicSpdyCompressor compressor;
- SpdyHeaderBlock headers;
- headers[":host"] = "www.google.com";
- headers[":path"] = "/index.hml";
- headers[":scheme"] = "http";
vector<QuicStreamFrame> frames;
QuicPacketHeader header;
header.public_header.guid = session_.guid();
@@ -243,11 +274,11 @@ TEST_F(QuicSessionTest, OutOfOrderHeaders) {
stream4->CloseWriteSide();
// Create frame with headers for stream2.
- string compressed_headers1 = compressor.CompressHeaders(headers);
+ string compressed_headers1 = compressor.CompressHeaders(headers_);
QuicStreamFrame frame1(stream2->id(), false, 0, compressed_headers1);
// Create frame with headers for stream4.
- string compressed_headers2 = compressor.CompressHeaders(headers);
+ string compressed_headers2 = compressor.CompressHeaders(headers_);
QuicStreamFrame frame2(stream4->id(), true, 0, compressed_headers2);
// Process the second frame first. This will cause the headers to
@@ -260,7 +291,7 @@ TEST_F(QuicSessionTest, OutOfOrderHeaders) {
session_.OnPacket(IPEndPoint(), IPEndPoint(), header, frames);
// Ensure that the streams actually close and we don't DCHECK.
- session_.ConnectionClose(QUIC_CONNECTION_TIMED_OUT, true);
+ connection_->CloseConnection(QUIC_CONNECTION_TIMED_OUT, true);
}
TEST_F(QuicSessionTest, SendGoAway) {
@@ -284,6 +315,40 @@ TEST_F(QuicSessionTest, IncreasedTimeoutAfterCryptoHandshake) {
QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
}
+TEST_F(QuicSessionTest, ZombieStream) {
+ StrictMock<MockConnection>* connection =
+ new StrictMock<MockConnection>(guid_, IPEndPoint(), false);
+ TestSession session(connection, /*is_server=*/ false);
+
+ TestStream* stream3 = session.CreateOutgoingReliableStream();
+ EXPECT_EQ(3u, stream3->id());
+ TestStream* stream5 = session.CreateOutgoingReliableStream();
+ EXPECT_EQ(5u, stream5->id());
+
+ // Reset the stream, but since the headers have not been decompressed
+ // it will become a zombie and will continue to process data
+ // until the headers are decompressed.
+ EXPECT_CALL(*connection, SendRstStream(3, QUIC_STREAM_CANCELLED));
+ session.SendRstStream(3, QUIC_STREAM_CANCELLED);
+
+ vector<QuicStreamFrame> frames;
+ QuicPacketHeader header;
+ header.public_header.guid = session_.guid();
+
+ // Create frame with headers for stream2.
+ QuicSpdyCompressor compressor;
+ string compressed_headers1 = compressor.CompressHeaders(headers_);
+ QuicStreamFrame frame1(stream3->id(), false, 0, compressed_headers1);
+
+ // Process the second frame first. This will cause the headers to
+ // be queued up and processed after the first frame is processed.
+ frames.push_back(frame1);
+ EXPECT_FALSE(stream3->headers_decompressed());
+ session.OnPacket(IPEndPoint(), IPEndPoint(), header, frames);
+
+ EXPECT_TRUE(connection->connected());
+}
+
} // namespace
} // namespace test
} // namespace net
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index 5c604ea..fb9d474 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -47,11 +47,12 @@ class QuicStreamFactoryTest : public ::testing::Test {
header.public_header.reset_flag = false;
header.public_header.version_flag = true;
header.packet_sequence_number = num;
+ header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
header.entropy_flag = false;
header.fec_flag = false;
header.fec_group = 0;
- QuicRstStreamFrame rst(stream_id, QUIC_STREAM_NO_ERROR);
+ QuicRstStreamFrame rst(stream_id, QUIC_SERVER_ERROR_PROCESSING_STREAM);
return scoped_ptr<QuicEncryptedPacket>(
ConstructPacket(header, QuicFrame(&rst)));
}
@@ -173,7 +174,12 @@ TEST_F(QuicStreamFactoryTest, MaxOpenStream) {
MockRead reads[] = {
MockRead(ASYNC, OK, 0) // EOF
};
- DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+ scoped_ptr<QuicEncryptedPacket> rst(ConstructRstPacket(1, 3));
+ MockWrite writes[] = {
+ MockWrite(ASYNC, rst->data(), rst->length(), 1),
+ };
+ DeterministicSocketData socket_data(reads, arraysize(reads),
+ writes, arraysize(writes));
socket_factory_.AddSocketDataProvider(&socket_data);
socket_data.StopAfter(1);
diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc
index 9c79a9b..b481969 100644
--- a/net/quic/quic_utils.cc
+++ b/net/quic/quic_utils.cc
@@ -124,6 +124,7 @@ const char* QuicUtils::StreamErrorToString(QuicRstStreamErrorCode error) {
RETURN_STRING_LITERAL(QUIC_MULTIPLE_TERMINATION_OFFSETS);
RETURN_STRING_LITERAL(QUIC_BAD_APPLICATION_PAYLOAD);
RETURN_STRING_LITERAL(QUIC_STREAM_PEER_GOING_AWAY);
+ RETURN_STRING_LITERAL(QUIC_STREAM_CANCELLED);
RETURN_STRING_LITERAL(QUIC_STREAM_LAST_ERROR);
}
// Return a default value so that we return this when |error| doesn't match