diff options
author | erikchen@google.com <erikchen@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-12 18:16:14 +0000 |
---|---|---|
committer | erikchen@google.com <erikchen@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-12 18:16:14 +0000 |
commit | 9c6527c9d4d289ed4f62194cf116e071b9d49ac9 (patch) | |
tree | c133ad0cabdbad4911a2af42311e3405a37eb23e /net/spdy | |
parent | 02d8d78f984d3201ad038460bc57dd66ac293ac2 (diff) | |
download | chromium_src-9c6527c9d4d289ed4f62194cf116e071b9d49ac9.zip chromium_src-9c6527c9d4d289ed4f62194cf116e071b9d49ac9.tar.gz chromium_src-9c6527c9d4d289ed4f62194cf116e071b9d49ac9.tar.bz2 |
Streams send a Rst frame upon being closed by client. Some minor editorial fixes.
TEST=net_unittests
BUG=46589
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=51007
Review URL: http://codereview.chromium.org/2804008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52106 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/spdy')
-rw-r--r-- | net/spdy/spdy_network_transaction.h | 3 | ||||
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 154 | ||||
-rw-r--r-- | net/spdy/spdy_protocol.h | 2 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 72 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 17 | ||||
-rw-r--r-- | net/spdy/spdy_stream.cc | 22 | ||||
-rw-r--r-- | net/spdy/spdy_stream.h | 21 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 13 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 6 |
9 files changed, 263 insertions, 47 deletions
diff --git a/net/spdy/spdy_network_transaction.h b/net/spdy/spdy_network_transaction.h index 12b055c..ac9df0c 100644 --- a/net/spdy/spdy_network_transaction.h +++ b/net/spdy/spdy_network_transaction.h @@ -52,6 +52,9 @@ class SpdyNetworkTransaction : public HttpTransaction { virtual LoadState GetLoadState() const; virtual uint64 GetUploadProgress() const; + // Provide access to the stream for testing. + friend class SpdyNetworkTransactionTest; + private: enum State { STATE_INIT_CONNECTION, diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc index 7f48a60..ce49f9e 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -176,6 +176,10 @@ class SpdyNetworkTransactionTest : public PlatformTest { return google_get_request_; } + SpdyStream* GetSpdyStream(SpdyNetworkTransaction* trans) { + return trans->stream_->stream(); + } + private: bool google_get_request_initialized_; HttpRequestInfo google_get_request_; @@ -333,6 +337,67 @@ TEST_F(SpdyNetworkTransactionTest, ResponseWithoutSynReply) { EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); } +class CloseStreamCallback : public CallbackRunner< Tuple1<int> > { + public: + explicit CloseStreamCallback(SpdyStream* stream) + : stream_(stream) {} + + // This callback should occur immediately after the SpdyStream has been + // closed. We check that the stream has been marked half closed from the + // client side. + virtual void RunWithParams(const Tuple1<int>& params) { + EXPECT_TRUE(stream_->half_closed_client_side()); + } + + private: + const SpdyStream* stream_; +}; + +// Test that spdy_session marks a stream as half closed immediately upon closing +// the stream. +TEST_F(SpdyNetworkTransactionTest, StreamHalfClosedClientSide) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame()); + MockRead reads[] = { + CreateMockRead(*resp.get(), 2), + CreateMockRead(*body.get(), 3), + MockRead(true, 0, 0, 4), // EOF + }; + + // We disable SSL for this test. + SpdySession::SetSSLMode(false); + + SessionDependencies session_deps; + scoped_ptr<SpdyNetworkTransaction> trans( + new SpdyNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + session_deps.socket_factory.AddSocketDataProvider(data); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + SpdyStream* stream = GetSpdyStream(trans.get()); + + // Setup a user callback which will check that the half closed flag is marked + // immediately after the stream is closed. + CloseStreamCallback callback2(stream); + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize); + rv = trans->Read(buf, kSize, &callback2); + data->SetCompletionCallback(NULL); + data->Reset(); +} + // Test that the transaction doesn't crash when we get two replies on the same // stream ID. See http://crbug.com/45639. TEST_F(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { @@ -354,10 +419,6 @@ TEST_F(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { MockRead(true, 0, 0) // EOF }; - HttpRequestInfo request; - request.method = "GET"; - request.url = GURL("http://www.google.com/"); - request.load_flags = 0; scoped_refptr<DelayedSocketData> data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); @@ -367,7 +428,7 @@ TEST_F(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { new SpdyNetworkTransaction(session)); TestCompletionCallback callback; - int rv = trans->Start(&request, &callback, BoundNetLog()); + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); rv = callback.WaitForResult(); EXPECT_EQ(OK, rv); @@ -420,6 +481,87 @@ TEST_F(SpdyNetworkTransactionTest, CancelledTransaction) { MessageLoop::current()->RunAllPending(); } +// The client upon cancellation tries to send a RST_STREAM frame. The mock +// socket causes the TCP write to return zero. This test checks that the client +// tries to queue up the RST_STREAM frame again. +TEST_F(SpdyNetworkTransactionTest, SocketWriteReturnsZero) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstFrame()); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + MockWrite(true, 0, 0, 3), + CreateMockWrite(*rst.get(), 4), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 2), + MockRead(true, 0, 0, 5) // EOF + }; + + // We disable SSL for this test. + SpdySession::SetSSLMode(false); + + SessionDependencies session_deps; + scoped_ptr<SpdyNetworkTransaction> trans( + new SpdyNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + session_deps.socket_factory.AddSocketDataProvider(data); + + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + trans.reset(); // Cancel the transaction. + MessageLoop::current()->RunAllPending(); + data->CompleteRead(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " << data->write_count() + << ". Write index: " << data->write_index() << "."; + EXPECT_TRUE(data->at_read_eof()) << "Read count: " << data->read_count() + << ". Read index: " << data->read_index() << "."; +} + +// Verify that the client sends a Rst Frame upon cancelling the stream. +TEST_F(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstFrame()); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + CreateMockWrite(*rst.get(), 3), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 2), + MockRead(true, 0, 0, 4) // EOF + }; + + // We disable SSL for this test. + SpdySession::SetSSLMode(false); + + SessionDependencies session_deps; + scoped_ptr<SpdyNetworkTransaction> trans( + new SpdyNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + session_deps.socket_factory.AddSocketDataProvider(data); + + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + trans.reset(); // Cancel the transaction. + MessageLoop::current()->RunAllPending(); + data->CompleteRead(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " << data->write_count() + << ". Write index: " << data->write_index() << "."; + EXPECT_TRUE(data->at_read_eof()) << "Read count: " << data->read_count() + << ". Read index: " << data->read_index() << "."; +} + class StartTransactionCallback : public CallbackRunner< Tuple1<int> > { public: explicit StartTransactionCallback( @@ -540,7 +682,7 @@ TEST_F(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { SpdySession::SetSSLMode(false); SessionDependencies session_deps; - SpdyNetworkTransaction * trans = + SpdyNetworkTransaction* trans = new SpdyNetworkTransaction(CreateSession(&session_deps)); scoped_refptr<OrderedSocketData> data( new OrderedSocketData(reads, arraysize(reads), diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h index 49d8bdc..8b8459d 100644 --- a/net/spdy/spdy_protocol.h +++ b/net/spdy/spdy_protocol.h @@ -63,7 +63,7 @@ // +----------------------------------+ // |1|000000000000001|0000000000000011| // +----------------------------------+ -// | flags (8) | Length (24 bits) | >= 4 +// | flags (8) | Length (24 bits) | >= 8 // +----------------------------------+ // |X| Stream-ID(31bits) | // +----------------------------------+ diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index b1ba0f1..7dcc30b 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -232,7 +232,7 @@ net::Error SpdySession::Connect(const std::string& group_name, int rv = connection_->Init(group_name, destination, priority, &connect_callback_, session_->tcp_socket_pool(), net_log_); - DCHECK(rv <= 0); + DCHECK_LE(rv, 0); // If the connect is pending, we still return ok. The APIs enqueue // work until after the connect completes asynchronously later. @@ -370,11 +370,21 @@ int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id, const int kMaxSpdyFrameChunkSize = (2 * kMss) - spdy::SpdyFrame::size(); // Find our stream - DCHECK(IsStreamActive(stream_id)); - scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; - CHECK_EQ(stream->stream_id(), stream_id); - if (!stream) + ActiveStreamMap::const_iterator it = active_streams_.find(stream_id); + if (it == active_streams_.end()) { + LOG(DFATAL) << "Attempting to write to stream " << stream_id + << ", which does not exist!"; + return ERR_INVALID_SPDY_STREAM; + } + + const scoped_refptr<SpdyStream>& stream = it->second; + if (!stream) { + LOG(DFATAL) << "Attempting to write to stream " << stream_id + << ", which does not exist!"; return ERR_INVALID_SPDY_STREAM; + } + + CHECK_EQ(stream->stream_id(), stream_id); // TODO(mbelshe): Setting of the FIN is assuming that the caller will pass // all data to write in a single chunk. Is this always true? @@ -393,12 +403,20 @@ int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id, return ERR_IO_PENDING; } -void SpdySession::CloseStream(spdy::SpdyStreamId stream_id, int status) { - LOG(INFO) << "Closing stream " << stream_id << " with status " << status; - // TODO(mbelshe): We should send a RST_STREAM control frame here - // so that the server can cancel a large send. +void SpdySession::CloseStreamAndSendRst(spdy::SpdyStreamId stream_id, + int status) { + LOG(INFO) << "Closing stream " << stream_id << " with status " << status + << " and sending RST_STREAM frame."; + + DCHECK(IsStreamActive(stream_id)); + const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id]; + // We send a RST_STREAM control frame here so that the server can cancel a + // large send. + scoped_ptr<spdy::SpdyRstStreamControlFrame> rst_frame( + spdy_framer_.CreateRstStream(stream_id, spdy::CANCEL)); + QueueFrame(rst_frame.get(), stream->priority(), stream); - DeleteStream(stream_id, status); + CloseStream(stream_id, status); } bool SpdySession::IsStreamActive(spdy::SpdyStreamId stream_id) const { @@ -525,7 +543,6 @@ void SpdySession::OnReadComplete(int bytes_read) { void SpdySession::OnWriteComplete(int result) { DCHECK(write_pending_); DCHECK(in_flight_write_.size()); - DCHECK_NE(result, 0); // This shouldn't happen for write. write_pending_ = false; @@ -545,15 +562,17 @@ void SpdySession::OnWriteComplete(int result) { // We only notify the stream when we've fully written the pending frame. if (!in_flight_write_.buffer()->BytesRemaining()) { if (stream) { + // If we finished writing all the data from the buffer, it should not be + // the case that we wrote nothing. + DCHECK_NE(result, 0); + // Report the number of bytes written to the caller, but exclude the // frame size overhead. NOTE: if this frame was compressed the // reported bytes written is the compressed size, not the original // size. - if (result > 0) { - result = in_flight_write_.buffer()->size(); - DCHECK_GT(result, static_cast<int>(spdy::SpdyFrame::size())); - result -= static_cast<int>(spdy::SpdyFrame::size()); - } + result = in_flight_write_.buffer()->size(); + DCHECK_GT(result, static_cast<int>(spdy::SpdyFrame::size())); + result -= static_cast<int>(spdy::SpdyFrame::size()); // It is possible that the stream was cancelled while we were writing // to the socket. @@ -712,11 +731,11 @@ void SpdySession::CloseAllStreams(net::Error status) { DCHECK(stream); LOG(ERROR) << "ABANDONED (stream_id=" << stream->stream_id() << "): " << stream->path(); - DeleteStream(stream->stream_id(), status); + CloseStream(stream->stream_id(), status); } // TODO(erikchen): ideally stream->OnClose() is only ever called by - // DeleteStream, but pending streams fall into their own category for now. + // CloseStream, but pending streams fall into their own category for now. PendingStreamMap::iterator it; for (it = pending_streams_.begin(); it != pending_streams_.end(); ++it) { @@ -777,7 +796,7 @@ void SpdySession::ActivateStream(SpdyStream* stream) { active_streams_[id] = stream; } -void SpdySession::DeleteStream(spdy::SpdyStreamId id, int status) { +void SpdySession::CloseStream(spdy::SpdyStreamId id, int status) { // Remove the stream from pushed_streams_ and active_streams_. ActivePushedStreamList::iterator it; for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) { @@ -796,8 +815,13 @@ void SpdySession::DeleteStream(spdy::SpdyStreamId id, int status) { // If this is an active stream, call the callback. const scoped_refptr<SpdyStream> stream(it2->second); active_streams_.erase(it2); - if (stream) + if (stream) { + // This is the only place that should set the half_closed flag on the + // stream, and it should only be done once. + DCHECK(!stream->half_closed_client_side()); + stream->HalfCloseClientSide(); stream->OnClose(status); + } } void SpdySession::RemoveFromPool() { @@ -870,7 +894,7 @@ bool SpdySession::Respond(const spdy::SpdyHeaderBlock& headers, if (rv < 0) { DCHECK_NE(rv, ERR_IO_PENDING); const spdy::SpdyStreamId stream_id = stream->stream_id(); - DeleteStream(stream_id, rv); + CloseStream(stream_id, rv); return false; } return true; @@ -1049,7 +1073,7 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { *reinterpret_cast<const spdy::SpdySettingsControlFrame*>(frame)); break; case spdy::RST_STREAM: - OnFin(*reinterpret_cast<const spdy::SpdyRstStreamControlFrame*>(frame)); + OnRst(*reinterpret_cast<const spdy::SpdyRstStreamControlFrame*>(frame)); break; case spdy::SYN_STREAM: OnSyn(*reinterpret_cast<const spdy::SpdySynStreamControlFrame*>(frame), @@ -1065,7 +1089,7 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { } } -void SpdySession::OnFin(const spdy::SpdyRstStreamControlFrame& frame) { +void SpdySession::OnRst(const spdy::SpdyRstStreamControlFrame& frame) { spdy::SpdyStreamId stream_id = frame.stream_id(); LOG(INFO) << "Spdy Fin for stream " << stream_id; @@ -1090,7 +1114,7 @@ void SpdySession::OnFin(const spdy::SpdyRstStreamControlFrame& frame) { LOG(ERROR) << "Spdy stream closed: " << frame.status(); // TODO(mbelshe): Map from Spdy-protocol errors to something sensical. // For now, it doesn't matter much - it is a protocol error. - DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); + CloseStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); } } diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index d128661..23a92a8 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -91,8 +91,16 @@ class SpdySession : public base::RefCounted<SpdySession>, int WriteStreamData(spdy::SpdyStreamId stream_id, net::IOBuffer* data, int len); - // Close a stream. - void CloseStream(spdy::SpdyStreamId stream_id, int status); + // This marks the stream as half closed from the client side, and removes it + // from the active_streams_ map. + void CloseStream(spdy::SpdyStreamId id, int status); + // This is identical to CloseStream, except it also sends a Rst stream frame. + void CloseStreamAndSendRst(spdy::SpdyStreamId stream_id, int status); + + // Half close a stream. + void HalfCloseStreamClientSide(spdy::SpdyStreamId stream_id, int status); + void HalfCloseStreamServerSide(spdy::SpdyStreamId stream_id, int status); + // Check if a stream is active. bool IsStreamActive(spdy::SpdyStreamId stream_id) const; @@ -148,7 +156,7 @@ class SpdySession : public base::RefCounted<SpdySession>, const linked_ptr<spdy::SpdyHeaderBlock>& headers); void OnSynReply(const spdy::SpdySynReplyControlFrame& frame, const linked_ptr<spdy::SpdyHeaderBlock>& headers); - void OnFin(const spdy::SpdyRstStreamControlFrame& frame); + void OnRst(const spdy::SpdyRstStreamControlFrame& frame); void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame); void OnSettings(const spdy::SpdySettingsControlFrame& frame); @@ -181,7 +189,6 @@ class SpdySession : public base::RefCounted<SpdySession>, // Track active streams in the active stream list. void ActivateStream(SpdyStream* stream); - void DeleteStream(spdy::SpdyStreamId id, int status); // Removes this session from the session pool. void RemoveFromPool(); @@ -273,7 +280,7 @@ class SpdySession : public base::RefCounted<SpdySession>, int streams_initiated_count_; int streams_pushed_count_; int streams_pushed_and_claimed_count_; - int streams_abandoned_count_; + int streams_abandoned_count_; // # of streams that were pushed & abandoned. bool sent_settings_; // Did this session send settings when it started. bool received_settings_; // Did this session receive at least one settings // frame. diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc index 1166cd5..9eebfcd6 100644 --- a/net/spdy/spdy_stream.cc +++ b/net/spdy/spdy_stream.cc @@ -28,7 +28,9 @@ SpdyStream::SpdyStream( cancelled_(false), send_bytes_(0), recv_bytes_(0), - histograms_recorded_(false) {} + histograms_recorded_(false), + half_closed_client_side_(false), + half_closed_server_side_(false) {} SpdyStream::~SpdyStream() { DLOG(INFO) << "Deleting SpdyStream for stream " << stream_id_; @@ -58,8 +60,8 @@ void SpdyStream::SetDelegate(Delegate* delegate) { void SpdyStream::DetachDelegate() { delegate_ = NULL; - if (!cancelled()) - Cancel(); + if (!half_closed_client_side()) + session_->CloseStream(stream_id_, ERR_ABORTED); } const linked_ptr<spdy::SpdyHeaderBlock>& SpdyStream::spdy_headers() const { @@ -129,6 +131,7 @@ void SpdyStream::OnDataReceived(const char* data, int length) { // received. if (response_->empty()) { session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED); + // TODO(erikchen): We should close the session here. return; } @@ -159,11 +162,13 @@ void SpdyStream::OnDataReceived(const char* data, int length) { } void SpdyStream::OnWriteComplete(int status) { - // TODO(mbelshe): Check for cancellation here. If we're cancelled, we - // should discontinue the DoLoop. + // Behavior for status==0 is undefined, and should never happen. This should + // have already been checked prior to calling this function. + DCHECK_NE(status, 0); - // It is possible that this stream was closed while we had a write pending. - if (response_complete_) + // It is possible that this stream was closed or cancelled while we had a + // write pending. + if (response_complete_ || cancelled_) return; if (status > 0) @@ -184,7 +189,8 @@ void SpdyStream::OnClose(int status) { void SpdyStream::Cancel() { cancelled_ = true; - session_->CloseStream(stream_id_, ERR_ABORTED); + if (!half_closed_client_side()) + session_->CloseStreamAndSendRst(stream_id_, ERR_ABORTED); } int SpdyStream::DoSendRequest(bool has_upload_data) { diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h index 6183be2..88b3e04 100644 --- a/net/spdy/spdy_stream.h +++ b/net/spdy/spdy_stream.h @@ -109,8 +109,7 @@ class SpdyStream : public base::RefCounted<SpdyStream> { void SetRequestTime(base::Time t); // Called by the SpdySession when a response (e.g. a SYN_REPLY) has been - // received for this stream. |path| is the path of the URL for a server - // initiated stream, otherwise is empty. + // received for this stream. // Returns a status code. int OnResponseReceived(const spdy::SpdyHeaderBlock& response); @@ -120,7 +119,7 @@ class SpdyStream : public base::RefCounted<SpdyStream> { // |buffer| contains the data received. The stream must copy any data // from this buffer before returning from this callback. // |length| is the number of bytes received or an error. - // A zero-length count does not indicate end-of-stream. + // A length of zero indicates end-of-stream. void OnDataReceived(const char* buffer, int bytes); // Called by the SpdySession when a write has completed. This callback @@ -160,6 +159,20 @@ class SpdyStream : public base::RefCounted<SpdyStream> { bool response_complete() const { return response_complete_; } int response_status() const { return response_status_; } + // If this function returns true, then the spdy_session is guaranteeing that + // this spdy_stream has been removed from active_streams. + bool half_closed_client_side() const { return half_closed_client_side_; } + + bool half_closed_server_side() const { return half_closed_server_side_; } + bool half_closed_both_sides() const { + return half_closed_client_side_ && half_closed_server_side_; + } + + // These two functions should only ever be called by spdy_session, and should + // only be called once. + void HalfCloseClientSide() { half_closed_client_side_ = true; } + void HalfCloseServerSide() { half_closed_server_side_ = true; } + private: enum State { STATE_NONE, @@ -235,6 +248,8 @@ class SpdyStream : public base::RefCounted<SpdyStream> { bool histograms_recorded_; // Data received before delegate is attached. std::vector<scoped_refptr<IOBufferWithSize> > pending_buffers_; + bool half_closed_client_side_; + bool half_closed_server_side_; DISALLOW_COPY_AND_ASSIGN(SpdyStream); }; diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index 697a3a6..922bab6 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -385,6 +385,13 @@ spdy::SpdyFrame* ConstructSpdyBodyFrame() { return framer.CreateDataFrame(1, "hello!", 6, spdy::DATA_FLAG_FIN); } +// Constructs a single SPDY reset frame. +spdy::SpdyFrame* ConstructSpdyRstFrame() { + spdy::SpdyFramer framer; + return framer.CreateRstStream(1, spdy::CANCEL); + +} + // Construct an expected SPDY reply string. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -475,6 +482,12 @@ MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq) { true, req.data(), req.length() + spdy::SpdyFrame::size(), seq); } +// Create a MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(spdy::SpdyFrame* req, int seq) { + return MockWrite( + true, req->data(), req->length() + spdy::SpdyFrame::size(), seq); +} + // Create a MockRead from the given SpdyFrame. MockRead CreateMockRead(const spdy::SpdyFrame& resp) { return MockRead( diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h index 02981ce..dca5fe8 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util.h @@ -170,12 +170,18 @@ spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[], // Constructs a single SPDY data frame with the contents "hello!" spdy::SpdyFrame* ConstructSpdyBodyFrame(); +// Constructs a single SPDY reset frame. +spdy::SpdyFrame* ConstructSpdyRstFrame(); + // Create an async MockWrite from the given SpdyFrame. MockWrite CreateMockWrite(const spdy::SpdyFrame& req); // Create an async MockWrite from the given SpdyFrame and sequence number. MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq); +// Create an async MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(spdy::SpdyFrame* req, int seq); + // Create a MockRead from the given SpdyFrame. MockRead CreateMockRead(const spdy::SpdyFrame& resp); |