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.
4
5 #include "google_apis/gcm/engine/heartbeat_manager.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/location.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/power_monitor/power_monitor.h"
14 #include "base/time/time.h"
15 #include "base/timer/timer.h"
16 #include "build/build_config.h"
17 #include "google_apis/gcm/protocol/mcs.pb.h"
18 #include "net/base/network_change_notifier.h"
19
20 namespace gcm {
21
22 namespace {
23 // The default heartbeat when on a mobile or unknown network .
24 const int kCellHeartbeatDefaultMs = 1000 * 60 * 28; // 28 minutes.
25 // The default heartbeat when on WiFi (also used for ethernet).
26 const int kWifiHeartbeatDefaultMs = 1000 * 60 * 15; // 15 minutes.
27 // The default heartbeat ack interval.
28 const int kHeartbeatAckDefaultMs = 1000 * 60 * 1; // 1 minute.
29 // Minimum allowed client default heartbeat interval.
30 const int kMinClientHeartbeatIntervalMs = 1000 * 30; // 30 seconds.
31 // Minimum time spent sleeping before we force a new heartbeat.
32 const int kMinSuspendTimeMs = 1000 * 10; // 10 seconds.
33
34 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
35 // The period at which to check if the heartbeat time has passed. Used to
36 // protect against platforms where the timer is delayed by the system being
37 // suspended. Only needed on linux because the other OSes provide a standard
38 // way to be notified of system suspend and resume events.
39 const int kHeartbeatMissedCheckMs = 1000 * 60 * 5; // 5 minutes.
40 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
41
42 } // namespace
43
HeartbeatManager(scoped_refptr<base::SequencedTaskRunner> io_task_runner,scoped_refptr<base::SequencedTaskRunner> maybe_power_wrapped_io_task_runner)44 HeartbeatManager::HeartbeatManager(
45 scoped_refptr<base::SequencedTaskRunner> io_task_runner,
46 scoped_refptr<base::SequencedTaskRunner> maybe_power_wrapped_io_task_runner)
47 : waiting_for_ack_(false),
48 heartbeat_interval_ms_(0),
49 server_interval_ms_(0),
50 client_interval_ms_(0),
51 io_task_runner_(std::move(io_task_runner)),
52 heartbeat_timer_(new base::RetainingOneShotTimer()) {
53 DCHECK(io_task_runner_);
54 DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
55 // Set the heartbeat timer task runner to |maybe_power_wrapped_io_task_runner|
56 // so that a delayed task posted to it can wake the system up from sleep to
57 // perform the task.
58 heartbeat_timer_->SetTaskRunner(
59 std::move(maybe_power_wrapped_io_task_runner));
60 }
61
~HeartbeatManager()62 HeartbeatManager::~HeartbeatManager() {
63 // Stop listening for system suspend and resume events.
64 base::PowerMonitor::RemoveObserver(this);
65 }
66
Start(const base::RepeatingClosure & send_heartbeat_callback,const ReconnectCallback & trigger_reconnect_callback)67 void HeartbeatManager::Start(
68 const base::RepeatingClosure& send_heartbeat_callback,
69 const ReconnectCallback& trigger_reconnect_callback) {
70 DCHECK(!send_heartbeat_callback.is_null());
71 DCHECK(!trigger_reconnect_callback.is_null());
72 send_heartbeat_callback_ = send_heartbeat_callback;
73 trigger_reconnect_callback_ = trigger_reconnect_callback;
74
75 // Listen for system suspend and resume events.
76 base::PowerMonitor::AddObserver(this);
77
78 // Calculated the heartbeat interval just before we start the timer.
79 UpdateHeartbeatInterval();
80
81 // Kicks off the timer.
82 waiting_for_ack_ = false;
83 RestartTimer();
84 }
85
Stop()86 void HeartbeatManager::Stop() {
87 heartbeat_expected_time_ = base::Time();
88 heartbeat_interval_ms_ = 0;
89 heartbeat_timer_->Stop();
90 waiting_for_ack_ = false;
91
92 base::PowerMonitor::RemoveObserver(this);
93 }
94
OnHeartbeatAcked()95 void HeartbeatManager::OnHeartbeatAcked() {
96 if (!heartbeat_timer_->IsRunning())
97 return;
98
99 DCHECK(!send_heartbeat_callback_.is_null());
100 DCHECK(!trigger_reconnect_callback_.is_null());
101 waiting_for_ack_ = false;
102 RestartTimer();
103 }
104
UpdateHeartbeatConfig(const mcs_proto::HeartbeatConfig & config)105 void HeartbeatManager::UpdateHeartbeatConfig(
106 const mcs_proto::HeartbeatConfig& config) {
107 if (!config.IsInitialized() ||
108 !config.has_interval_ms() ||
109 config.interval_ms() <= 0) {
110 return;
111 }
112 DVLOG(1) << "Updating server heartbeat interval to " << config.interval_ms();
113 server_interval_ms_ = config.interval_ms();
114
115 // Make sure heartbeat interval is recalculated when new server interval is
116 // available.
117 UpdateHeartbeatInterval();
118 }
119
GetNextHeartbeatTime() const120 base::TimeTicks HeartbeatManager::GetNextHeartbeatTime() const {
121 if (heartbeat_timer_->IsRunning())
122 return heartbeat_timer_->desired_run_time();
123 else
124 return base::TimeTicks();
125 }
126
UpdateHeartbeatTimer(std::unique_ptr<base::RetainingOneShotTimer> timer)127 void HeartbeatManager::UpdateHeartbeatTimer(
128 std::unique_ptr<base::RetainingOneShotTimer> timer) {
129 bool was_running = heartbeat_timer_->IsRunning();
130 base::TimeDelta remaining_delay =
131 heartbeat_timer_->desired_run_time() - base::TimeTicks::Now();
132 base::RepeatingClosure timer_task = heartbeat_timer_->user_task();
133
134 heartbeat_timer_->Stop();
135 heartbeat_timer_ = std::move(timer);
136
137 if (was_running)
138 heartbeat_timer_->Start(FROM_HERE, remaining_delay, timer_task);
139 }
140
OnSuspend()141 void HeartbeatManager::OnSuspend() {
142 // The system is going to sleep. Record the time, so on resume we know how
143 // much time the machine was suspended.
144 suspend_time_ = base::Time::Now();
145 }
146
OnResume()147 void HeartbeatManager::OnResume() {
148 // The system just resumed from sleep. It's likely that the connection to
149 // MCS was silently lost during that time, even if a heartbeat is not yet
150 // due. Force a heartbeat to detect if the connection is still good.
151 base::TimeDelta elapsed = base::Time::Now() - suspend_time_;
152 UMA_HISTOGRAM_LONG_TIMES("GCM.SuspendTime", elapsed);
153
154 // Make sure a minimum amount of time has passed before forcing a heartbeat to
155 // avoid any tight loop scenarios.
156 // If the |send_heartbeat_callback_| is null, it means the heartbeat manager
157 // hasn't been started, so do nothing.
158 if (elapsed > base::TimeDelta::FromMilliseconds(kMinSuspendTimeMs) &&
159 !send_heartbeat_callback_.is_null())
160 OnHeartbeatTriggered();
161 }
162
OnHeartbeatTriggered()163 void HeartbeatManager::OnHeartbeatTriggered() {
164 // Reset the weak pointers used for heartbeat checks.
165 weak_ptr_factory_.InvalidateWeakPtrs();
166
167 if (waiting_for_ack_) {
168 LOG(WARNING) << "Lost connection to MCS, reconnecting.";
169 ResetConnection(ConnectionFactory::HEARTBEAT_FAILURE);
170 return;
171 }
172
173 waiting_for_ack_ = true;
174 RestartTimer();
175 send_heartbeat_callback_.Run();
176 }
177
RestartTimer()178 void HeartbeatManager::RestartTimer() {
179 int interval_ms = heartbeat_interval_ms_;
180 if (waiting_for_ack_) {
181 interval_ms = kHeartbeatAckDefaultMs;
182 DVLOG(1) << "Resetting timer for ack within " << interval_ms << " ms.";
183 } else {
184 DVLOG(1) << "Sending next heartbeat in " << interval_ms << " ms.";
185 }
186
187 heartbeat_expected_time_ =
188 base::Time::Now() + base::TimeDelta::FromMilliseconds(interval_ms);
189 heartbeat_timer_->Start(
190 FROM_HERE, base::TimeDelta::FromMilliseconds(interval_ms),
191 base::BindRepeating(&HeartbeatManager::OnHeartbeatTriggered,
192 weak_ptr_factory_.GetWeakPtr()));
193
194 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
195 // Windows, Mac, Android, iOS, and Chrome OS all provide a way to be notified
196 // when the system is suspending or resuming. The only one that does not is
197 // Linux so we need to poll to check for missed heartbeats.
198 io_task_runner_->PostDelayedTask(
199 FROM_HERE,
200 base::BindOnce(&HeartbeatManager::CheckForMissedHeartbeat,
201 weak_ptr_factory_.GetWeakPtr()),
202 base::TimeDelta::FromMilliseconds(kHeartbeatMissedCheckMs));
203 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
204 }
205
CheckForMissedHeartbeat()206 void HeartbeatManager::CheckForMissedHeartbeat() {
207 // If there's no heartbeat pending, return without doing anything.
208 if (heartbeat_expected_time_.is_null())
209 return;
210
211 // If the heartbeat has been missed, manually trigger it.
212 if (base::Time::Now() > heartbeat_expected_time_) {
213 UMA_HISTOGRAM_LONG_TIMES("GCM.HeartbeatMissedDelta",
214 base::Time::Now() - heartbeat_expected_time_);
215 OnHeartbeatTriggered();
216 return;
217 }
218
219 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
220 // Otherwise check again later.
221 io_task_runner_->PostDelayedTask(
222 FROM_HERE,
223 base::BindOnce(&HeartbeatManager::CheckForMissedHeartbeat,
224 weak_ptr_factory_.GetWeakPtr()),
225 base::TimeDelta::FromMilliseconds(kHeartbeatMissedCheckMs));
226 #endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
227 }
228
UpdateHeartbeatInterval()229 void HeartbeatManager::UpdateHeartbeatInterval() {
230 // Server interval takes precedence over client interval, even if the latter
231 // is less.
232 if (server_interval_ms_ != 0) {
233 // If a server interval is set, it overrides any local one.
234 heartbeat_interval_ms_ = server_interval_ms_;
235 } else if (HasClientHeartbeatInterval() &&
236 (client_interval_ms_ < heartbeat_interval_ms_ ||
237 heartbeat_interval_ms_ == 0)) {
238 // Client interval might have been adjusted up, which should only take
239 // effect during a reconnection.
240 heartbeat_interval_ms_ = client_interval_ms_;
241 } else if (heartbeat_interval_ms_ == 0) {
242 // If interval is still 0, recalculate it based on network type.
243 heartbeat_interval_ms_ = GetDefaultHeartbeatInterval();
244 }
245 DCHECK_GT(heartbeat_interval_ms_, 0);
246 }
247
GetDefaultHeartbeatInterval()248 int HeartbeatManager::GetDefaultHeartbeatInterval() {
249 // For unknown connections, use the longer cellular heartbeat interval.
250 int heartbeat_interval_ms = kCellHeartbeatDefaultMs;
251 if (net::NetworkChangeNotifier::GetConnectionType() ==
252 net::NetworkChangeNotifier::CONNECTION_WIFI ||
253 net::NetworkChangeNotifier::GetConnectionType() ==
254 net::NetworkChangeNotifier::CONNECTION_ETHERNET) {
255 heartbeat_interval_ms = kWifiHeartbeatDefaultMs;
256 }
257 return heartbeat_interval_ms;
258 }
259
GetMaxClientHeartbeatIntervalMs()260 int HeartbeatManager::GetMaxClientHeartbeatIntervalMs() {
261 return GetDefaultHeartbeatInterval();
262 }
263
GetMinClientHeartbeatIntervalMs()264 int HeartbeatManager::GetMinClientHeartbeatIntervalMs() {
265 // Returning a constant. This should be adjusted for connection type, like the
266 // default/max interval.
267 return kMinClientHeartbeatIntervalMs;
268 }
269
SetClientHeartbeatIntervalMs(int interval_ms)270 void HeartbeatManager::SetClientHeartbeatIntervalMs(int interval_ms) {
271 if ((interval_ms != 0 && !IsValidClientHeartbeatInterval(interval_ms)) ||
272 interval_ms == client_interval_ms_) {
273 return;
274 }
275
276 client_interval_ms_ = interval_ms;
277 // Only reset connection if the new heartbeat interval is shorter. If it is
278 // longer, the connection will reset itself at some point and interval will be
279 // fixed.
280 if (client_interval_ms_ > 0 && client_interval_ms_ < heartbeat_interval_ms_) {
281 ResetConnection(ConnectionFactory::NEW_HEARTBEAT_INTERVAL);
282 }
283 }
284
GetClientHeartbeatIntervalMs()285 int HeartbeatManager::GetClientHeartbeatIntervalMs() {
286 return client_interval_ms_;
287 }
288
HasClientHeartbeatInterval()289 bool HeartbeatManager::HasClientHeartbeatInterval() {
290 return client_interval_ms_ != 0;
291 }
292
IsValidClientHeartbeatInterval(int interval)293 bool HeartbeatManager::IsValidClientHeartbeatInterval(int interval) {
294 int max_heartbeat_interval = GetDefaultHeartbeatInterval();
295 return kMinClientHeartbeatIntervalMs <= interval &&
296 interval <= max_heartbeat_interval;
297 }
298
ResetConnection(ConnectionFactory::ConnectionResetReason reason)299 void HeartbeatManager::ResetConnection(
300 ConnectionFactory::ConnectionResetReason reason) {
301 Stop();
302 trigger_reconnect_callback_.Run(reason);
303 }
304
305 } // namespace gcm
306