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