1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "google_apis/gcm/engine/connection_factory_impl.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/metrics/histogram.h"
9 #include "base/metrics/sparse_histogram.h"
10 #include "google_apis/gcm/engine/connection_handler_impl.h"
11 #include "google_apis/gcm/protocol/mcs.pb.h"
12 #include "net/base/net_errors.h"
13 #include "net/http/http_network_session.h"
14 #include "net/http/http_request_headers.h"
15 #include "net/proxy/proxy_info.h"
16 #include "net/socket/client_socket_handle.h"
17 #include "net/socket/client_socket_pool_manager.h"
18 #include "net/ssl/ssl_config_service.h"
24 // The amount of time a Socket read should wait before timing out.
25 const int kReadTimeoutMs = 30000; // 30 seconds.
27 // If a connection is reset after succeeding within this window of time,
28 // the previous backoff entry is restored (and the connection success is treated
29 // as if it was transient).
30 const int kConnectionResetWindowSecs = 10; // 10 seconds.
32 // Decides whether the last login was within kConnectionResetWindowSecs of now
34 bool ShouldRestorePreviousBackoff(const base::TimeTicks& login_time,
35 const base::TimeTicks& now_ticks) {
36 return !login_time.is_null() &&
37 now_ticks - login_time <=
38 base::TimeDelta::FromSeconds(kConnectionResetWindowSecs);
43 ConnectionFactoryImpl::ConnectionFactoryImpl(
44 const GURL& mcs_endpoint,
45 const net::BackoffEntry::Policy& backoff_policy,
46 scoped_refptr<net::HttpNetworkSession> network_session,
48 : mcs_endpoint_(mcs_endpoint),
49 backoff_policy_(backoff_policy),
50 network_session_(network_session),
54 weak_ptr_factory_(this) {
57 ConnectionFactoryImpl::~ConnectionFactoryImpl() {
60 void ConnectionFactoryImpl::Initialize(
61 const BuildLoginRequestCallback& request_builder,
62 const ConnectionHandler::ProtoReceivedCallback& read_callback,
63 const ConnectionHandler::ProtoSentCallback& write_callback) {
64 DCHECK(!connection_handler_);
66 previous_backoff_ = CreateBackoffEntry(&backoff_policy_);
67 backoff_entry_ = CreateBackoffEntry(&backoff_policy_);
68 request_builder_ = request_builder;
70 net::NetworkChangeNotifier::AddIPAddressObserver(this);
71 net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
72 connection_handler_.reset(
73 new ConnectionHandlerImpl(
74 base::TimeDelta::FromMilliseconds(kReadTimeoutMs),
77 base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback,
78 weak_ptr_factory_.GetWeakPtr())));
81 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const {
82 return connection_handler_.get();
85 void ConnectionFactoryImpl::Connect() {
86 DCHECK(connection_handler_);
89 if (backoff_entry_->ShouldRejectRequest()) {
90 DVLOG(1) << "Delaying MCS endpoint connection for "
91 << backoff_entry_->GetTimeUntilRelease().InMilliseconds()
93 base::MessageLoop::current()->PostDelayedTask(
95 base::Bind(&ConnectionFactoryImpl::Connect,
96 weak_ptr_factory_.GetWeakPtr()),
97 backoff_entry_->GetTimeUntilRelease());
101 DVLOG(1) << "Attempting connection to MCS endpoint.";
105 bool ConnectionFactoryImpl::IsEndpointReachable() const {
106 return connection_handler_ &&
107 connection_handler_->CanSendMessage() &&
111 void ConnectionFactoryImpl::SignalConnectionReset(
112 ConnectionResetReason reason) {
113 // A failure can trigger multiple resets, so no need to do anything if a
114 // connection is already in progress.
118 UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason",
120 CONNECTION_RESET_COUNT);
121 if (!last_login_time_.is_null()) {
122 UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime",
123 NowTicks() - last_login_time_,
124 base::TimeDelta::FromSeconds(1),
125 base::TimeDelta::FromHours(24),
127 // |last_login_time_| will be reset below, before attempting the new
131 if (connection_handler_)
132 connection_handler_->Reset();
134 if (socket_handle_.socket() && socket_handle_.socket()->IsConnected())
135 socket_handle_.socket()->Disconnect();
136 socket_handle_.Reset();
139 // Failures prior to login completion just reuse the existing backoff entry.
141 backoff_entry_->InformOfRequest(false);
142 } else if (reason == LOGIN_FAILURE ||
143 ShouldRestorePreviousBackoff(last_login_time_, NowTicks())) {
144 // Failures due to login, or within the reset window of a login, restore
145 // the backoff entry that was saved off at login completion time.
146 backoff_entry_.swap(previous_backoff_);
147 backoff_entry_->InformOfRequest(false);
149 // We shouldn't be in backoff in thise case.
150 DCHECK(backoff_entry_->CanDiscard());
153 // At this point the last login time has been consumed or deemed irrelevant,
155 last_login_time_ = base::TimeTicks();
160 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const {
162 return base::TimeTicks();
163 return backoff_entry_->GetReleaseTime();
166 void ConnectionFactoryImpl::OnConnectionTypeChanged(
167 net::NetworkChangeNotifier::ConnectionType type) {
168 if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
171 // TODO(zea): implement different backoff/retry policies based on connection
173 DVLOG(1) << "Connection type changed to " << type << ", resetting backoff.";
174 backoff_entry_->Reset();
175 // Connect(..) should be retrying with backoff already if a connection is
176 // necessary, so no need to call again.
179 void ConnectionFactoryImpl::OnIPAddressChanged() {
180 DVLOG(1) << "IP Address changed, resetting backoff.";
181 backoff_entry_->Reset();
182 // Connect(..) should be retrying with backoff already if a connection is
183 // necessary, so no need to call again.
186 void ConnectionFactoryImpl::ConnectImpl() {
188 DCHECK(!socket_handle_.socket());
190 // TODO(zea): resolve proxies.
191 net::ProxyInfo proxy_info;
192 proxy_info.UseDirect();
193 net::SSLConfig ssl_config;
194 network_session_->ssl_config_service()->GetSSLConfig(&ssl_config);
196 int status = net::InitSocketHandleForTlsConnect(
197 net::HostPortPair::FromURL(mcs_endpoint_),
198 network_session_.get(),
202 net::kPrivacyModeDisabled,
203 net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_SOCKET),
205 base::Bind(&ConnectionFactoryImpl::OnConnectDone,
206 weak_ptr_factory_.GetWeakPtr()));
207 if (status != net::ERR_IO_PENDING)
208 OnConnectDone(status);
211 void ConnectionFactoryImpl::InitHandler() {
212 // May be null in tests.
213 mcs_proto::LoginRequest login_request;
214 if (!request_builder_.is_null()) {
215 request_builder_.Run(&login_request);
216 DCHECK(login_request.IsInitialized());
219 connection_handler_->Init(login_request, socket_handle_.socket());
222 scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry(
223 const net::BackoffEntry::Policy* const policy) {
224 return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy));
227 base::TimeTicks ConnectionFactoryImpl::NowTicks() {
228 return base::TimeTicks::Now();
231 void ConnectionFactoryImpl::OnConnectDone(int result) {
232 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", (result == net::OK));
234 if (result != net::OK) {
235 LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result;
236 backoff_entry_->InformOfRequest(false);
237 UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result);
244 DVLOG(1) << "MCS endpoint socket connection success, starting login.";
248 void ConnectionFactoryImpl::ConnectionHandlerCallback(int result) {
249 DCHECK(!connecting_);
250 if (result != net::OK) {
251 // TODO(zea): Consider how to handle errors that may require some sort of
252 // user intervention (login page, etc.).
253 UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionDisconnectErrorCode", result);
254 SignalConnectionReset(SOCKET_FAILURE);
258 // Handshake complete, reset backoff. If the login failed with an error,
259 // the client should invoke SignalConnectionReset(LOGIN_FAILURE), which will
260 // restore the previous backoff.
261 last_login_time_ = NowTicks();
262 previous_backoff_.swap(backoff_entry_);
263 backoff_entry_->Reset();