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