OLD | NEW |
---|---|
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "google_apis/gcm/engine/connection_factory_impl.h" | 5 #include "google_apis/gcm/engine/connection_factory_impl.h" |
6 | 6 |
7 #include "base/message_loop/message_loop.h" | 7 #include "base/message_loop/message_loop.h" |
8 #include "base/metrics/histogram.h" | 8 #include "base/metrics/histogram.h" |
9 #include "base/metrics/sparse_histogram.h" | 9 #include "base/metrics/sparse_histogram.h" |
10 #include "google_apis/gcm/engine/connection_handler_impl.h" | 10 #include "google_apis/gcm/engine/connection_handler_impl.h" |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
47 net::NetLog* net_log) | 47 net::NetLog* net_log) |
48 : mcs_endpoints_(mcs_endpoints), | 48 : mcs_endpoints_(mcs_endpoints), |
49 next_endpoint_(0), | 49 next_endpoint_(0), |
50 last_successful_endpoint_(0), | 50 last_successful_endpoint_(0), |
51 backoff_policy_(backoff_policy), | 51 backoff_policy_(backoff_policy), |
52 network_session_(network_session), | 52 network_session_(network_session), |
53 bound_net_log_( | 53 bound_net_log_( |
54 net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)), | 54 net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)), |
55 pac_request_(NULL), | 55 pac_request_(NULL), |
56 connecting_(false), | 56 connecting_(false), |
57 waiting_for_backoff_(false), | |
57 logging_in_(false), | 58 logging_in_(false), |
58 weak_ptr_factory_(this) { | 59 weak_ptr_factory_(this) { |
59 DCHECK_GE(mcs_endpoints_.size(), 1U); | 60 DCHECK_GE(mcs_endpoints_.size(), 1U); |
60 } | 61 } |
61 | 62 |
62 ConnectionFactoryImpl::~ConnectionFactoryImpl() { | 63 ConnectionFactoryImpl::~ConnectionFactoryImpl() { |
63 if (pac_request_) { | 64 if (pac_request_) { |
64 network_session_->proxy_service()->CancelPacRequest(pac_request_); | 65 network_session_->proxy_service()->CancelPacRequest(pac_request_); |
65 pac_request_ = NULL; | 66 pac_request_ = NULL; |
66 } | 67 } |
67 } | 68 } |
68 | 69 |
69 void ConnectionFactoryImpl::Initialize( | 70 void ConnectionFactoryImpl::Initialize( |
70 const BuildLoginRequestCallback& request_builder, | 71 const BuildLoginRequestCallback& request_builder, |
71 const ConnectionHandler::ProtoReceivedCallback& read_callback, | 72 const ConnectionHandler::ProtoReceivedCallback& read_callback, |
72 const ConnectionHandler::ProtoSentCallback& write_callback) { | 73 const ConnectionHandler::ProtoSentCallback& write_callback) { |
73 DCHECK(!connection_handler_); | 74 DCHECK(!connection_handler_); |
74 | 75 |
75 previous_backoff_ = CreateBackoffEntry(&backoff_policy_); | 76 previous_backoff_ = CreateBackoffEntry(&backoff_policy_); |
76 backoff_entry_ = CreateBackoffEntry(&backoff_policy_); | 77 backoff_entry_ = CreateBackoffEntry(&backoff_policy_); |
77 request_builder_ = request_builder; | 78 request_builder_ = request_builder; |
78 | 79 |
79 net::NetworkChangeNotifier::AddIPAddressObserver(this); | 80 net::NetworkChangeNotifier::AddIPAddressObserver(this); |
80 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); | 81 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); |
81 connection_handler_.reset( | 82 connection_handler_ = CreateConnectionHandler( |
82 new ConnectionHandlerImpl( | 83 base::TimeDelta::FromMilliseconds(kReadTimeoutMs), |
83 base::TimeDelta::FromMilliseconds(kReadTimeoutMs), | 84 read_callback, |
84 read_callback, | 85 write_callback, |
85 write_callback, | 86 base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback, |
86 base::Bind(&ConnectionFactoryImpl::ConnectionHandlerCallback, | 87 weak_ptr_factory_.GetWeakPtr())).Pass(); |
87 weak_ptr_factory_.GetWeakPtr()))); | |
88 } | 88 } |
89 | 89 |
90 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const { | 90 ConnectionHandler* ConnectionFactoryImpl::GetConnectionHandler() const { |
91 return connection_handler_.get(); | 91 return connection_handler_.get(); |
92 } | 92 } |
93 | 93 |
94 void ConnectionFactoryImpl::Connect() { | 94 void ConnectionFactoryImpl::Connect() { |
95 DCHECK(connection_handler_); | 95 DCHECK(connection_handler_); |
96 | 96 |
97 connecting_ = true; | 97 if (connecting_ || waiting_for_backoff_) |
jianli
2014/03/28 04:26:13
Now it becomes a bit harder to follow all the logi
Nicolas Zea
2014/03/28 17:53:42
The problem is that, because of canaries, these tw
| |
98 return; // Connection attempt already in progress or pending. | |
99 | |
100 if (IsEndpointReachable()) | |
101 return; // Already connected. | |
102 | |
103 ConnectWithBackoff(); | |
104 } | |
105 | |
106 void ConnectionFactoryImpl::ConnectWithBackoff() { | |
107 // If a canary managed to connect while a backoff expiration was pending, | |
108 // just cleanup the internal state. | |
109 if (connecting_ || IsEndpointReachable()) { | |
110 waiting_for_backoff_ = false; | |
111 return; | |
112 } | |
113 | |
98 if (backoff_entry_->ShouldRejectRequest()) { | 114 if (backoff_entry_->ShouldRejectRequest()) { |
99 DVLOG(1) << "Delaying MCS endpoint connection for " | 115 DVLOG(1) << "Delaying MCS endpoint connection for " |
100 << backoff_entry_->GetTimeUntilRelease().InMilliseconds() | 116 << backoff_entry_->GetTimeUntilRelease().InMilliseconds() |
101 << " milliseconds."; | 117 << " milliseconds."; |
118 waiting_for_backoff_ = true; | |
102 base::MessageLoop::current()->PostDelayedTask( | 119 base::MessageLoop::current()->PostDelayedTask( |
103 FROM_HERE, | 120 FROM_HERE, |
104 base::Bind(&ConnectionFactoryImpl::Connect, | 121 base::Bind(&ConnectionFactoryImpl::ConnectWithBackoff, |
105 weak_ptr_factory_.GetWeakPtr()), | 122 weak_ptr_factory_.GetWeakPtr()), |
106 backoff_entry_->GetTimeUntilRelease()); | 123 backoff_entry_->GetTimeUntilRelease()); |
107 return; | 124 return; |
108 } | 125 } |
109 | 126 |
110 DVLOG(1) << "Attempting connection to MCS endpoint."; | 127 DVLOG(1) << "Attempting connection to MCS endpoint."; |
128 waiting_for_backoff_ = false; | |
111 ConnectImpl(); | 129 ConnectImpl(); |
112 } | 130 } |
113 | 131 |
114 bool ConnectionFactoryImpl::IsEndpointReachable() const { | 132 bool ConnectionFactoryImpl::IsEndpointReachable() const { |
115 return connection_handler_ && | 133 return connection_handler_ && connection_handler_->CanSendMessage(); |
116 connection_handler_->CanSendMessage() && | |
117 !connecting_; | |
118 } | 134 } |
119 | 135 |
120 void ConnectionFactoryImpl::SignalConnectionReset( | 136 void ConnectionFactoryImpl::SignalConnectionReset( |
121 ConnectionResetReason reason) { | 137 ConnectionResetReason reason) { |
122 // A failure can trigger multiple resets, so no need to do anything if a | 138 // A failure can trigger multiple resets, so no need to do anything if a |
123 // connection is already in progress. | 139 // connection is already in progress. |
124 if (connecting_) | 140 if (connecting_) { |
141 DVLOG(1) << "Connection in progress, ignoring reset."; | |
125 return; | 142 return; |
143 } | |
126 | 144 |
127 UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason", | 145 UMA_HISTOGRAM_ENUMERATION("GCM.ConnectionResetReason", |
128 reason, | 146 reason, |
129 CONNECTION_RESET_COUNT); | 147 CONNECTION_RESET_COUNT); |
130 if (!last_login_time_.is_null()) { | 148 if (!last_login_time_.is_null()) { |
131 UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime", | 149 UMA_HISTOGRAM_CUSTOM_TIMES("GCM.ConnectionUpTime", |
132 NowTicks() - last_login_time_, | 150 NowTicks() - last_login_time_, |
133 base::TimeDelta::FromSeconds(1), | 151 base::TimeDelta::FromSeconds(1), |
134 base::TimeDelta::FromHours(24), | 152 base::TimeDelta::FromHours(24), |
135 50); | 153 50); |
136 // |last_login_time_| will be reset below, before attempting the new | 154 // |last_login_time_| will be reset below, before attempting the new |
137 // connection. | 155 // connection. |
138 } | 156 } |
139 | 157 |
140 CloseSocket(); | 158 CloseSocket(); |
159 DCHECK(!IsEndpointReachable()); | |
160 | |
161 // Network changes get special treatment as they can trigger a one-off canary | |
162 // request that bypasses backoff (but does nothing if a connection is in | |
163 // progress). Other connection reset events can be ignored as a connection | |
164 // is already awaiting backoff expiration. | |
165 if (waiting_for_backoff_ && reason != NETWORK_CHANGE) { | |
166 DVLOG(1) << "Backoff expiration pending, ignoring reset."; | |
167 return; | |
168 } | |
141 | 169 |
142 if (logging_in_) { | 170 if (logging_in_) { |
143 // Failures prior to login completion just reuse the existing backoff entry. | 171 // Failures prior to login completion just reuse the existing backoff entry. |
144 logging_in_ = false; | 172 logging_in_ = false; |
145 backoff_entry_->InformOfRequest(false); | 173 backoff_entry_->InformOfRequest(false); |
146 } else if (reason == LOGIN_FAILURE || | 174 } else if (reason == LOGIN_FAILURE || |
147 ShouldRestorePreviousBackoff(last_login_time_, NowTicks())) { | 175 ShouldRestorePreviousBackoff(last_login_time_, NowTicks())) { |
148 // Failures due to login, or within the reset window of a login, restore | 176 // Failures due to login, or within the reset window of a login, restore |
149 // the backoff entry that was saved off at login completion time. | 177 // the backoff entry that was saved off at login completion time. |
150 backoff_entry_.swap(previous_backoff_); | 178 backoff_entry_.swap(previous_backoff_); |
151 backoff_entry_->InformOfRequest(false); | 179 backoff_entry_->InformOfRequest(false); |
180 } else if (reason == NETWORK_CHANGE) { | |
181 ConnectImpl(); // Canary attempts bypass backoff without resetting it. | |
182 return; | |
152 } else { | 183 } else { |
153 // We shouldn't be in backoff in thise case. | 184 // We shouldn't be in backoff in thise case. |
154 DCHECK_EQ(0, backoff_entry_->failure_count()); | 185 DCHECK_EQ(0, backoff_entry_->failure_count()); |
155 } | 186 } |
187 DCHECK(!connecting_); | |
188 DCHECK(!waiting_for_backoff_); | |
156 | 189 |
157 // At this point the last login time has been consumed or deemed irrelevant, | 190 // At this point the last login time has been consumed or deemed irrelevant, |
158 // reset it. | 191 // reset it. |
159 last_login_time_ = base::TimeTicks(); | 192 last_login_time_ = base::TimeTicks(); |
160 | 193 |
161 Connect(); | 194 Connect(); |
162 } | 195 } |
163 | 196 |
164 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const { | 197 base::TimeTicks ConnectionFactoryImpl::NextRetryAttempt() const { |
165 if (!backoff_entry_) | 198 if (!backoff_entry_) |
166 return base::TimeTicks(); | 199 return base::TimeTicks(); |
167 return backoff_entry_->GetReleaseTime(); | 200 return backoff_entry_->GetReleaseTime(); |
168 } | 201 } |
169 | 202 |
170 void ConnectionFactoryImpl::OnConnectionTypeChanged( | 203 void ConnectionFactoryImpl::OnConnectionTypeChanged( |
171 net::NetworkChangeNotifier::ConnectionType type) { | 204 net::NetworkChangeNotifier::ConnectionType type) { |
172 if (type == net::NetworkChangeNotifier::CONNECTION_NONE) | 205 if (type == net::NetworkChangeNotifier::CONNECTION_NONE) |
173 return; | 206 return; |
174 | 207 |
175 // TODO(zea): implement different backoff/retry policies based on connection | 208 DVLOG(1) << "Connection type changed to " << type << ", reconnecting."; |
176 // type. | 209 |
177 DVLOG(1) << "Connection type changed to " << type << ", resetting backoff."; | 210 // The connection may have been silently dropped, attempt to reconnect. |
178 backoff_entry_->Reset(); | 211 SignalConnectionReset(NETWORK_CHANGE); |
179 // Connect(..) should be retrying with backoff already if a connection is | |
180 // necessary, so no need to call again. | |
181 } | 212 } |
182 | 213 |
183 void ConnectionFactoryImpl::OnIPAddressChanged() { | 214 void ConnectionFactoryImpl::OnIPAddressChanged() { |
184 DVLOG(1) << "IP Address changed, resetting backoff."; | 215 DVLOG(1) << "IP Address changed, reconnecting."; |
185 backoff_entry_->Reset(); | 216 |
186 // Connect(..) should be retrying with backoff already if a connection is | 217 // The connection may have been silently dropped, attempt to reconnect. |
187 // necessary, so no need to call again. | 218 SignalConnectionReset(NETWORK_CHANGE); |
188 } | 219 } |
189 | 220 |
190 GURL ConnectionFactoryImpl::GetCurrentEndpoint() const { | 221 GURL ConnectionFactoryImpl::GetCurrentEndpoint() const { |
191 // Note that IsEndpointReachable() returns false anytime connecting_ is true, | 222 // Note that IsEndpointReachable() returns false anytime connecting_ is true, |
192 // so while connecting this always uses |next_endpoint_|. | 223 // so while connecting this always uses |next_endpoint_|. |
193 if (IsEndpointReachable()) | 224 if (IsEndpointReachable()) |
194 return mcs_endpoints_[last_successful_endpoint_]; | 225 return mcs_endpoints_[last_successful_endpoint_]; |
195 return mcs_endpoints_[next_endpoint_]; | 226 return mcs_endpoints_[next_endpoint_]; |
196 } | 227 } |
197 | 228 |
198 void ConnectionFactoryImpl::ConnectImpl() { | 229 void ConnectionFactoryImpl::ConnectImpl() { |
199 DCHECK(connecting_); | 230 DCHECK(!IsEndpointReachable()); |
200 DCHECK(!socket_handle_.socket()); | 231 DCHECK(!socket_handle_.socket()); |
201 | 232 |
233 connecting_ = true; | |
202 int status = network_session_->proxy_service()->ResolveProxy( | 234 int status = network_session_->proxy_service()->ResolveProxy( |
203 GetCurrentEndpoint(), | 235 GetCurrentEndpoint(), |
204 &proxy_info_, | 236 &proxy_info_, |
205 base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone, | 237 base::Bind(&ConnectionFactoryImpl::OnProxyResolveDone, |
206 weak_ptr_factory_.GetWeakPtr()), | 238 weak_ptr_factory_.GetWeakPtr()), |
207 &pac_request_, | 239 &pac_request_, |
208 bound_net_log_); | 240 bound_net_log_); |
209 if (status != net::ERR_IO_PENDING) | 241 if (status != net::ERR_IO_PENDING) |
210 OnProxyResolveDone(status); | 242 OnProxyResolveDone(status); |
211 } | 243 } |
212 | 244 |
213 void ConnectionFactoryImpl::InitHandler() { | 245 void ConnectionFactoryImpl::InitHandler() { |
214 // May be null in tests. | 246 // May be null in tests. |
215 mcs_proto::LoginRequest login_request; | 247 mcs_proto::LoginRequest login_request; |
216 if (!request_builder_.is_null()) { | 248 if (!request_builder_.is_null()) { |
217 request_builder_.Run(&login_request); | 249 request_builder_.Run(&login_request); |
218 DCHECK(login_request.IsInitialized()); | 250 DCHECK(login_request.IsInitialized()); |
219 } | 251 } |
220 | 252 |
221 connection_handler_->Init(login_request, socket_handle_.socket()); | 253 connection_handler_->Init(login_request, socket_handle_.socket()); |
222 } | 254 } |
223 | 255 |
224 scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry( | 256 scoped_ptr<net::BackoffEntry> ConnectionFactoryImpl::CreateBackoffEntry( |
225 const net::BackoffEntry::Policy* const policy) { | 257 const net::BackoffEntry::Policy* const policy) { |
226 return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy)); | 258 return scoped_ptr<net::BackoffEntry>(new net::BackoffEntry(policy)); |
227 } | 259 } |
228 | 260 |
261 scoped_ptr<ConnectionHandler> ConnectionFactoryImpl::CreateConnectionHandler( | |
262 base::TimeDelta read_timeout, | |
263 const ConnectionHandler::ProtoReceivedCallback& read_callback, | |
264 const ConnectionHandler::ProtoSentCallback& write_callback, | |
265 const ConnectionHandler::ConnectionChangedCallback& connection_callback) { | |
266 return make_scoped_ptr<ConnectionHandler>( | |
267 new ConnectionHandlerImpl(read_timeout, | |
268 read_callback, | |
269 write_callback, | |
270 connection_callback)); | |
271 } | |
272 | |
229 base::TimeTicks ConnectionFactoryImpl::NowTicks() { | 273 base::TimeTicks ConnectionFactoryImpl::NowTicks() { |
230 return base::TimeTicks::Now(); | 274 return base::TimeTicks::Now(); |
231 } | 275 } |
232 | 276 |
233 void ConnectionFactoryImpl::OnConnectDone(int result) { | 277 void ConnectionFactoryImpl::OnConnectDone(int result) { |
234 if (result != net::OK) { | 278 if (result != net::OK) { |
235 // If the connection fails, try another proxy. | 279 // If the connection fails, try another proxy. |
236 result = ReconsiderProxyAfterError(result); | 280 result = ReconsiderProxyAfterError(result); |
237 // ReconsiderProxyAfterError either returns an error (in which case it is | 281 // ReconsiderProxyAfterError either returns an error (in which case it is |
238 // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering | 282 // not reconsidering a proxy) or returns ERR_IO_PENDING if it is considering |
239 // another proxy. | 283 // another proxy. |
240 DCHECK_NE(result, net::OK); | 284 DCHECK_NE(result, net::OK); |
241 if (result == net::ERR_IO_PENDING) | 285 if (result == net::ERR_IO_PENDING) |
242 return; // Proxy reconsideration pending. Return. | 286 return; // Proxy reconsideration pending. Return. |
243 LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result; | 287 LOG(ERROR) << "Failed to connect to MCS endpoint with error " << result; |
244 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", false); | 288 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", false); |
245 CloseSocket(); | 289 CloseSocket(); |
246 backoff_entry_->InformOfRequest(false); | 290 backoff_entry_->InformOfRequest(false); |
247 UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result); | 291 UMA_HISTOGRAM_SPARSE_SLOWLY("GCM.ConnectionFailureErrorCode", result); |
248 | 292 |
249 // If there are other endpoints available, use the next endpoint on the | 293 // If there are other endpoints available, use the next endpoint on the |
250 // subsequent retry. | 294 // subsequent retry. |
251 next_endpoint_++; | 295 next_endpoint_++; |
252 if (next_endpoint_ >= mcs_endpoints_.size()) | 296 if (next_endpoint_ >= mcs_endpoints_.size()) |
253 next_endpoint_ = 0; | 297 next_endpoint_ = 0; |
298 connecting_ = false; | |
254 Connect(); | 299 Connect(); |
255 return; | 300 return; |
256 } | 301 } |
257 | 302 |
258 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", true); | 303 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectionSuccessRate", true); |
259 UMA_HISTOGRAM_COUNTS("GCM.ConnectionEndpoint", next_endpoint_); | 304 UMA_HISTOGRAM_COUNTS("GCM.ConnectionEndpoint", next_endpoint_); |
260 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectedViaProxy", | 305 UMA_HISTOGRAM_BOOLEAN("GCM.ConnectedViaProxy", |
261 !(proxy_info_.is_empty() || proxy_info_.is_direct())); | 306 !(proxy_info_.is_empty() || proxy_info_.is_direct())); |
262 ReportSuccessfulProxyConnection(); | 307 ReportSuccessfulProxyConnection(); |
263 | 308 |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
436 // the destroyed socket. | 481 // the destroyed socket. |
437 if (connection_handler_) | 482 if (connection_handler_) |
438 connection_handler_->Reset(); | 483 connection_handler_->Reset(); |
439 | 484 |
440 if (socket_handle_.socket() && socket_handle_.socket()->IsConnected()) | 485 if (socket_handle_.socket() && socket_handle_.socket()->IsConnected()) |
441 socket_handle_.socket()->Disconnect(); | 486 socket_handle_.socket()->Disconnect(); |
442 socket_handle_.Reset(); | 487 socket_handle_.Reset(); |
443 } | 488 } |
444 | 489 |
445 } // namespace gcm | 490 } // namespace gcm |
OLD | NEW |