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