summaryrefslogtreecommitdiffstats
path: root/net/spdy
diff options
context:
space:
mode:
authorbengr@google.com <bengr@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-18 22:24:03 +0000
committerbengr@google.com <bengr@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-18 22:24:03 +0000
commite897bd6c7bb533f62ed909fdb1499176dafb1683 (patch)
tree0d4129e0a1fb671e171b8dac85e5e3c4988f8d41 /net/spdy
parent3f2b51a7e6433c7a15edbcfafab9097169fe5c80 (diff)
downloadchromium_src-e897bd6c7bb533f62ed909fdb1499176dafb1683.zip
chromium_src-e897bd6c7bb533f62ed909fdb1499176dafb1683.tar.gz
chromium_src-e897bd6c7bb533f62ed909fdb1499176dafb1683.tar.bz2
Remove old unclaimed streams pushed by a SPDY server.
BUG=113424 TEST=SpdySessionSpdy[2,3]Test.DeleteExpiredPushStreams Review URL: https://chromiumcodereview.appspot.com/10702189 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147321 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/spdy')
-rw-r--r--net/spdy/spdy_session.cc68
-rw-r--r--net/spdy/spdy_session.h21
-rw-r--r--net/spdy/spdy_session_spdy2_unittest.cc72
-rw-r--r--net/spdy/spdy_session_spdy3_unittest.cc76
-rw-r--r--net/spdy/spdy_stream.h1
5 files changed, 232 insertions, 6 deletions
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index c127cdf..5528617 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -45,6 +45,9 @@ const int kReadBufferSize = 8 * 1024;
const int kDefaultConnectionAtRiskOfLossSeconds = 10;
const int kHungIntervalSeconds = 10;
+// Minimum seconds that unclaimed pushed streams will be kept in memory.
+const int kMinPushedStreamLifetimeSeconds = 300;
+
Value* NetLogSpdySynCallback(const SpdyHeaderBlock* headers,
bool fin,
bool unidirectional,
@@ -182,6 +185,10 @@ size_t g_max_concurrent_stream_limit = 256;
size_t g_default_initial_rcv_window_size = 10 * 1024 * 1024; // 10MB
bool g_enable_ping_based_connection_checking = true;
+typedef base::TimeTicks (*ExternalTimeFunc)(void);
+
+static ExternalTimeFunc g_time_func = base::TimeTicks::Now;
+
} // namespace
// static
@@ -218,6 +225,23 @@ void SpdySession::ResetStaticSettingsToInit() {
g_max_concurrent_stream_limit = 256;
g_default_initial_rcv_window_size = kSpdyStreamInitialWindowSize;
g_enable_ping_based_connection_checking = true;
+ g_time_func = base::TimeTicks::Now;
+}
+
+// Outside of tests, g_time_func will always be base::TimeTicks::Now.
+// When performing linker optimization for the main executable, the compiler
+// should be able to see that set_time_func() is an uncalled function, that
+// the static .cc variable never changes, and thus that the extra pointer
+// indirection can be removed.
+
+
+
+SpdySession::TimeFunc SpdySession::set_time_func(
+ SpdySession::TimeFunc time_func) {
+ SpdySession::TimeFunc old_time_func =
+ static_cast<SpdySession::TimeFunc>(g_time_func);
+ g_time_func = static_cast<ExternalTimeFunc>(time_func);
+ return old_time_func;
}
SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
@@ -270,6 +294,8 @@ SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
net_log_.BeginEvent(
NetLog::TYPE_SPDY_SESSION,
base::Bind(&NetLogSpdySessionCallback, &host_port_proxy_pair_));
+ next_unclaimed_push_stream_sweep_time_ = g_time_func() +
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
// TODO(mbelshe): consider randomization of the stream_hi_water_mark.
}
@@ -1118,7 +1144,7 @@ void SpdySession::DeleteStream(SpdyStreamId id, int status) {
PushedStreamMap::iterator it;
for (it = unclaimed_pushed_streams_.begin();
it != unclaimed_pushed_streams_.end(); ++it) {
- scoped_refptr<SpdyStream> curr = it->second;
+ scoped_refptr<SpdyStream> curr = it->second.first;
if (id == curr->stream_id()) {
unclaimed_pushed_streams_.erase(it);
break;
@@ -1154,7 +1180,7 @@ scoped_refptr<SpdyStream> SpdySession::GetActivePushStream(
PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path);
if (it != unclaimed_pushed_streams_.end()) {
net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM);
- scoped_refptr<SpdyStream> stream = it->second;
+ scoped_refptr<SpdyStream> stream = it->second.first;
unclaimed_pushed_streams_.erase(it);
used_push_streams.Increment();
return stream;
@@ -1363,7 +1389,11 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id,
stream->set_send_window_size(initial_send_window_size_);
stream->set_recv_window_size(initial_recv_window_size_);
- unclaimed_pushed_streams_[url] = stream;
+ DeleteExpiredPushedStreams();
+ unclaimed_pushed_streams_[url] =
+ std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> (
+ stream, g_time_func());
+
ActivateStream(stream);
stream->set_response_received();
@@ -1376,6 +1406,38 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id,
push_requests.Increment();
}
+void SpdySession::DeleteExpiredPushedStreams() {
+ if (unclaimed_pushed_streams_.empty())
+ return;
+
+ // Check that adequate time has elapsed since the last sweep.
+ if (g_time_func() < next_unclaimed_push_stream_sweep_time_)
+ return;
+
+ // Delete old streams.
+ base::TimeTicks minimum_freshness = g_time_func() -
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+ PushedStreamMap::iterator it;
+ for (it = unclaimed_pushed_streams_.begin();
+ it != unclaimed_pushed_streams_.end(); ) {
+ const scoped_refptr<SpdyStream>& stream = it->second.first;
+ base::TimeTicks creation_time = it->second.second;
+ // DeleteStream() will invalidate the current iterator, so move to next.
+ ++it;
+ if (minimum_freshness > creation_time) {
+ DeleteStream(stream->stream_id(), ERR_INVALID_SPDY_STREAM);
+ base::StatsCounter abandoned_push_streams(
+ "spdy.abandoned_push_streams");
+ base::StatsCounter abandoned_streams("spdy.abandoned_streams");
+ abandoned_push_streams.Increment();
+ abandoned_streams.Increment();
+ streams_abandoned_count_++;
+ }
+ }
+ next_unclaimed_push_stream_sweep_time_ = g_time_func() +
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+}
+
void SpdySession::OnSynReply(SpdyStreamId stream_id,
bool fin,
const SpdyHeaderBlock& headers) {
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
index 9b4cf73..2975203 100644
--- a/net/spdy/spdy_session.h
+++ b/net/spdy/spdy_session.h
@@ -14,6 +14,7 @@
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "base/time.h"
#include "net/base/io_buffer.h"
#include "net/base/load_states.h"
#include "net/base/net_errors.h"
@@ -322,9 +323,11 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, Ping);
FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, FailedPing);
FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, GetActivePushStream);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, DeleteExpiredPushStreams);
FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, Ping);
FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, FailedPing);
FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, GetActivePushStream);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, DeleteExpiredPushStreams);
struct PendingCreateStream {
PendingCreateStream(const GURL& url, RequestPriority priority,
@@ -346,11 +349,11 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
const BoundNetLog* stream_net_log;
CompletionCallback callback;
};
- typedef std::queue<PendingCreateStream, std::list< PendingCreateStream> >
+ typedef std::queue<PendingCreateStream, std::list<PendingCreateStream> >
PendingCreateStreamQueue;
typedef std::map<int, scoped_refptr<SpdyStream> > ActiveStreamMap;
- // Only HTTP push a stream.
- typedef std::map<std::string, scoped_refptr<SpdyStream> > PushedStreamMap;
+ typedef std::map<std::string,
+ std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> > PushedStreamMap;
typedef std::priority_queue<SpdyIOBuffer> OutputQueue;
struct CallbackResultPair {
@@ -372,6 +375,8 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
CLOSED
};
+ typedef base::TimeTicks (*TimeFunc)(void);
+
virtual ~SpdySession();
void ProcessPendingCreateStreams();
@@ -461,6 +466,9 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
// can be deferred to the MessageLoop, so we avoid re-entrancy problems.
void InvokeUserStreamCreationCallback(scoped_refptr<SpdyStream>* stream);
+ // Remove old unclaimed pushed streams.
+ void DeleteExpiredPushedStreams();
+
// BufferedSpdyFramerVisitorInterface:
virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE;
virtual void OnStreamError(SpdyStreamId stream_id,
@@ -500,6 +508,9 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
// --------------------------
// Helper methods for testing
// --------------------------
+
+ static TimeFunc set_time_func(TimeFunc new_time_func);
+
void set_connection_at_risk_of_loss_time(base::TimeDelta duration) {
connection_at_risk_of_loss_time_ = duration;
}
@@ -620,6 +631,10 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
// This is the last time we had activity in the session.
base::TimeTicks last_activity_time_;
+ // This is the next time that unclaimed push streams should be checked for
+ // expirations.
+ base::TimeTicks next_unclaimed_push_stream_sweep_time_;
+
// Indicate if we have already scheduled a delayed task to check the ping
// status.
bool check_ping_status_pending_;
diff --git a/net/spdy/spdy_session_spdy2_unittest.cc b/net/spdy/spdy_session_spdy2_unittest.cc
index a884854f..4171d9f 100644
--- a/net/spdy/spdy_session_spdy2_unittest.cc
+++ b/net/spdy/spdy_session_spdy2_unittest.cc
@@ -15,6 +15,15 @@
using namespace net::test_spdy2;
+namespace {
+
+base::TimeTicks the_near_future() {
+ return base::TimeTicks::Now() +
+ base::TimeDelta::FromSeconds(301);
+}
+
+} // namespace
+
namespace net {
// TODO(cbentzel): Expose compression setter/getter in public SpdySession
@@ -251,6 +260,69 @@ TEST_F(SpdySessionSpdy2Test, Ping) {
session = NULL;
}
+TEST_F(SpdySessionSpdy2Test, DeleteExpiredPushStreams) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.google.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());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ // Give the session a SPDY2 framer.
+ session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(2));
+
+ // Create the associated stream and add to active streams.
+ scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
+ (*request_headers)["scheme"] = "http";
+ (*request_headers)["host"] = "www.google.com";
+ (*request_headers)["url"] = "/";
+
+ scoped_refptr<SpdyStream> stream(
+ new SpdyStream(session, 1, false, session->net_log_));
+ stream->set_spdy_headers(request_headers.Pass());
+ session->ActivateStream(stream);
+
+ SpdyHeaderBlock headers;
+ headers["url"] = "http://www.google.com/a.dat";
+ session->OnSynStream(2, 1, 0, 0, true, false, headers);
+
+ // Verify that there is one unclaimed push stream.
+ EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+ SpdySession::PushedStreamMap::iterator iter =
+ session->unclaimed_pushed_streams_.find("http://www.google.com/a.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Shift time.
+ SpdySession::set_time_func(the_near_future);
+
+ headers["url"] = "http://www.google.com/b.dat";
+ session->OnSynStream(4, 1, 0, 0, true, false, headers);
+
+ // Verify that the second pushed stream evicted the first pushed stream.
+ EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+ iter = session->unclaimed_pushed_streams_.find("http://www.google.com/b.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ SpdySession::ResetStaticSettingsToInit();
+
+ // Delete the session.
+ session = NULL;
+}
+
+
TEST_F(SpdySessionSpdy2Test, FailedPing) {
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
diff --git a/net/spdy/spdy_session_spdy3_unittest.cc b/net/spdy/spdy_session_spdy3_unittest.cc
index 2d8d969..d8d526c 100644
--- a/net/spdy/spdy_session_spdy3_unittest.cc
+++ b/net/spdy/spdy_session_spdy3_unittest.cc
@@ -15,6 +15,15 @@
using namespace net::test_spdy3;
+namespace {
+
+base::TimeTicks the_near_future() {
+ return base::TimeTicks::Now() +
+ base::TimeDelta::FromSeconds(301);
+}
+
+} // namespace
+
namespace net {
// TODO(cbentzel): Expose compression setter/getter in public SpdySession
@@ -251,6 +260,73 @@ TEST_F(SpdySessionSpdy3Test, Ping) {
session = NULL;
}
+TEST_F(SpdySessionSpdy3Test, DeleteExpiredPushStreams) {
+ SpdySessionDependencies session_deps;
+ session_deps.host_resolver->set_synchronous_mode(true);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> http_session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ const std::string kTestHost("www.google.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());
+ EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+ scoped_refptr<SpdySession> session =
+ spdy_session_pool->Get(pair, BoundNetLog());
+ EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+ // Give the session a SPDY3 framer.
+ session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(3));
+
+ // Create the associated stream and add to active streams.
+ scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
+ (*request_headers)[":scheme"] = "http";
+ (*request_headers)[":host"] = "www.google.com";
+ (*request_headers)[":path"] = "/";
+
+ scoped_refptr<SpdyStream> stream(
+ new SpdyStream(session, 1, false, session->net_log_));
+ stream->set_spdy_headers(request_headers.Pass());
+ session->ActivateStream(stream);
+
+ SpdyHeaderBlock headers;
+ headers[":scheme"] = "http";
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/a.dat";
+ session->OnSynStream(2, 1, 0, 0, true, false, headers);
+
+ // Verify that there is one unclaimed push stream.
+ EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+ SpdySession::PushedStreamMap::iterator iter =
+ session->unclaimed_pushed_streams_.find("http://www.google.com/a.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Shift time.
+ SpdySession::set_time_func(the_near_future);
+
+ headers[":scheme"] = "http";
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/b.dat";
+ session->OnSynStream(4, 1, 0, 0, true, false, headers);
+
+ // Verify that the second pushed stream evicted the first pushed stream.
+ EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+ iter = session->unclaimed_pushed_streams_.find("http://www.google.com/b.dat");
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ SpdySession::ResetStaticSettingsToInit();
+
+ // Delete the session.
+ session = NULL;
+}
+
+
TEST_F(SpdySessionSpdy3Test, FailedPing) {
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
index 1c6625b..40e436a 100644
--- a/net/spdy/spdy_stream.h
+++ b/net/spdy/spdy_stream.h
@@ -267,6 +267,7 @@ class NET_EXPORT_PRIVATE SpdyStream
};
friend class base::RefCounted<SpdyStream>;
+
virtual ~SpdyStream();
// If the stream is stalled and if |send_window_size_| is positive, then set