When a request fails because the QUIC handshake failed, retry the request
(which will happen over TCP).

Review URL: https://codereview.chromium.org/27181004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@229112 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 5ec421ed..ec25dc4c 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -552,6 +552,10 @@
 // The HTTP headers were truncated by an EOF.
 NET_ERROR(RESPONSE_HEADERS_TRUNCATED, -357)
 
+// The QUIC crytpo handshake failed.  This means that the server was unable
+// to read any requests sent, so they may be resent.
+NET_ERROR(QUIC_HANDSHAKE_FAILED, -358)
+
 // The cache does not have the requested entry.
 NET_ERROR(CACHE_MISS, -400)
 
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 7c5f0de..018ba3b 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -897,6 +897,11 @@
       return result;
   }
 
+  if (result == ERR_QUIC_HANDSHAKE_FAILED) {
+    ResetConnectionAndRequestForResend();
+    return OK;
+  }
+
   if (result < 0 && result != ERR_CONNECTION_CLOSED)
     return HandleIOError(result);
 
@@ -1339,6 +1344,7 @@
       break;
     case ERR_SPDY_PING_FAILED:
     case ERR_SPDY_SERVER_REFUSED_STREAM:
+    case ERR_QUIC_HANDSHAKE_FAILED:
       net_log_.AddEventWithNetErrorCode(
           NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error);
       ResetConnectionAndRequestForResend();
diff --git a/net/quic/quic_client_session.cc b/net/quic/quic_client_session.cc
index 860429408..13362b64 100644
--- a/net/quic/quic_client_session.cc
+++ b/net/quic/quic_client_session.cc
@@ -47,7 +47,7 @@
 }
 
 int QuicClientSession::StreamRequest::StartRequest(
-    const base::WeakPtr<QuicClientSession> session,
+    const base::WeakPtr<QuicClientSession>& session,
     QuicReliableClientStream** stream,
     const CompletionCallback& callback) {
   session_ = session;
@@ -112,6 +112,12 @@
 }
 
 QuicClientSession::~QuicClientSession() {
+  // The session must be closed before it is destroyed.
+  DCHECK(streams()->empty());
+  CloseAllStreams(ERR_UNEXPECTED);
+  DCHECK(observers_.empty());
+  CloseAllObservers(ERR_UNEXPECTED);
+
   connection()->set_debug_visitor(NULL);
   net_log_.EndEvent(NetLog::TYPE_QUIC_SESSION);
 
@@ -156,6 +162,16 @@
   return QuicSession::OnStreamFrames(frames);
 }
 
+void QuicClientSession::AddObserver(Observer* observer) {
+  DCHECK(!ContainsKey(observers_, observer));
+  observers_.insert(observer);
+}
+
+void QuicClientSession::RemoveObserver(Observer* observer) {
+  DCHECK(ContainsKey(observers_, observer));
+  observers_.erase(observer);
+}
+
 int QuicClientSession::TryCreateStream(StreamRequest* request,
                                        QuicReliableClientStream** stream) {
   if (!crypto_stream_->encryption_established()) {
@@ -296,6 +312,14 @@
     // following code needs to changed.
     base::ResetAndReturn(&callback_).Run(OK);
   }
+  if (event == HANDSHAKE_CONFIRMED) {
+    ObserverSet::iterator it = observers_.begin();
+    while (it != observers_.end()) {
+      Observer* observer = *it;
+      ++it;
+      observer->OnCryptoHandshakeConfirmed();
+    }
+  }
   QuicSession::OnCryptoHandshakeEvent(event);
 }
 
@@ -328,12 +352,13 @@
 
   UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.QuicVersion",
                               connection()->version());
+  NotifyFactoryOfSessionGoingAway();
   if (!callback_.is_null()) {
     base::ResetAndReturn(&callback_).Run(ERR_QUIC_PROTOCOL_ERROR);
   }
   socket_->Close();
   QuicSession::ConnectionClose(error, from_peer);
-  NotifyFactoryOfSessionCloseLater();
+  NotifyFactoryOfSessionClosedLater();
 }
 
 void QuicClientSession::OnSuccessfulVersionNegotiation(
@@ -367,7 +392,7 @@
 void QuicClientSession::CloseSessionOnError(int error) {
   UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.CloseSessionOnError", -error);
   CloseSessionOnErrorInner(error, QUIC_INTERNAL_ERROR);
-  NotifyFactoryOfSessionClose();
+  NotifyFactoryOfSessionClosed();
 }
 
 void QuicClientSession::CloseSessionOnErrorInner(int net_error,
@@ -375,12 +400,8 @@
   if (!callback_.is_null()) {
     base::ResetAndReturn(&callback_).Run(net_error);
   }
-  while (!streams()->empty()) {
-    ReliableQuicStream* stream = streams()->begin()->second;
-    QuicStreamId id = stream->id();
-    static_cast<QuicReliableClientStream*>(stream)->OnError(net_error);
-    CloseStream(id);
-  }
+  CloseAllStreams(net_error);
+  CloseAllObservers(net_error);
   net_log_.AddEvent(
       NetLog::TYPE_QUIC_SESSION_CLOSE_ON_ERROR,
       NetLog::IntegerCallback("net_error", net_error));
@@ -389,6 +410,23 @@
   DCHECK(!connection()->connected());
 }
 
+void QuicClientSession::CloseAllStreams(int net_error) {
+  while (!streams()->empty()) {
+    ReliableQuicStream* stream = streams()->begin()->second;
+    QuicStreamId id = stream->id();
+    static_cast<QuicReliableClientStream*>(stream)->OnError(net_error);
+    CloseStream(id);
+  }
+}
+
+void QuicClientSession::CloseAllObservers(int net_error) {
+  while (!observers_.empty()) {
+    Observer* observer = *observers_.begin();
+    observers_.erase(observer);
+    observer->OnSessionClosed(net_error);
+  }
+}
+
 base::Value* QuicClientSession::GetInfoAsValue(const HostPortPair& pair) const {
   base::DictionaryValue* dict = new base::DictionaryValue();
   dict->SetString("host_port_pair", pair.ToString());
@@ -412,8 +450,9 @@
   if (result < 0) {
     DLOG(INFO) << "Closing session on read error: " << result;
     UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ReadError", -result);
+    NotifyFactoryOfSessionGoingAway();
     CloseSessionOnErrorInner(result, QUIC_PACKET_READ_ERROR);
-    NotifyFactoryOfSessionCloseLater();
+    NotifyFactoryOfSessionClosedLater();
     return;
   }
 
@@ -428,26 +467,31 @@
   // use a weak pointer to be safe.
   connection()->ProcessUdpPacket(local_address, peer_address, packet);
   if (!connection()->connected()) {
-    stream_factory_->OnSessionClose(this);
+    stream_factory_->OnSessionClosed(this);
     return;
   }
   StartReading();
 }
 
-void QuicClientSession::NotifyFactoryOfSessionCloseLater() {
+void QuicClientSession::NotifyFactoryOfSessionGoingAway() {
+  if (stream_factory_)
+    stream_factory_->OnSessionGoingAway(this);
+}
+
+void QuicClientSession::NotifyFactoryOfSessionClosedLater() {
   DCHECK_EQ(0u, GetNumOpenStreams());
   DCHECK(!connection()->connected());
   base::MessageLoop::current()->PostTask(
       FROM_HERE,
-      base::Bind(&QuicClientSession::NotifyFactoryOfSessionClose,
+      base::Bind(&QuicClientSession::NotifyFactoryOfSessionClosed,
                  weak_factory_.GetWeakPtr()));
 }
 
-void QuicClientSession::NotifyFactoryOfSessionClose() {
+void QuicClientSession::NotifyFactoryOfSessionClosed() {
   DCHECK_EQ(0u, GetNumOpenStreams());
-  DCHECK(stream_factory_);
   // Will delete |this|.
-  stream_factory_->OnSessionClose(this);
+  if (stream_factory_)
+    stream_factory_->OnSessionClosed(this);
 }
 
 }  // namespace net
diff --git a/net/quic/quic_client_session.h b/net/quic/quic_client_session.h
index 2a7a7d0..f1a328e 100644
--- a/net/quic/quic_client_session.h
+++ b/net/quic/quic_client_session.h
@@ -34,6 +34,14 @@
 
 class NET_EXPORT_PRIVATE QuicClientSession : public QuicSession {
  public:
+  // An interface for observing events on a session.
+  class NET_EXPORT_PRIVATE Observer {
+   public:
+    virtual ~Observer() {}
+    virtual void OnCryptoHandshakeConfirmed() = 0;
+    virtual void OnSessionClosed(int error) = 0;
+  };
+
   // A helper class used to manage a request to create a stream.
   class NET_EXPORT_PRIVATE StreamRequest {
    public:
@@ -44,7 +52,7 @@
     // |stream| will be updated with the newly created stream.  If
     // ERR_IO_PENDING is returned, then when the request is eventuallly
     // complete |callback| will be called.
-    int StartRequest(const base::WeakPtr<QuicClientSession> session,
+    int StartRequest(const base::WeakPtr<QuicClientSession>& session,
                      QuicReliableClientStream** stream,
                      const CompletionCallback& callback);
 
@@ -85,6 +93,9 @@
 
   virtual ~QuicClientSession();
 
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
   // Attempts to create a new stream.  If the stream can be
   // created immediately, returns OK.  If the open stream limit
   // has been reached, returns ERR_IO_PENDING, and |request|
@@ -149,6 +160,7 @@
  private:
   friend class test::QuicClientSessionPeer;
 
+  typedef std::set<Observer*> ObserverSet;
   typedef std::list<StreamRequest*> StreamRequestQueue;
 
   QuicReliableClientStream* CreateOutgoingReliableStreamImpl();
@@ -157,20 +169,36 @@
 
   void OnClosedStream();
 
+  // A Session may be closed via any of three methods:
+  // OnConnectionClosed - called by the connection when the connection has been
+  //     closed, perhaps due to a timeout or a protocol error.
+  // CloseSessionOnError - called from the owner of the session,
+  //     the QuicStreamFactory, when there is an error.
+  // OnReadComplete - when there is a read error.
+  // This method closes all stream and performs any necessary cleanup.
   void CloseSessionOnErrorInner(int net_error, QuicErrorCode quic_error);
 
+  void CloseAllStreams(int net_error);
+  void CloseAllObservers(int net_error);
+
+  // Notifies the factory that this session is going away and no more streams
+  // should be created from it.  This needs to be called before closing any
+  // streams, because closing a stream may cause a new stream to be created.
+  void NotifyFactoryOfSessionGoingAway();
+
   // Posts a task to notify the factory that this session has been closed.
-  void NotifyFactoryOfSessionCloseLater();
+  void NotifyFactoryOfSessionClosedLater();
 
   // Notifies the factory that this session has been closed which will
   // delete |this|.
-  void NotifyFactoryOfSessionClose();
+  void NotifyFactoryOfSessionClosed();
 
   bool require_confirmation_;
   scoped_ptr<QuicCryptoClientStream> crypto_stream_;
   QuicStreamFactory* stream_factory_;
   scoped_ptr<DatagramClientSocket> socket_;
   scoped_refptr<IOBufferWithSize> read_buffer_;
+  ObserverSet observers_;
   StreamRequestQueue stream_requests_;
   bool read_pending_;
   CompletionCallback callback_;
diff --git a/net/quic/quic_client_session_test.cc b/net/quic/quic_client_session_test.cc
index 385be52..65937de 100644
--- a/net/quic/quic_client_session_test.cc
+++ b/net/quic/quic_client_session_test.cc
@@ -6,6 +6,7 @@
 
 #include <vector>
 
+#include "base/rand_util.h"
 #include "net/base/capturing_net_log.h"
 #include "net/base/test_completion_callback.h"
 #include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
@@ -15,6 +16,7 @@
 #include "net/quic/test_tools/crypto_test_utils.h"
 #include "net/quic/test_tools/quic_client_session_peer.h"
 #include "net/quic/test_tools/quic_test_utils.h"
+#include "net/socket/socket_test_util.h"
 #include "net/udp/datagram_client_socket.h"
 
 using testing::_;
@@ -30,13 +32,24 @@
   QuicClientSessionTest()
       : guid_(1),
         connection_(new PacketSavingConnection(guid_, IPEndPoint(), false)),
-        session_(connection_, scoped_ptr<DatagramClientSocket>(), NULL,
+        session_(connection_, GetSocket().Pass(), NULL,
                  NULL, kServerHostname, DefaultQuicConfig(), &crypto_config_,
                  &net_log_) {
     session_.config()->SetDefaults();
     crypto_config_.SetDefaults();
   }
 
+  virtual void TearDown() OVERRIDE {
+    session_.CloseSessionOnError(ERR_ABORTED);
+  }
+
+  scoped_ptr<DatagramClientSocket> GetSocket() {
+    socket_factory_.AddSocketDataProvider(&socket_data_);
+    return socket_factory_.CreateDatagramClientSocket(
+        DatagramSocket::DEFAULT_BIND, base::Bind(&base::RandInt),
+        &net_log_, NetLog::Source());
+  }
+
   void CompleteCryptoHandshake() {
     ASSERT_EQ(ERR_IO_PENDING,
               session_.CryptoConnect(false, callback_.callback()));
@@ -48,6 +61,8 @@
   QuicGuid guid_;
   PacketSavingConnection* connection_;
   CapturingNetLog net_log_;
+  MockClientSocketFactory socket_factory_;
+  StaticSocketDataProvider socket_data_;
   QuicClientSession session_;
   MockClock clock_;
   MockRandom random_;
diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc
index 79ab001..56936ed 100644
--- a/net/quic/quic_http_stream.cc
+++ b/net/quic/quic_http_stream.cc
@@ -24,9 +24,11 @@
 
 static const size_t kHeaderBufInitialSize = 4096;
 
-QuicHttpStream::QuicHttpStream(const base::WeakPtr<QuicClientSession> session)
+QuicHttpStream::QuicHttpStream(const base::WeakPtr<QuicClientSession>& session)
     : next_state_(STATE_NONE),
       session_(session),
+      session_error_(OK),
+      was_handshake_confirmed_(session->IsCryptoHandshakeConfirmed()),
       stream_(NULL),
       request_info_(NULL),
       request_body_stream_(NULL),
@@ -38,10 +40,13 @@
       user_buffer_len_(0),
       weak_factory_(this) {
   DCHECK(session_);
+  session_->AddObserver(this);
 }
 
 QuicHttpStream::~QuicHttpStream() {
   Close(false);
+  if (session_)
+    session_->RemoveObserver(this);
 }
 
 int QuicHttpStream::InitializeStream(const HttpRequestInfo* request_info,
@@ -50,7 +55,8 @@
                                      const CompletionCallback& callback) {
   DCHECK(!stream_);
   if (!session_)
-    return ERR_CONNECTION_CLOSED;
+    return was_handshake_confirmed_ ? ERR_CONNECTION_CLOSED :
+        ERR_QUIC_HANDSHAKE_FAILED;
 
   stream_net_log_ = stream_net_log;
   request_info_ = request_info;
@@ -59,19 +65,24 @@
   int rv = stream_request_.StartRequest(
       session_, &stream_, base::Bind(&QuicHttpStream::OnStreamReady,
                                      weak_factory_.GetWeakPtr()));
-  if (rv == ERR_IO_PENDING)
+  if (rv == ERR_IO_PENDING) {
     callback_ = callback;
-
-  if (rv == OK)
+  } else if (rv == OK) {
     stream_->SetDelegate(this);
+  } else if (!was_handshake_confirmed_) {
+    rv = ERR_QUIC_HANDSHAKE_FAILED;
+  }
 
   return rv;
 }
 
 void QuicHttpStream::OnStreamReady(int rv) {
   DCHECK(rv == OK || !stream_);
-  if (rv == OK)
+  if (rv == OK) {
     stream_->SetDelegate(this);
+  } else if (!was_handshake_confirmed_) {
+    rv = ERR_QUIC_HANDSHAKE_FAILED;
+  }
 
   ResetAndReturn(&callback_).Run(rv);
 }
@@ -323,7 +334,8 @@
 
 void QuicHttpStream::OnClose(QuicErrorCode error) {
   if (error != QUIC_NO_ERROR) {
-    response_status_ = ERR_QUIC_PROTOCOL_ERROR;
+    response_status_ = was_handshake_confirmed_ ?
+        ERR_QUIC_PROTOCOL_ERROR : ERR_QUIC_HANDSHAKE_FAILED;
   } else if (!response_headers_received_) {
     response_status_ = ERR_ABORTED;
   }
@@ -335,7 +347,8 @@
 
 void QuicHttpStream::OnError(int error) {
   stream_ = NULL;
-  response_status_ = error;
+  response_status_ = was_handshake_confirmed_ ?
+      error : ERR_QUIC_HANDSHAKE_FAILED;
   if (!callback_.is_null())
     DoCallback(response_status_);
 }
@@ -344,6 +357,15 @@
   return next_state_ > STATE_SEND_HEADERS_COMPLETE;
 }
 
+void QuicHttpStream::OnCryptoHandshakeConfirmed() {
+  was_handshake_confirmed_ = true;
+}
+
+void QuicHttpStream::OnSessionClosed(int error) {
+  session_error_ = error;
+  session_.reset();
+}
+
 void QuicHttpStream::OnIOComplete(int rv) {
   rv = DoLoop(rv);
 
diff --git a/net/quic/quic_http_stream.h b/net/quic/quic_http_stream.h
index 71fb515..31e6e994 100644
--- a/net/quic/quic_http_stream.h
+++ b/net/quic/quic_http_stream.h
@@ -23,10 +23,11 @@
 // non-owning pointer to a QuicReliableClientStream which it uses to
 // send and receive data.
 class NET_EXPORT_PRIVATE QuicHttpStream :
+      public QuicClientSession::Observer,
       public QuicReliableClientStream::Delegate,
       public HttpStream {
  public:
-  explicit QuicHttpStream(const base::WeakPtr<QuicClientSession> session);
+  explicit QuicHttpStream(const base::WeakPtr<QuicClientSession>& session);
 
   virtual ~QuicHttpStream();
 
@@ -68,6 +69,10 @@
   virtual void OnError(int error) OVERRIDE;
   virtual bool HasSendHeadersComplete() OVERRIDE;
 
+  // QuicClientSession::Observer implementation
+  virtual void OnCryptoHandshakeConfirmed() OVERRIDE;
+  virtual void OnSessionClosed(int error) OVERRIDE;
+
  private:
   friend class test::QuicHttpStreamPeer;
 
@@ -102,7 +107,9 @@
 
   State next_state_;
 
-  const base::WeakPtr<QuicClientSession> session_;
+  base::WeakPtr<QuicClientSession> session_;
+  int session_error_;  // Error code from the connection shutdown.
+  bool was_handshake_confirmed_;  // True if the crypto handshake succeeded.
   QuicClientSession::StreamRequest stream_request_;
   QuicReliableClientStream* stream_;  // Non-owning.
 
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index b26087f9..ffbfad1 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -143,6 +143,7 @@
   }
 
   ~QuicHttpStreamTest() {
+    session_->CloseSessionOnError(ERR_ABORTED);
     for (size_t i = 0; i < writes_.size(); i++) {
       delete writes_[i].packet;
     }
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index a9438c4d..526cb68 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -834,18 +834,28 @@
 
   AddHangingNonAlternateProtocolSocketData();
 
+  // Final job that will proceed when the QUIC job fails.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     NULL, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
   CreateSession();
 
   AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
 
-  scoped_ptr<HttpNetworkTransaction> trans(
-      new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
-  TestCompletionCallback callback;
-  int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
-  EXPECT_EQ(ERR_IO_PENDING, rv);
-  EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+  SendRequestAndExpectHttpResponse("hello from http");
 
   ExpectBrokenAlternateProtocolMapping();
+
+  EXPECT_TRUE(quic_data.at_read_eof());
+  EXPECT_TRUE(quic_data.at_write_eof());
 }
 
 }  // namespace test
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index ecb94f9..7df1f37f 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -257,6 +257,7 @@
 }
 
 QuicStreamFactory::~QuicStreamFactory() {
+  CloseAllSessions(ERR_ABORTED);
   STLDeleteElements(&all_sessions_);
   STLDeleteValues(&active_jobs_);
   STLDeleteValues(&all_crypto_configs_);
@@ -336,14 +337,14 @@
 
   QuicClientSession* session = active_sessions_[host_port_proxy_pair];
   DCHECK(session);
-  return scoped_ptr<QuicHttpStream>(new QuicHttpStream(session->GetWeakPtr()));
+  return scoped_ptr<QuicHttpStream>(
+      new QuicHttpStream(session->GetWeakPtr()));
 }
 
 void QuicStreamFactory::OnIdleSession(QuicClientSession* session) {
 }
 
-void QuicStreamFactory::OnSessionClose(QuicClientSession* session) {
-  DCHECK_EQ(0u, session->GetNumOpenStreams());
+void QuicStreamFactory::OnSessionGoingAway(QuicClientSession* session) {
   const AliasSet& aliases = session_aliases_[session];
   for (AliasSet::const_iterator it = aliases.begin(); it != aliases.end();
        ++it) {
@@ -358,8 +359,13 @@
       http_server_properties_->SetBrokenAlternateProtocol(it->first);
     }
   }
-  all_sessions_.erase(session);
   session_aliases_.erase(session);
+}
+
+void QuicStreamFactory::OnSessionClosed(QuicClientSession* session) {
+  DCHECK_EQ(0u, session->GetNumOpenStreams());
+  OnSessionGoingAway(session);
+  all_sessions_.erase(session);
   delete session;
 }
 
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index 48a9917..6316d62 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -109,8 +109,12 @@
   // Called by a session when it becomes idle.
   void OnIdleSession(QuicClientSession* session);
 
+  // Called by a session when it is going away and no more streams should be
+  // created on it.
+  void OnSessionGoingAway(QuicClientSession* session);
+
   // Called by a session after it shuts down.
-  void OnSessionClose(QuicClientSession* session);
+  void OnSessionClosed(QuicClientSession* session);
 
   // Cancels a pending request.
   void CancelRequest(QuicStreamRequest* request);
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index c09fcc2..8f1291a 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -32,6 +32,29 @@
       const HostPortProxyPair& host_port_proxy_pair) {
     return factory->GetOrCreateCryptoConfig(host_port_proxy_pair);
   }
+
+  static bool HasActiveSession(QuicStreamFactory* factory,
+                               const HostPortProxyPair& host_port_proxy_pair) {
+    return factory->HasActiveSession(host_port_proxy_pair);
+  }
+
+  static QuicClientSession* GetActiveSession(
+      QuicStreamFactory* factory,
+      const HostPortProxyPair& host_port_proxy_pair) {
+    DCHECK(factory->HasActiveSession(host_port_proxy_pair));
+    return factory->active_sessions_[host_port_proxy_pair];
+  }
+
+  static bool IsLiveSession(QuicStreamFactory* factory,
+                            QuicClientSession* session) {
+    for (QuicStreamFactory::SessionSet::iterator it =
+             factory->all_sessions_.begin();
+         it != factory->all_sessions_.end(); ++it) {
+      if (*it == session)
+        return true;
+    }
+    return false;
+  }
 };
 
 class QuicStreamFactoryTest : public ::testing::Test {
@@ -180,6 +203,61 @@
   EXPECT_TRUE(socket_data.at_write_eof());
 }
 
+TEST_F(QuicStreamFactoryTest, Goaway) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+  socket_data.StopAfter(1);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), NULL, 0);
+  socket_data2.StopAfter(1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING, request.Request(host_port_proxy_pair_, is_https_,
+                                            cert_verifier_.get(), net_log_,
+                                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  // Mark the session as going away.  Ensure that while it is still alive
+  // that it is no longer active.
+  QuicClientSession* session = QuicStreamFactoryPeer::GetActiveSession(
+      &factory_, host_port_proxy_pair_);
+  factory_.OnSessionGoingAway(session);
+  EXPECT_EQ(true, QuicStreamFactoryPeer::IsLiveSession(&factory_, session));
+  EXPECT_FALSE(QuicStreamFactoryPeer::HasActiveSession(&factory_,
+                                                       host_port_proxy_pair_));
+  EXPECT_EQ(NULL, factory_.CreateIfSessionExists(host_port_proxy_pair_,
+                                                 net_log_).get());
+
+  // Create a new request for the same destination and verify that a
+  // new session is created.
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING, request2.Request(host_port_proxy_pair_, is_https_,
+                                             cert_verifier_.get(), net_log_,
+                                             callback_.callback()));
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_TRUE(QuicStreamFactoryPeer::HasActiveSession(&factory_,
+                                                      host_port_proxy_pair_));
+  EXPECT_NE(session,
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, host_port_proxy_pair_));
+  EXPECT_EQ(true, QuicStreamFactoryPeer::IsLiveSession(&factory_, session));
+
+  stream2.reset();
+  stream.reset();
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
 TEST_F(QuicStreamFactoryTest, MaxOpenStream) {
   MockRead reads[] = {
     MockRead(ASYNC, OK, 0)  // EOF
diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h
index 5449e87..99bbd4d 100644
--- a/net/quic/test_tools/quic_test_utils.h
+++ b/net/quic/test_tools/quic_test_utils.h
@@ -292,6 +292,7 @@
                                             QuicStreamOffset offset,
                                             bool fin));
   MOCK_METHOD0(IsHandshakeComplete, bool());
+  MOCK_METHOD0(IsCryptoHandshakeConfirmed, bool());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockSession);