diff options
author | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-16 08:05:08 +0000 |
---|---|---|
committer | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-16 08:05:08 +0000 |
commit | 8753a129c18923d9bf1805e1431bfab3e4b6aabc (patch) | |
tree | c3785516a42b354d763f73c06548487923805b31 /net | |
parent | b404b1f2399b7183f7ec54d42c7797ee54ffcd13 (diff) | |
download | chromium_src-8753a129c18923d9bf1805e1431bfab3e4b6aabc.zip chromium_src-8753a129c18923d9bf1805e1431bfab3e4b6aabc.tar.gz chromium_src-8753a129c18923d9bf1805e1431bfab3e4b6aabc.tar.bz2 |
Send PING to check the status of the SPDY connection.
Make SpdySessions more resilient to connections dying.
BUG=89725,34752
R=willchan,jar
TEST=network unit tests
Review URL: http://codereview.chromium.org/8230037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@105723 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/net_error_list.h | 3 | ||||
-rw-r--r-- | net/base/net_log_event_type_list.h | 7 | ||||
-rw-r--r-- | net/base/run_all_unittests.cc | 3 | ||||
-rw-r--r-- | net/http/http_network_layer.cc | 3 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 1 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 171 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 125 | ||||
-rw-r--r-- | net/spdy/spdy_session_unittest.cc | 143 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 7 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 4 |
10 files changed, 462 insertions, 5 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index d0dae4d..cc012e0 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -505,6 +505,9 @@ NET_ERROR(RESPONSE_HEADERS_MULTIPLE_LOCATION, -350) // user-visible error. NET_ERROR(SPDY_SERVER_REFUSED_STREAM, -351) +// SPDY server didn't respond to the PING message. +NET_ERROR(SPDY_PING_FAILED, -352) + // The cache does not have the requested entry. NET_ERROR(CACHE_MISS, -400) diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h index b03bc27..6bf62bb 100644 --- a/net/base/net_log_event_type_list.h +++ b/net/base/net_log_event_type_list.h @@ -851,6 +851,13 @@ EVENT_TYPE(SPDY_SESSION_RST_STREAM) // } EVENT_TYPE(SPDY_SESSION_SEND_RST_STREAM) +// Sending of a SPDY PING frame. +// The following parameters are attached: +// { +// "unique_id": <The unique id of the PING message>, +// } +EVENT_TYPE(SPDY_SESSION_PING) + // Receipt of a SPDY GOAWAY frame. // The following parameters are attached: // { diff --git a/net/base/run_all_unittests.cc b/net/base/run_all_unittests.cc index a844354..bcadda0 100644 --- a/net/base/run_all_unittests.cc +++ b/net/base/run_all_unittests.cc @@ -7,14 +7,17 @@ #include "crypto/nss_util.h" #include "net/base/net_test_suite.h" #include "net/socket/client_socket_pool_base.h" +#include "net/spdy/spdy_session.h" using net::internal::ClientSocketPoolBaseHelper; +using net::SpdySession; int main(int argc, char** argv) { // Record histograms, so we can get histograms data in tests. base::StatisticsRecorder recorder; NetTestSuite test_suite(argc, argv); ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false); + SpdySession::set_enable_ping_based_connection_checking(false); #if defined(OS_WIN) // We want to be sure to init NSPR on the main thread. diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc index 759ea13..63cd287 100644 --- a/net/http/http_network_layer.cc +++ b/net/http/http_network_layer.cc @@ -42,6 +42,7 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) { static const char kOff[] = "off"; static const char kSSL[] = "ssl"; static const char kDisableSSL[] = "no-ssl"; + static const char kDisablePing[] = "no-ping"; static const char kExclude[] = "exclude"; // Hosts to exclude static const char kDisableCompression[] = "no-compress"; static const char kDisableAltProtocols[] = "no-alt-protocols"; @@ -101,6 +102,8 @@ void HttpNetworkLayer::EnableSpdy(const std::string& mode) { } else if (option == kSSL) { HttpStreamFactory::set_force_spdy_over_ssl(true); HttpStreamFactory::set_force_spdy_always(true); + } else if (option == kDisablePing) { + SpdySession::set_enable_ping_based_connection_checking(false); } else if (option == kExclude) { HttpStreamFactory::add_forced_spdy_exclusion(value); } else if (option == kDisableCompression) { diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index c994051..0169d96 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -1202,6 +1202,7 @@ int HttpNetworkTransaction::HandleIOError(int error) { error = OK; } break; + case ERR_SPDY_PING_FAILED: case ERR_SPDY_SERVER_REFUSED_STREAM: ResetConnectionAndRequestForResend(); error = OK; diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index cd7d10b..422ef0d 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -172,6 +172,23 @@ class NetLogSpdyRstParameter : public NetLog::EventParameters { 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 @@ size_t SpdySession::init_max_concurrent_streams_ = 10; // 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 @@ SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair, 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 @@ int SpdySession::WriteSynStream( 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 @@ int SpdySession::WriteSynStream( make_scoped_refptr( new NetLogSpdySynParameter(headers, flags, stream_id, 0))); } + last_sent_was_ping_ = false; return ERR_IO_PENDING; } @@ -505,6 +543,8 @@ int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id, 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 @@ int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id, 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 @@ void SpdySession::ResetStream( 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 @@ void SpdySession::OnReadComplete(int bytes_read) { 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 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { 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 @@ void SpdySession::OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) { // 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 @@ void SpdySession::SendWindowUpdate(spdy::SpdyStreamId stream_id, 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 @@ void SpdySession::SendSettings() { 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::HandleSettings(const spdy::SpdySettings& settings) { } } +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_, diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 96b67f9..8193cb4 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -104,7 +104,8 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, // NOTE: This function can have false negatives on some platforms. bool VerifyDomainAuthentication(const std::string& domain); - // Send the SYN frame for |stream_id|. + // Send the SYN frame for |stream_id|. This also sends PING message to check + // the status of the connection. int WriteSynStream( spdy::SpdyStreamId stream_id, RequestPriority priority, @@ -156,6 +157,14 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, return max_concurrent_stream_limit_; } + // Enable sending of PING frame with each request. + static void set_enable_ping_based_connection_checking(bool enable) { + enable_ping_based_connection_checking_ = enable; + } + static bool enable_ping_based_connection_checking() { + return enable_ping_based_connection_checking_; + } + // The initial max concurrent streams per session, can be overridden by the // server via SETTINGS. static void set_init_max_concurrent_streams(size_t value) { @@ -217,6 +226,8 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, private: friend class base::RefCounted<SpdySession>; + // Allow tests to access our innards for testing purposes. + FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, Ping); FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GetActivePushStream); struct PendingCreateStream { @@ -277,6 +288,7 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, const linked_ptr<spdy::SpdyHeaderBlock>& headers); void OnRst(const spdy::SpdyRstStreamControlFrame& frame); void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame); + void OnPing(const spdy::SpdyPingControlFrame& frame); void OnSettings(const spdy::SpdySettingsControlFrame& frame); void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame); @@ -291,6 +303,31 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, // SETTINGS ontrol frame, update our SpdySession accordingly. void HandleSettings(const spdy::SpdySettings& settings); + // Send the PING (preface-PING and trailing-PING) frames. + void SendPrefacePingIfNoneInFlight(); + + // Send PING if there are no PINGs in flight and we haven't heard from server. + void SendPrefacePing(); + + // Send a PING after delay. Don't post a PING if there is already + // a trailing PING pending. + void PlanToSendTrailingPing(); + + // Send a PING if there is no |trailing_ping_pending_|. This PING verifies + // that the requests are being received by the server. + void SendTrailingPing(); + + // Send the PING frame. + void WritePingFrame(uint32 unique_id); + + // Post a CheckPingStatus call after delay. Don't post if there is already + // CheckPingStatus running. + void PlanToCheckPingStatus(); + + // Check the status of the connection. It calls |CloseSessionOnError| if we + // haven't received any data in |kHungInterval| time period. + void CheckPingStatus(base::TimeTicks last_check_time); + // Start reading from the socket. // Returns OK on success, or an error on failure. net::Error ReadSocket(); @@ -348,6 +385,40 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, virtual void OnDataFrameHeader(const spdy::SpdyDataFrame* frame); + // -------------------------- + // Helper methods for testing + // -------------------------- + static void set_connection_at_risk_of_loss_ms(int duration) { + connection_at_risk_of_loss_ms_ = duration; + } + static int connection_at_risk_of_loss_ms() { + return connection_at_risk_of_loss_ms_; + } + + static void set_trailing_ping_delay_time_ms(int duration) { + trailing_ping_delay_time_ms_ = duration; + } + static int trailing_ping_delay_time_ms() { + return trailing_ping_delay_time_ms_; + } + + static void set_hung_interval_ms(int duration) { + hung_interval_ms_ = duration; + } + static int hung_interval_ms() { + return hung_interval_ms_; + } + + int64 pings_in_flight() const { return pings_in_flight_; } + + uint32 next_ping_id() const { return next_ping_id_; } + + base::TimeTicks received_data_time() const { return received_data_time_; } + + bool trailing_ping_pending() const { return trailing_ping_pending_; } + + bool check_ping_status_pending() const { return check_ping_status_pending_; } + // Callbacks for the Spdy session. OldCompletionCallbackImpl<SpdySession> read_callback_; OldCompletionCallbackImpl<SpdySession> write_callback_; @@ -437,6 +508,28 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, // frame. int stalled_streams_; // Count of streams that were ever stalled. + // Count of all pings on the wire, for which we have not gotten a response. + int64 pings_in_flight_; + + // This is the next ping_id (unique_id) to be sent in PING frame. + uint32 next_ping_id_; + + // This is the last time we have received data. + base::TimeTicks received_data_time_; + + // Indicate if we have already scheduled a delayed task to send a trailing + // ping (and we never have more than one scheduled at a time). + bool trailing_ping_pending_; + + // Indicate if we have already scheduled a delayed task to check the ping + // status. + bool check_ping_status_pending_; + + // Indicate if the last data we sent was a ping (generally, a trailing ping). + // This helps us to decide if we need yet another trailing ping, or if it + // would be a waste of effort (and MUST not be done). + bool last_sent_was_ping_; + // Initial send window size for the session; can be changed by an // arriving SETTINGS frame; newly created streams use this value for the // initial send window size. @@ -457,6 +550,36 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, static bool use_flow_control_; static size_t init_max_concurrent_streams_; static size_t max_concurrent_stream_limit_; + + // This enables or disables connection health checking system. + static bool enable_ping_based_connection_checking_; + + // |connection_at_risk_of_loss_ms_| is an optimization to avoid sending + // wasteful preface pings (when we just got some data). + // + // If it is zero (the most conservative figure), then we always send the + // preface ping (when none are in flight). + // + // It is common for TCP/IP sessions to time out in about 3-5 minutes. + // Certainly if it has been more than 3 minutes, we do want to send a preface + // ping. + // + // We don't think any connection will time out in under about 10 seconds. So + // this might as well be set to something conservative like 10 seconds. Later, + // we could adjust it to send fewer pings perhaps. + static int connection_at_risk_of_loss_ms_; + + // This is the amount of time (in milliseconds) we wait before sending a + // trailing ping. We use a trailing ping (sent after all data) to get an + // effective acknowlegement from the server that it has indeed received all + // (prior) data frames. With that assurance, we are willing to enter into a + // wait state for responses to our last data frame(s) without further pings. + static int trailing_ping_delay_time_ms_; + + // The amount of time (in milliseconds) that we are willing to tolerate with + // no data received (of any form), while there is a ping in flight, before we + // declare the connection to be hung. + static int hung_interval_ms_; }; class NetLogSpdySynParameter : public NetLog::EventParameters { diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc index 8d69fd3..ac9785c 100644 --- a/net/spdy/spdy_session_unittest.cc +++ b/net/spdy/spdy_session_unittest.cc @@ -20,9 +20,53 @@ class SpdySessionTest : public PlatformTest { static void TurnOffCompression() { spdy::SpdyFramer::set_enable_compression_default(false); } + protected: + virtual void TearDown() { + // Wanted to be 100% sure PING is disabled. + SpdySession::set_enable_ping_based_connection_checking(false); + } +}; + +class TestSpdyStreamDelegate : public net::SpdyStream::Delegate { + public: + explicit TestSpdyStreamDelegate(OldCompletionCallback* callback) + : callback_(callback) {} + virtual ~TestSpdyStreamDelegate() {} + + virtual bool OnSendHeadersComplete(int status) { return true; } + + virtual int OnSendBody() { + return ERR_UNEXPECTED; + } + + virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) { + return ERR_UNEXPECTED; + } + + virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, + base::Time response_time, + int status) { + return status; + } + + virtual void OnDataReceived(const char* buffer, int bytes) { + } + + virtual void OnDataSent(int length) { + } + + virtual void OnClose(int status) { + OldCompletionCallback* callback = callback_; + callback_ = NULL; + callback->Run(OK); + } + + virtual void set_chunk_callback(net::ChunkCallback *) {} + + private: + OldCompletionCallback* callback_; }; -namespace { // Test the SpdyIOBuffer class. TEST_F(SpdySessionTest, SpdyIOBuffer) { @@ -122,6 +166,101 @@ TEST_F(SpdySessionTest, GoAway) { session2 = NULL; } +TEST_F(SpdySessionTest, Ping) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(false, OK); + scoped_ptr<spdy::SpdyFrame> read_ping(ConstructSpdyPing()); + MockRead reads[] = { + CreateMockRead(*read_ping), + CreateMockRead(*read_ping), + MockRead(false, 0, 0) // EOF + }; + scoped_ptr<spdy::SpdyFrame> write_ping(ConstructSpdyPing()); + MockRead writes[] = { + CreateMockRead(*write_ping), + CreateMockRead(*write_ping), + }; + StaticSocketDataProvider data( + reads, arraysize(reads), writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(false, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + static const char kStreamUrl[] = "http://www.google.com/"; + GURL url(kStreamUrl); + + 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)); + + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + GURL(), + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, + connection->Init(test_host_port_pair.ToString(), + transport_params, + MEDIUM, + NULL, + http_session->transport_socket_pool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + scoped_refptr<SpdyStream> spdy_stream1; + TestOldCompletionCallback callback1; + EXPECT_EQ(OK, session->CreateStream(url, + MEDIUM, + &spdy_stream1, + BoundNetLog(), + &callback1)); + scoped_ptr<TestSpdyStreamDelegate> delegate( + new TestSpdyStreamDelegate(&callback1)); + spdy_stream1->SetDelegate(delegate.get()); + + base::TimeTicks before_ping_time = base::TimeTicks::Now(); + + // Enable sending of PING. + SpdySession::set_enable_ping_based_connection_checking(true); + SpdySession::set_connection_at_risk_of_loss_ms(0); + SpdySession::set_trailing_ping_delay_time_ms(0); + SpdySession::set_hung_interval_ms(50); + + session->SendPrefacePingIfNoneInFlight(); + + EXPECT_EQ(OK, callback1.WaitForResult()); + + EXPECT_EQ(0, session->pings_in_flight()); + EXPECT_GT(session->next_ping_id(), static_cast<uint32>(1)); + EXPECT_FALSE(session->trailing_ping_pending()); + // TODO(rtenneti): check_ping_status_pending works in debug mode with + // breakpoints, but fails if run in stand alone mode. + // EXPECT_FALSE(session->check_ping_status_pending()); + EXPECT_GE(session->received_data_time(), before_ping_time); + + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + + // Delete the first session. + session = NULL; +} + class StreamReleaserCallback : public CallbackRunner<Tuple1<int> > { public: StreamReleaserCallback(SpdySession* session, @@ -683,6 +822,4 @@ TEST_F(SpdySessionTest, ClearSettingsStorageOnIPAddressChanged) { EXPECT_EQ(0u, test_settings_storage->Get(test_host_port_pair).size()); } -} // namespace - } // namespace net diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index ec0f5d7..ee478451 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -196,6 +196,13 @@ spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings) { return framer.CreateSettings(settings); } +// Construct a SPDY PING frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyPing() { + spdy::SpdyFramer framer; + return framer.CreatePingFrame(1); +} + // Construct a SPDY GOAWAY frame. // Returns the constructed frame. The caller takes ownership of the frame. spdy::SpdyFrame* ConstructSpdyGoAway() { diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h index 2d724bd..9598bb9 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util.h @@ -159,6 +159,10 @@ int ConstructSpdyReplyString(const char* const extra_headers[], // Returns the constructed frame. The caller takes ownership of the frame. spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings); +// Construct a SPDY PING frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyPing(); + // Construct a SPDY GOAWAY frame. // Returns the constructed frame. The caller takes ownership of the frame. spdy::SpdyFrame* ConstructSpdyGoAway(); |