Index: net/spdy/spdy_session.cc |
=================================================================== |
--- net/spdy/spdy_session.cc (revision 105385) |
+++ net/spdy/spdy_session.cc (working copy) |
@@ -172,6 +172,23 @@ |
DISALLOW_COPY_AND_ASSIGN(NetLogSpdyRstParameter); |
}; |
+class NetLogSpdyPingParameter : public NetLog::EventParameters { |
+ public: |
+ explicit NetLogSpdyPingParameter(uint32 unique_id) : unique_id_(unique_id) {} |
+ |
+ virtual Value* ToValue() const { |
+ DictionaryValue* dict = new DictionaryValue(); |
+ dict->SetInteger("unique_id", unique_id_); |
+ return dict; |
+ } |
+ |
+ private: |
+ ~NetLogSpdyPingParameter() {} |
+ const uint32 unique_id_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NetLogSpdyPingParameter); |
+}; |
+ |
class NetLogSpdyGoAwayParameter : public NetLog::EventParameters { |
public: |
NetLogSpdyGoAwayParameter(spdy::SpdyStreamId last_stream_id, |
@@ -213,6 +230,18 @@ |
// static |
size_t SpdySession::max_concurrent_stream_limit_ = 256; |
+// static |
+bool SpdySession::enable_ping_based_connection_checking_ = true; |
+ |
+// static |
+int SpdySession::connection_at_risk_of_loss_ms_ = 0; |
+ |
+// static |
+int SpdySession::trailing_ping_delay_time_ms_ = 1000; |
+ |
+// static |
+int SpdySession::hung_interval_ms_ = 10000; |
+ |
SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair, |
SpdySessionPool* spdy_session_pool, |
SpdySettingsStorage* spdy_settings, |
@@ -246,6 +275,12 @@ |
sent_settings_(false), |
received_settings_(false), |
stalled_streams_(0), |
+ pings_in_flight_(0), |
+ next_ping_id_(1), |
+ received_data_time_(base::TimeTicks::Now()), |
+ trailing_ping_pending_(false), |
+ check_ping_status_pending_(false), |
+ last_sent_was_ping_(false), |
initial_send_window_size_(spdy::kSpdyStreamInitialWindowSize), |
initial_recv_window_size_(spdy::kSpdyStreamInitialWindowSize), |
net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)), |
@@ -474,6 +509,8 @@ |
const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id]; |
CHECK_EQ(stream->stream_id(), stream_id); |
+ SendPrefacePingIfNoneInFlight(); |
+ |
scoped_ptr<spdy::SpdySynStreamControlFrame> syn_frame( |
spdy_framer_.CreateSynStream( |
stream_id, 0, |
@@ -491,6 +528,7 @@ |
make_scoped_refptr( |
new NetLogSpdySynParameter(headers, flags, stream_id, 0))); |
} |
+ last_sent_was_ping_ = false; |
return ERR_IO_PENDING; |
} |
@@ -505,6 +543,8 @@ |
if (!stream) |
return ERR_INVALID_SPDY_STREAM; |
+ SendPrefacePingIfNoneInFlight(); |
+ |
if (len > kMaxSpdyFrameChunkSize) { |
len = kMaxSpdyFrameChunkSize; |
flags = static_cast<spdy::SpdyDataFlags>(flags & ~spdy::DATA_FLAG_FIN); |
@@ -544,6 +584,7 @@ |
scoped_ptr<spdy::SpdyDataFrame> frame( |
spdy_framer_.CreateDataFrame(stream_id, data->data(), len, flags)); |
QueueFrame(frame.get(), stream->priority(), stream); |
+ last_sent_was_ping_ = false; |
return ERR_IO_PENDING; |
} |
@@ -571,7 +612,7 @@ |
priority = stream->priority(); |
} |
QueueFrame(rst_frame.get(), priority, NULL); |
- |
+ last_sent_was_ping_ = false; |
DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); |
} |
@@ -612,6 +653,8 @@ |
bytes_received_ += bytes_read; |
+ received_data_time_ = base::TimeTicks::Now(); |
+ |
// The SpdyFramer will use callbacks onto |this| as it parses frames. |
// When errors occur, those callbacks can lead to teardown of all references |
// to |this|, so maintain a reference to self during this call for safe |
@@ -1229,6 +1272,9 @@ |
case spdy::GOAWAY: |
OnGoAway(*reinterpret_cast<const spdy::SpdyGoAwayControlFrame*>(frame)); |
break; |
+ case spdy::PING: |
+ OnPing(*reinterpret_cast<const spdy::SpdyPingControlFrame*>(frame)); |
+ break; |
case spdy::SETTINGS: |
OnSettings( |
*reinterpret_cast<const spdy::SpdySettingsControlFrame*>(frame)); |
@@ -1317,6 +1363,32 @@ |
// closed. |
} |
+void SpdySession::OnPing(const spdy::SpdyPingControlFrame& frame) { |
+ net_log_.AddEvent( |
+ NetLog::TYPE_SPDY_SESSION_PING, |
+ make_scoped_refptr(new NetLogSpdyPingParameter(frame.unique_id()))); |
+ |
+ // Send response to a PING from server. |
+ if (frame.unique_id() % 2 == 0) { |
+ WritePingFrame(frame.unique_id()); |
+ return; |
+ } |
+ |
+ --pings_in_flight_; |
+ if (pings_in_flight_ < 0) { |
+ CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR, true); |
+ return; |
+ } |
+ |
+ if (pings_in_flight_ > 0) |
+ return; |
+ |
+ if (last_sent_was_ping_) |
+ return; |
+ |
+ PlanToSendTrailingPing(); |
+} |
+ |
void SpdySession::OnSettings(const spdy::SpdySettingsControlFrame& frame) { |
spdy::SpdySettings settings; |
if (spdy_framer_.ParseSettings(&frame, &settings)) { |
@@ -1374,6 +1446,7 @@ |
scoped_ptr<spdy::SpdyWindowUpdateControlFrame> window_update_frame( |
spdy_framer_.CreateWindowUpdate(stream_id, delta_window_size)); |
QueueFrame(window_update_frame.get(), stream->priority(), stream); |
+ last_sent_was_ping_ = false; |
} |
// Given a cwnd that we would have sent to the server, modify it based on the |
@@ -1438,6 +1511,7 @@ |
spdy_framer_.CreateSettings(settings)); |
sent_settings_ = true; |
QueueFrame(settings_frame.get(), 0, NULL); |
+ last_sent_was_ping_ = false; |
} |
void SpdySession::HandleSettings(const spdy::SpdySettings& settings) { |
@@ -1455,6 +1529,101 @@ |
} |
} |
+void SpdySession::SendPrefacePingIfNoneInFlight() { |
+ if (pings_in_flight_ || trailing_ping_pending_ || |
+ !enable_ping_based_connection_checking_) |
+ return; |
+ |
+ const base::TimeDelta kConnectionAtRiskOfLoss = |
+ base::TimeDelta::FromMilliseconds(connection_at_risk_of_loss_ms_); |
+ |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ // If we haven't heard from server, then send a preface-PING. |
+ if ((now - received_data_time_) > kConnectionAtRiskOfLoss) |
+ SendPrefacePing(); |
+ |
+ PlanToSendTrailingPing(); |
+} |
+ |
+void SpdySession::SendPrefacePing() { |
+ WritePingFrame(next_ping_id_); |
+} |
+ |
+void SpdySession::PlanToSendTrailingPing() { |
+ if (trailing_ping_pending_) |
+ return; |
+ |
+ trailing_ping_pending_ = true; |
+ MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, |
+ method_factory_.NewRunnableMethod(&SpdySession::SendTrailingPing), |
+ trailing_ping_delay_time_ms_); |
+} |
+ |
+void SpdySession::SendTrailingPing() { |
+ DCHECK(trailing_ping_pending_); |
+ trailing_ping_pending_ = false; |
+ WritePingFrame(next_ping_id_); |
+} |
+ |
+void SpdySession::WritePingFrame(uint32 unique_id) { |
+ scoped_ptr<spdy::SpdyPingControlFrame> ping_frame( |
+ spdy_framer_.CreatePingFrame(next_ping_id_)); |
+ QueueFrame(ping_frame.get(), SPDY_PRIORITY_HIGHEST, NULL); |
+ |
+ if (net_log().IsLoggingAllEvents()) { |
+ net_log().AddEvent( |
+ NetLog::TYPE_SPDY_SESSION_PING, |
+ make_scoped_refptr(new NetLogSpdyPingParameter(next_ping_id_))); |
+ } |
+ if (unique_id % 2 != 0) { |
+ next_ping_id_ += 2; |
+ ++pings_in_flight_; |
+ last_sent_was_ping_ = true; |
+ PlanToCheckPingStatus(); |
+ } |
+} |
+ |
+void SpdySession::PlanToCheckPingStatus() { |
+ if (check_ping_status_pending_) |
+ return; |
+ |
+ check_ping_status_pending_ = true; |
+ MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, |
+ method_factory_.NewRunnableMethod( |
+ &SpdySession::CheckPingStatus, base::TimeTicks::Now()), |
+ hung_interval_ms_); |
+} |
+ |
+void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) { |
+ // Check if we got a response back for all PINGs we had sent. |
+ if (pings_in_flight_ == 0) { |
+ check_ping_status_pending_ = false; |
+ return; |
+ } |
+ |
+ DCHECK(check_ping_status_pending_); |
+ |
+ const base::TimeDelta kHungInterval = |
+ base::TimeDelta::FromMilliseconds(hung_interval_ms_); |
+ |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ base::TimeDelta delay = kHungInterval - (now - received_data_time_); |
+ |
+ if (delay.InMilliseconds() < 0 || received_data_time_ < last_check_time) { |
+ DCHECK(now - received_data_time_ > kHungInterval); |
+ CloseSessionOnError(net::ERR_SPDY_PING_FAILED, true); |
+ return; |
+ } |
+ |
+ // Check the status of connection after a delay. |
+ MessageLoop::current()->PostDelayedTask( |
+ FROM_HERE, |
+ method_factory_.NewRunnableMethod(&SpdySession::CheckPingStatus, now), |
+ delay.InMilliseconds()); |
+} |
+ |
void SpdySession::RecordHistograms() { |
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession", |
streams_initiated_count_, |