1 // Copyright 2014 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/heartbeat_manager.h"
7 #include "base/callback.h"
8 #include "base/location.h"
9 #include "base/metrics/histogram.h"
10 #include "base/power_monitor/power_monitor.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/time/time.h"
13 #include "base/timer/timer.h"
14 #include "google_apis/gcm/protocol/mcs.pb.h"
15 #include "net/base/network_change_notifier.h"
20 // The default heartbeat when on a mobile or unknown network .
21 const int kCellHeartbeatDefaultMs
= 1000 * 60 * 28; // 28 minutes.
22 // The default heartbeat when on WiFi (also used for ethernet).
23 const int kWifiHeartbeatDefaultMs
= 1000 * 60 * 15; // 15 minutes.
24 // The default heartbeat ack interval.
25 const int kHeartbeatAckDefaultMs
= 1000 * 60 * 1; // 1 minute.
26 // Minimum allowed client default heartbeat interval.
27 const int kMinClientHeartbeatIntervalMs
= 1000 * 60 * 2; // 2 minutes.
29 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
30 // The period at which to check if the heartbeat time has passed. Used to
31 // protect against platforms where the timer is delayed by the system being
32 // suspended. Only needed on linux because the other OSes provide a standard
33 // way to be notified of system suspend and resume events.
34 const int kHeartbeatMissedCheckMs
= 1000 * 60 * 5; // 5 minutes.
35 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
39 HeartbeatManager::HeartbeatManager()
40 : waiting_for_ack_(false),
41 heartbeat_interval_ms_(0),
42 server_interval_ms_(0),
43 client_interval_ms_(0),
44 heartbeat_timer_(new base::Timer(true /* retain_user_task */,
45 false /* is_repeating */)),
46 weak_ptr_factory_(this) {
47 // Listen for system suspend and resume events.
48 base::PowerMonitor
* monitor
= base::PowerMonitor::Get();
50 monitor
->AddObserver(this);
53 HeartbeatManager::~HeartbeatManager() {
54 // Stop listening for system suspend and resume events.
55 base::PowerMonitor
* monitor
= base::PowerMonitor::Get();
57 monitor
->RemoveObserver(this);
60 void HeartbeatManager::Start(
61 const base::Closure
& send_heartbeat_callback
,
62 const ReconnectCallback
& trigger_reconnect_callback
) {
63 DCHECK(!send_heartbeat_callback
.is_null());
64 DCHECK(!trigger_reconnect_callback
.is_null());
65 send_heartbeat_callback_
= send_heartbeat_callback
;
66 trigger_reconnect_callback_
= trigger_reconnect_callback
;
68 // Kicks off the timer.
69 waiting_for_ack_
= false;
73 void HeartbeatManager::Stop() {
74 heartbeat_expected_time_
= base::Time();
75 heartbeat_interval_ms_
= 0;
76 heartbeat_timer_
->Stop();
77 waiting_for_ack_
= false;
80 void HeartbeatManager::OnHeartbeatAcked() {
81 if (!heartbeat_timer_
->IsRunning())
84 DCHECK(!send_heartbeat_callback_
.is_null());
85 DCHECK(!trigger_reconnect_callback_
.is_null());
86 waiting_for_ack_
= false;
90 void HeartbeatManager::UpdateHeartbeatConfig(
91 const mcs_proto::HeartbeatConfig
& config
) {
92 if (!config
.IsInitialized() ||
93 !config
.has_interval_ms() ||
94 config
.interval_ms() <= 0) {
97 DVLOG(1) << "Updating server heartbeat interval to " << config
.interval_ms();
98 server_interval_ms_
= config
.interval_ms();
101 base::TimeTicks
HeartbeatManager::GetNextHeartbeatTime() const {
102 if (heartbeat_timer_
->IsRunning())
103 return heartbeat_timer_
->desired_run_time();
105 return base::TimeTicks();
108 void HeartbeatManager::UpdateHeartbeatTimer(scoped_ptr
<base::Timer
> timer
) {
109 bool was_running
= heartbeat_timer_
->IsRunning();
110 base::TimeDelta remaining_delay
=
111 heartbeat_timer_
->desired_run_time() - base::TimeTicks::Now();
112 base::Closure
timer_task(heartbeat_timer_
->user_task());
114 heartbeat_timer_
->Stop();
115 heartbeat_timer_
= timer
.Pass();
118 heartbeat_timer_
->Start(FROM_HERE
, remaining_delay
, timer_task
);
121 void HeartbeatManager::OnResume() {
122 CheckForMissedHeartbeat();
125 void HeartbeatManager::OnHeartbeatTriggered() {
126 // Reset the weak pointers used for heartbeat checks.
127 weak_ptr_factory_
.InvalidateWeakPtrs();
129 if (waiting_for_ack_
) {
130 LOG(WARNING
) << "Lost connection to MCS, reconnecting.";
131 ResetConnection(ConnectionFactory::HEARTBEAT_FAILURE
);
135 waiting_for_ack_
= true;
137 send_heartbeat_callback_
.Run();
140 void HeartbeatManager::RestartTimer() {
141 if (!waiting_for_ack_
) {
142 // Recalculate the timer interval based network type.
143 // Server interval takes precedence over client interval, even if the latter
145 if (server_interval_ms_
!= 0) {
146 // If a server interval is set, it overrides any local one.
147 heartbeat_interval_ms_
= server_interval_ms_
;
148 } else if (HasClientHeartbeatInterval()) {
149 // Client interval might have been adjusted up, which should only take
150 // effect during a reconnection.
151 if (client_interval_ms_
< heartbeat_interval_ms_
||
152 heartbeat_interval_ms_
== 0) {
153 heartbeat_interval_ms_
= client_interval_ms_
;
156 heartbeat_interval_ms_
= GetDefaultHeartbeatInterval();
158 DVLOG(1) << "Sending next heartbeat in "
159 << heartbeat_interval_ms_
<< " ms.";
161 heartbeat_interval_ms_
= kHeartbeatAckDefaultMs
;
162 DVLOG(1) << "Resetting timer for ack with "
163 << heartbeat_interval_ms_
<< " ms interval.";
166 heartbeat_expected_time_
=
168 base::TimeDelta::FromMilliseconds(heartbeat_interval_ms_
);
169 heartbeat_timer_
->Start(FROM_HERE
,
170 base::TimeDelta::FromMilliseconds(
171 heartbeat_interval_ms_
),
172 base::Bind(&HeartbeatManager::OnHeartbeatTriggered
,
173 weak_ptr_factory_
.GetWeakPtr()));
175 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
176 // Windows, Mac, Android, iOS, and Chrome OS all provide a way to be notified
177 // when the system is suspending or resuming. The only one that does not is
178 // Linux so we need to poll to check for missed heartbeats.
179 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
181 base::Bind(&HeartbeatManager::CheckForMissedHeartbeat
,
182 weak_ptr_factory_
.GetWeakPtr()),
183 base::TimeDelta::FromMilliseconds(kHeartbeatMissedCheckMs
));
184 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
187 void HeartbeatManager::CheckForMissedHeartbeat() {
188 // If there's no heartbeat pending, return without doing anything.
189 if (heartbeat_expected_time_
.is_null())
192 // If the heartbeat has been missed, manually trigger it.
193 if (base::Time::Now() > heartbeat_expected_time_
) {
194 UMA_HISTOGRAM_LONG_TIMES("GCM.HeartbeatMissedDelta",
195 base::Time::Now() - heartbeat_expected_time_
);
196 OnHeartbeatTriggered();
200 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
201 // Otherwise check again later.
202 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
204 base::Bind(&HeartbeatManager::CheckForMissedHeartbeat
,
205 weak_ptr_factory_
.GetWeakPtr()),
206 base::TimeDelta::FromMilliseconds(kHeartbeatMissedCheckMs
));
207 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
210 int HeartbeatManager::GetDefaultHeartbeatInterval() {
211 // For unknown connections, use the longer cellular heartbeat interval.
212 int heartbeat_interval_ms
= kCellHeartbeatDefaultMs
;
213 if (net::NetworkChangeNotifier::GetConnectionType() ==
214 net::NetworkChangeNotifier::CONNECTION_WIFI
||
215 net::NetworkChangeNotifier::GetConnectionType() ==
216 net::NetworkChangeNotifier::CONNECTION_ETHERNET
) {
217 heartbeat_interval_ms
= kWifiHeartbeatDefaultMs
;
219 return heartbeat_interval_ms
;
222 int HeartbeatManager::GetMaxClientHeartbeatIntervalMs() {
223 return GetDefaultHeartbeatInterval();
226 int HeartbeatManager::GetMinClientHeartbeatIntervalMs() {
227 // Returning a constant. This should be adjusted for connection type, like the
228 // default/max interval.
229 return kMinClientHeartbeatIntervalMs
;
232 void HeartbeatManager::SetClientHeartbeatIntervalMs(int interval_ms
) {
233 if ((interval_ms
!= 0 && !IsValidClientHeartbeatInterval(interval_ms
)) ||
234 interval_ms
== client_interval_ms_
) {
238 client_interval_ms_
= interval_ms
;
239 // Only reset connection if the new heartbeat interval is shorter. If it is
240 // longer, the connection will reset itself at some point and interval will be
242 if (client_interval_ms_
> 0 && client_interval_ms_
< heartbeat_interval_ms_
) {
243 ResetConnection(ConnectionFactory::NEW_HEARTBEAT_INTERVAL
);
247 int HeartbeatManager::GetClientHeartbeatIntervalMs() {
248 return client_interval_ms_
;
251 bool HeartbeatManager::HasClientHeartbeatInterval() {
252 return client_interval_ms_
!= 0;
255 bool HeartbeatManager::IsValidClientHeartbeatInterval(int interval
) {
256 int max_heartbeat_interval
= GetDefaultHeartbeatInterval();
257 return kMinClientHeartbeatIntervalMs
<= interval
&&
258 interval
<= max_heartbeat_interval
;
261 void HeartbeatManager::ResetConnection(
262 ConnectionFactory::ConnectionResetReason reason
) {
264 trigger_reconnect_callback_
.Run(reason
);