Commit e1f58efa authored by rch@chromium.org's avatar rch@chromium.org

Change the stream_id for streams which are serialized out of order.

BUG=111708
TEST=SpdySessionSpdy\*Test.OutOfOrderSynStreams


Review URL: https://chromiumcodereview.appspot.com/10382107

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@137180 0039d316-1c4b-4281-b951-d872f2087c98
parent 5c67c703
......@@ -939,6 +939,14 @@ EVENT_TYPE(SPDY_SESSION)
// }
EVENT_TYPE(SPDY_SESSION_SYN_STREAM)
// This event is sent when a SPDY SYN_STREAM is renumbered.
// The following parameters are attached:
// {
// "old_id": <The old stream id>,
// "new_id": <The new stream id>,
// }
EVENT_TYPE(SPDY_SESSION_SYN_STREAM_RENUMBER)
// This event is sent for a SPDY SYN_STREAM pushed by the server, where a
// net::URLRequest is already waiting for the stream.
// The following parameters are attached:
......
......@@ -5561,4 +5561,90 @@ TEST_P(SpdyNetworkTransactionSpdy2Test, RetryAfterRefused) {
EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
}
TEST_P(SpdyNetworkTransactionSpdy2Test, OutOfOrderSynStream) {
// This first request will start to establish the SpdySession.
// Then we will start the second (MEDIUM priority) and then third
// (HIGHEST priority) request in such a way that the third will actually
// start before the second, causing the second to be re-numbered.
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
CreateMockWrite(*req2, 3),
CreateMockWrite(*req3, 4),
};
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 7));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(7, true));
scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
MockRead reads[] = {
CreateMockRead(*resp1, 1),
CreateMockRead(*body1, 2),
CreateMockRead(*resp2, 5),
CreateMockRead(*body2, 6),
CreateMockRead(*resp3, 7),
CreateMockRead(*body3, 8),
MockRead(ASYNC, 0, 9) // EOF
};
scoped_refptr<DeterministicSocketData> data(
new DeterministicSocketData(reads, arraysize(reads),
writes, arraysize(writes)));
NormalSpdyTransactionHelper helper(CreateGetRequest(),
BoundNetLog(), GetParam(), NULL);
helper.SetDeterministic();
helper.RunPreTestSetup();
helper.AddDeterministicData(data.get());
// Start the first transaction to set up the SpdySession
HttpNetworkTransaction* trans = helper.trans();
TestCompletionCallback callback;
HttpRequestInfo info1 = CreateGetRequest();
info1.priority = LOWEST;
int rv = trans->Start(&info1, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Run the message loop, but do not allow the write to complete.
// This leaves the SpdySession with a write pending, which prevents
// SpdySession from attempting subsequent writes until this write completes.
MessageLoop::current()->RunAllPending();
// Now, start both new transactions
HttpRequestInfo info2 = CreateGetRequest();
info2.priority = MEDIUM;
TestCompletionCallback callback2;
scoped_ptr<HttpNetworkTransaction> trans2(
new HttpNetworkTransaction(helper.session()));
rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
MessageLoop::current()->RunAllPending();
HttpRequestInfo info3 = CreateGetRequest();
info3.priority = HIGHEST;
TestCompletionCallback callback3;
scoped_ptr<HttpNetworkTransaction> trans3(
new HttpNetworkTransaction(helper.session()));
rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
MessageLoop::current()->RunAllPending();
// We now have two SYN_STREAM frames queued up which will be
// dequeued only once the first write completes, which we
// now allow to happen.
data->RunFor(2);
EXPECT_EQ(OK, callback.WaitForResult());
// And now we can allow everything else to run to completion.
data->SetStop(10);
data->Run();
EXPECT_EQ(OK, callback2.WaitForResult());
EXPECT_EQ(OK, callback3.WaitForResult());
helper.VerifyDataConsumed();
}
} // namespace net
......@@ -6144,4 +6144,90 @@ TEST_P(SpdyNetworkTransactionSpdy3Test, RetryAfterRefused) {
EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
}
TEST_P(SpdyNetworkTransactionSpdy3Test, OutOfOrderSynStream) {
// This first request will start to establish the SpdySession.
// Then we will start the second (MEDIUM priority) and then third
// (HIGHEST priority) request in such a way that the third will actually
// start before the second, causing the second to be re-numbered.
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
CreateMockWrite(*req2, 3),
CreateMockWrite(*req3, 4),
};
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 7));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(7, true));
scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
MockRead reads[] = {
CreateMockRead(*resp1, 1),
CreateMockRead(*body1, 2),
CreateMockRead(*resp2, 5),
CreateMockRead(*body2, 6),
CreateMockRead(*resp3, 7),
CreateMockRead(*body3, 8),
MockRead(ASYNC, 0, 9) // EOF
};
scoped_refptr<DeterministicSocketData> data(
new DeterministicSocketData(reads, arraysize(reads),
writes, arraysize(writes)));
NormalSpdyTransactionHelper helper(CreateGetRequest(),
BoundNetLog(), GetParam(), NULL);
helper.SetDeterministic();
helper.RunPreTestSetup();
helper.AddDeterministicData(data.get());
// Start the first transaction to set up the SpdySession
HttpNetworkTransaction* trans = helper.trans();
TestCompletionCallback callback;
HttpRequestInfo info1 = CreateGetRequest();
info1.priority = LOWEST;
int rv = trans->Start(&info1, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Run the message loop, but do not allow the write to complete.
// This leaves the SpdySession with a write pending, which prevents
// SpdySession from attempting subsequent writes until this write completes.
MessageLoop::current()->RunAllPending();
// Now, start both new transactions
HttpRequestInfo info2 = CreateGetRequest();
info2.priority = MEDIUM;
TestCompletionCallback callback2;
scoped_ptr<HttpNetworkTransaction> trans2(
new HttpNetworkTransaction(helper.session()));
rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
MessageLoop::current()->RunAllPending();
HttpRequestInfo info3 = CreateGetRequest();
info3.priority = HIGHEST;
TestCompletionCallback callback3;
scoped_ptr<HttpNetworkTransaction> trans3(
new HttpNetworkTransaction(helper.session()));
rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
MessageLoop::current()->RunAllPending();
// We now have two SYN_STREAM frames queued up which will be
// dequeued only once the first write completes, which we
// now allow to happen.
data->RunFor(2);
EXPECT_EQ(OK, callback.WaitForResult());
// And now we can allow everything else to run to completion.
data->SetStop(10);
data->Run();
EXPECT_EQ(OK, callback2.WaitForResult());
EXPECT_EQ(OK, callback3.WaitForResult());
helper.VerifyDataConsumed();
}
} // namespace net
......@@ -67,6 +67,23 @@ Value* NetLogSpdySynParameter::ToValue() const {
NetLogSpdySynParameter::~NetLogSpdySynParameter() {}
NetLogSpdySynRenumberParameter::NetLogSpdySynRenumberParameter(
SpdyStreamId old_id,
SpdyStreamId new_id)
: old_id_(old_id),
new_id_(new_id) {
}
Value* NetLogSpdySynRenumberParameter::ToValue() const {
DictionaryValue* dict = new DictionaryValue();
dict->SetInteger("old_id", old_id_);
dict->SetInteger("new_id", new_id_);
return dict;
}
NetLogSpdySynRenumberParameter::~NetLogSpdySynRenumberParameter() {}
NetLogSpdyCredentialParameter::NetLogSpdyCredentialParameter(
size_t slot,
const std::string& origin)
......@@ -348,6 +365,7 @@ SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
read_buffer_(new IOBuffer(kReadBufferSize)),
read_pending_(false),
stream_hi_water_mark_(1), // Always start at 1 for the first stream id.
last_syn_stream_id_(0),
write_pending_(false),
delayed_write_pending_(false),
is_secure_(false),
......@@ -1016,6 +1034,33 @@ void SpdySession::WriteSocket() {
// which is now. At this time, we don't compress our data frames.
SpdyFrame uncompressed_frame(next_buffer.buffer()->data(), false);
size_t size;
if (uncompressed_frame.is_control_frame()) {
SpdyControlFrame control_frame(next_buffer.buffer()->data(), false);
if (control_frame.type() == SYN_STREAM) {
SpdySynStreamControlFrame syn_stream(next_buffer.buffer()->data(),
false);
SpdyStreamId id = syn_stream.stream_id();
DCHECK(IsStreamActive(id));
if (id < last_syn_stream_id_) {
SpdyStreamId old_id = id;
// need to play some games to change the stream_id
scoped_refptr<SpdyStream> stream = active_streams_[id];
active_streams_.erase(id);
id = GetNewStreamId();
syn_stream.set_stream_id(id);
stream->set_stream_id(id);
ActivateStream(stream);
if (net_log().IsLoggingAllEvents()) {
net_log().AddEvent(
NetLog::TYPE_SPDY_SESSION_SYN_STREAM_RENUMBER,
make_scoped_refptr(
new NetLogSpdySynRenumberParameter(old_id, id)));
}
}
last_syn_stream_id_ = id;
}
}
if (buffered_spdy_framer_->IsCompressible(uncompressed_frame)) {
DCHECK(uncompressed_frame.is_control_frame());
scoped_ptr<SpdyFrame> compressed_frame(
......
......@@ -540,6 +540,8 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
bool read_pending_;
int stream_hi_water_mark_; // The next stream id to use.
// The stream id of the last SYN_STREAM frame written on this session.
SpdyStreamId last_syn_stream_id_;
// Queue, for each priority, of pending Create Streams that have not
// yet been satisfied
......@@ -687,6 +689,24 @@ class NetLogSpdySynParameter : public NetLog::EventParameters {
};
class NetLogSpdySynRenumberParameter : public NetLog::EventParameters {
public:
NetLogSpdySynRenumberParameter(SpdyStreamId old_id,
SpdyStreamId new_id);
virtual base::Value* ToValue() const OVERRIDE;
protected:
virtual ~NetLogSpdySynRenumberParameter();
private:
const SpdyStreamId old_id_;
const SpdyStreamId new_id_;
DISALLOW_COPY_AND_ASSIGN(NetLogSpdySynRenumberParameter);
};
class NetLogSpdyCredentialParameter : public NetLog::EventParameters {
public:
NetLogSpdyCredentialParameter(size_t slot, const std::string& origin);
......
......@@ -1036,4 +1036,106 @@ TEST_F(SpdySessionSpdy2Test, CloseSessionOnError) {
EXPECT_EQ(ERR_CONNECTION_CLOSED, request_params->status());
}
TEST_F(SpdySessionSpdy2Test, OutOfOrderSynStreams) {
// Construct the request.
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 3, HIGHEST));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req1, 1),
CreateMockWrite(*req2, 2),
};
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(3, true));
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 5));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(5, true));
MockRead reads[] = {
CreateMockRead(*resp1, 3),
CreateMockRead(*body1, 4),
CreateMockRead(*resp2, 5),
CreateMockRead(*body2, 6),
MockRead(ASYNC, 0, 7) // EOF
};
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
StaticSocketDataProvider data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_refptr<HttpNetworkSession> http_session(
SpdySessionDependencies::SpdyCreateSession(&session_deps));
const std::string kTestHost("www.foo.com");
const int kTestPort = 80;
HostPortPair test_host_port_pair(kTestHost, kTestPort);
HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
// Create a session.
EXPECT_FALSE(spdy_session_pool->HasSession(pair));
scoped_refptr<SpdySession> session =
spdy_session_pool->Get(pair, BoundNetLog());
ASSERT_TRUE(spdy_session_pool->HasSession(pair));
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(test_host_port_pair,
MEDIUM,
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM, CompletionCallback(),
http_session->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
scoped_refptr<SpdyStream> spdy_stream1;
TestCompletionCallback callback1;
GURL url1("http://www.google.com");
EXPECT_EQ(OK, session->CreateStream(url1, LOWEST, &spdy_stream1,
BoundNetLog(), callback1.callback()));
EXPECT_EQ(1u, spdy_stream1->stream_id());
scoped_refptr<SpdyStream> spdy_stream2;
TestCompletionCallback callback2;
GURL url2("http://www.google.com");
EXPECT_EQ(OK, session->CreateStream(url2, HIGHEST, &spdy_stream2,
BoundNetLog(), callback2.callback()));
EXPECT_EQ(3u, spdy_stream2->stream_id());
linked_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
spdy_stream1->set_spdy_headers(headers);
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream2->set_spdy_headers(headers);
EXPECT_TRUE(spdy_stream2->HasUrl());
spdy_stream1->SendRequest(false);
spdy_stream2->SendRequest(false);
MessageLoop::current()->RunAllPending();
EXPECT_EQ(5u, spdy_stream1->stream_id());
EXPECT_EQ(3u, spdy_stream2->stream_id());
spdy_stream1->Cancel();
spdy_stream1 = NULL;
spdy_stream2->Cancel();
spdy_stream2 = NULL;
}
} // namespace net
......@@ -1192,4 +1192,106 @@ TEST_F(SpdySessionSpdy3Test, UpdateStreamsSendWindowSize) {
spdy_stream2 = NULL;
}
TEST_F(SpdySessionSpdy3Test, OutOfOrderSynStreams) {
// Construct the request.
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 3, HIGHEST));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req1, 1),
CreateMockWrite(*req2, 2),
};
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(3, true));
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 5));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(5, true));
MockRead reads[] = {
CreateMockRead(*resp1, 3),
CreateMockRead(*body1, 4),
CreateMockRead(*resp2, 5),
CreateMockRead(*body2, 6),
MockRead(ASYNC, 0, 7) // EOF
};
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
StaticSocketDataProvider data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_refptr<HttpNetworkSession> http_session(
SpdySessionDependencies::SpdyCreateSession(&session_deps));
const std::string kTestHost("www.foo.com");
const int kTestPort = 80;
HostPortPair test_host_port_pair(kTestHost, kTestPort);
HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
// Create a session.
EXPECT_FALSE(spdy_session_pool->HasSession(pair));
scoped_refptr<SpdySession> session =
spdy_session_pool->Get(pair, BoundNetLog());
ASSERT_TRUE(spdy_session_pool->HasSession(pair));
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(test_host_port_pair,
MEDIUM,
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM, CompletionCallback(),
http_session->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
scoped_refptr<SpdyStream> spdy_stream1;
TestCompletionCallback callback1;
GURL url1("http://www.google.com");
EXPECT_EQ(OK, session->CreateStream(url1, LOWEST, &spdy_stream1,
BoundNetLog(), callback1.callback()));
EXPECT_EQ(1u, spdy_stream1->stream_id());
scoped_refptr<SpdyStream> spdy_stream2;
TestCompletionCallback callback2;
GURL url2("http://www.google.com");
EXPECT_EQ(OK, session->CreateStream(url2, HIGHEST, &spdy_stream2,
BoundNetLog(), callback2.callback()));
EXPECT_EQ(3u, spdy_stream2->stream_id());
linked_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)[":method"] = "GET";
(*headers)[":scheme"] = url1.scheme();
(*headers)[":host"] = url1.host();
(*headers)[":path"] = url1.path();
(*headers)[":version"] = "HTTP/1.1";
spdy_stream1->set_spdy_headers(headers);
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream2->set_spdy_headers(headers);
EXPECT_TRUE(spdy_stream2->HasUrl());
spdy_stream1->SendRequest(false);
spdy_stream2->SendRequest(false);
MessageLoop::current()->RunAllPending();
EXPECT_EQ(5u, spdy_stream1->stream_id());
EXPECT_EQ(3u, spdy_stream2->stream_id());
spdy_stream1->Cancel();
spdy_stream1 = NULL;
spdy_stream2->Cancel();
spdy_stream2 = NULL;
}
} // namespace net
......@@ -519,6 +519,9 @@ int SpdyStream::SendRequest(bool has_upload_data) {
int SpdyStream::WriteStreamData(IOBuffer* data, int length,
SpdyDataFlags flags) {
// Until the headers have been completely sent, we can not be sure
// that our stream_id is correct.
DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
return session_->WriteStreamData(stream_id_, data, length, flags);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment