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.
33 const net::BackoffEntry::Policy kConnectionBackoffPolicy
= {
34 // Number of initial errors (in sequence) to ignore before applying
35 // exponential back-off rules.
38 // Initial delay for exponential back-off in ms.
41 // Factor by which the waiting time will be multiplied.
44 // Fuzzing percentage. ex: 10% will spread requests randomly
45 // between 90%-100% of the calculated time.
48 // Maximum amount of time we are willing to delay our request in ms.
49 1000 * 60 * 5, // 5 minutes.
51 // Time to keep an entry from being discarded even when it
52 // has no significant state, -1 to never discard.
55 // Don't use initial delay unless the last request was an error.
61 ConnectionFactoryImpl::ConnectionFactoryImpl(
62 const GURL
& mcs_endpoint
,
63 scoped_refptr
<net::HttpNetworkSession
> network_session
,
65 : mcs_endpoint_(mcs_endpoint
),
66 network_session_(network_session
),
69 weak_ptr_factory_(this) {
72 ConnectionFactoryImpl::~ConnectionFactoryImpl() {
75 void ConnectionFactoryImpl::Initialize(
76 const BuildLoginRequestCallback
& request_builder
,
77 const ConnectionHandler::ProtoReceivedCallback
& read_callback
,
78 const ConnectionHandler::ProtoSentCallback
& write_callback
) {
79 DCHECK(!connection_handler_
);
81 previous_backoff_
= CreateBackoffEntry(&kConnectionBackoffPolicy
);
82 backoff_entry_
= CreateBackoffEntry(&kConnectionBackoffPolicy
);
83 request_builder_
= request_builder
;
85 net::NetworkChangeNotifier::AddIPAddressObserver(this);
86 net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
87 connection_handler_
.reset(
88 new ConnectionHandlerImpl(
89 base::TimeDelta::FromMilliseconds(kReadTimeoutMs
),
92 base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback
,
93 weak_ptr_factory_
.GetWeakPtr())));
96 ConnectionHandler
* ConnectionFactoryImpl::GetConnectionHandler() const {
97 return connection_handler_
.get();
100 void ConnectionFactoryImpl::Connect() {
101 DCHECK(connection_handler_
);
104 if (backoff_entry_
->ShouldRejectRequest()) {
105 DVLOG(1) << "Delaying MCS endpoint connection for "
106 << backoff_entry_
->GetTimeUntilRelease().InMilliseconds()
108 base::MessageLoop::current()->PostDelayedTask(
110 base::Bind(&ConnectionFactoryImpl::Connect
,
111 weak_ptr_factory_
.GetWeakPtr()),
112 backoff_entry_
->GetTimeUntilRelease());
116 DVLOG(1) << "Attempting connection to MCS endpoint.";
120 bool ConnectionFactoryImpl::IsEndpointReachable() const {
121 return connection_handler_
&&
122 connection_handler_
->CanSendMessage() &&
126 void ConnectionFactoryImpl::SignalConnectionReset() {
128 return; // Already attempting to reconnect.
130 if (!backoff_reset_time_
.is_null() &&
131 NowTicks() - backoff_reset_time_
<=
132 base::TimeDelta::FromSeconds(kConnectionResetWindowSecs
)) {
133 backoff_entry_
.swap(previous_backoff_
);
134 backoff_entry_
->InformOfRequest(false);
136 backoff_reset_time_
= base::TimeTicks();
137 previous_backoff_
->Reset();
141 base::TimeTicks
ConnectionFactoryImpl::NextRetryAttempt() const {
143 return base::TimeTicks();
144 return backoff_entry_
->GetReleaseTime();
147 void ConnectionFactoryImpl::OnConnectionTypeChanged(
148 net::NetworkChangeNotifier::ConnectionType type
) {
149 if (type
== net::NetworkChangeNotifier::CONNECTION_NONE
)
152 // TODO(zea): implement different backoff/retry policies based on connection
154 DVLOG(1) << "Connection type changed to " << type
<< ", resetting backoff.";
155 backoff_entry_
->Reset();
156 // Connect(..) should be retrying with backoff already if a connection is
157 // necessary, so no need to call again.
160 void ConnectionFactoryImpl::OnIPAddressChanged() {
161 DVLOG(1) << "IP Address changed, resetting backoff.";
162 backoff_entry_
->Reset();
163 // Connect(..) should be retrying with backoff already if a connection is
164 // necessary, so no need to call again.
167 void ConnectionFactoryImpl::ConnectImpl() {
168 if (socket_handle_
.socket() && socket_handle_
.socket()->IsConnected())
169 socket_handle_
.socket()->Disconnect();
170 socket_handle_
.Reset();
172 // TODO(zea): resolve proxies.
173 net::ProxyInfo proxy_info
;
174 proxy_info
.UseDirect();
175 net::SSLConfig ssl_config
;
176 network_session_
->ssl_config_service()->GetSSLConfig(&ssl_config
);
178 int status
= net::InitSocketHandleForTlsConnect(
179 net::HostPortPair::FromURL(mcs_endpoint_
),
180 network_session_
.get(),
184 net::kPrivacyModeDisabled
,
185 net::BoundNetLog::Make(net_log_
, net::NetLog::SOURCE_SOCKET
),
187 base::Bind(&ConnectionFactoryImpl::OnConnectDone
,
188 weak_ptr_factory_
.GetWeakPtr()));
189 if (status
!= net::ERR_IO_PENDING
)
190 OnConnectDone(status
);
193 void ConnectionFactoryImpl::InitHandler() {
194 // May be null in tests.
195 mcs_proto::LoginRequest login_request
;
196 if (!request_builder_
.is_null()) {
197 request_builder_
.Run(&login_request
);
198 DCHECK(login_request
.IsInitialized());
201 connection_handler_
->Init(login_request
, socket_handle_
.socket());
204 scoped_ptr
<net::BackoffEntry
> ConnectionFactoryImpl::CreateBackoffEntry(
205 const net::BackoffEntry::Policy
* const policy
) {
206 return scoped_ptr
<net::BackoffEntry
>(new net::BackoffEntry(policy
));
209 base::TimeTicks
ConnectionFactoryImpl::NowTicks() {
210 return base::TimeTicks::Now();
213 void ConnectionFactoryImpl::OnConnectDone(int result
) {
214 if (result
!= net::OK
) {
215 LOG(ERROR
) << "Failed to connect to MCS endpoint with error " << result
;
216 backoff_entry_
->InformOfRequest(false);
217 UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result
);
222 DVLOG(1) << "MCS endpoint socket connection success, starting handshake.";
226 void ConnectionFactoryImpl::ConnectionHandlerCallback(int result
) {
227 if (result
== net::OK
) {
228 // Handshake succeeded, reset the backoff.
230 backoff_reset_time_
= NowTicks();
231 previous_backoff_
.swap(backoff_entry_
);
232 backoff_entry_
->Reset();
237 UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionDisconnectErrorCode", result
);
239 // TODO(zea): Consider how to handle errors that may require some sort of
240 // user intervention (login page, etc.).
241 LOG(ERROR
) << "Connection reset with error " << result
;
242 backoff_entry_
->InformOfRequest(false);