blob: cf379513c967cbd47e2727e02b2c1c878a164ec9 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/nearby_sharing/scheduling/nearby_share_scheduler_base.h"
#include <algorithm>
#include <sstream>
#include <utility>
#include "base/i18n/time_formatting.h"
#include "base/json/values_util.h"
#include "base/numerics/clamped_math.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/clock.h"
#include "chrome/browser/nearby_sharing/logging/logging.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/network_service_instance.h"
namespace {
constexpr base::TimeDelta kZeroTimeDelta = base::Seconds(0);
constexpr base::TimeDelta kBaseRetryDelay = base::Seconds(5);
constexpr base::TimeDelta kMaxRetryDelay = base::Hours(1);
const char kLastAttemptTimeKeyName[] = "a";
const char kLastSuccessTimeKeyName[] = "s";
const char kNumConsecutiveFailuresKeyName[] = "f";
const char kHasPendingImmediateRequestKeyName[] = "p";
const char kIsWaitingForResultKeyName[] = "w";
} // namespace
NearbyShareSchedulerBase::NearbyShareSchedulerBase(bool retry_failures,
bool require_connectivity,
const std::string& pref_name,
PrefService* pref_service,
OnRequestCallback callback,
const base::Clock* clock)
: NearbyShareScheduler(std::move(callback)),
retry_failures_(retry_failures),
require_connectivity_(require_connectivity),
pref_name_(pref_name),
pref_service_(pref_service),
clock_(clock) {
DCHECK(pref_service_);
InitializePersistedRequest();
if (require_connectivity_) {
content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
}
}
NearbyShareSchedulerBase::~NearbyShareSchedulerBase() {
if (require_connectivity_) {
content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(
this);
}
}
void NearbyShareSchedulerBase::MakeImmediateRequest() {
timer_.Stop();
SetHasPendingImmediateRequest(true);
Reschedule();
}
void NearbyShareSchedulerBase::HandleResult(bool success) {
base::Time now = clock_->Now();
SetLastAttemptTime(now);
NS_LOG(VERBOSE) << "Nearby Share scheduler \"" << pref_name_
<< "\" latest attempt " << (success ? "succeeded" : "failed");
if (success) {
SetLastSuccessTime(now);
SetNumConsecutiveFailures(0);
} else {
SetNumConsecutiveFailures(base::ClampAdd(GetNumConsecutiveFailures(), 1));
}
SetIsWaitingForResult(false);
Reschedule();
PrintSchedulerState();
}
void NearbyShareSchedulerBase::Reschedule() {
if (!is_running())
return;
timer_.Stop();
absl::optional<base::TimeDelta> delay = GetTimeUntilNextRequest();
if (!delay)
return;
timer_.Start(FROM_HERE, *delay,
base::BindOnce(&NearbyShareSchedulerBase::OnTimerFired,
base::Unretained(this)));
}
absl::optional<base::Time> NearbyShareSchedulerBase::GetLastSuccessTime()
const {
return base::ValueToTime(pref_service_->GetDictionary(pref_name_)
->FindKey(kLastSuccessTimeKeyName));
}
absl::optional<base::TimeDelta>
NearbyShareSchedulerBase::GetTimeUntilNextRequest() const {
if (!is_running() || IsWaitingForResult())
return absl::nullopt;
if (HasPendingImmediateRequest())
return kZeroTimeDelta;
base::Time now = clock_->Now();
// Recover from failures using exponential backoff strategy if necessary.
absl::optional<base::TimeDelta> time_until_retry = TimeUntilRetry(now);
if (time_until_retry)
return time_until_retry;
// Schedule the periodic request if applicable.
return TimeUntilRecurringRequest(now);
}
bool NearbyShareSchedulerBase::IsWaitingForResult() const {
return pref_service_->GetDictionary(pref_name_)
->FindBoolKey(kIsWaitingForResultKeyName)
.value_or(false);
}
size_t NearbyShareSchedulerBase::GetNumConsecutiveFailures() const {
const std::string* str = pref_service_->GetDictionary(pref_name_)
->FindStringKey(kNumConsecutiveFailuresKeyName);
if (!str)
return 0;
size_t num_failures = 0;
if (!base::StringToSizeT(*str, &num_failures))
return 0;
return num_failures;
}
void NearbyShareSchedulerBase::OnStart() {
Reschedule();
NS_LOG(VERBOSE) << "Starting Nearby Share scheduler \"" << pref_name_ << "\"";
PrintSchedulerState();
}
void NearbyShareSchedulerBase::OnStop() {
timer_.Stop();
}
void NearbyShareSchedulerBase::OnConnectionChanged(
network::mojom::ConnectionType type) {
if (content::GetNetworkConnectionTracker()->IsOffline())
return;
Reschedule();
}
absl::optional<base::Time> NearbyShareSchedulerBase::GetLastAttemptTime()
const {
return base::ValueToTime(pref_service_->GetDictionary(pref_name_)
->FindKey(kLastAttemptTimeKeyName));
}
bool NearbyShareSchedulerBase::HasPendingImmediateRequest() const {
return pref_service_->GetDictionary(pref_name_)
->FindBoolKey(kHasPendingImmediateRequestKeyName)
.value_or(false);
}
void NearbyShareSchedulerBase::SetLastAttemptTime(
base::Time last_attempt_time) {
DictionaryPrefUpdateDeprecated(pref_service_, pref_name_)
.Get()
->SetKey(kLastAttemptTimeKeyName, base::TimeToValue(last_attempt_time));
}
void NearbyShareSchedulerBase::SetLastSuccessTime(
base::Time last_success_time) {
DictionaryPrefUpdateDeprecated(pref_service_, pref_name_)
.Get()
->SetKey(kLastSuccessTimeKeyName, base::TimeToValue(last_success_time));
}
void NearbyShareSchedulerBase::SetNumConsecutiveFailures(size_t num_failures) {
DictionaryPrefUpdateDeprecated(pref_service_, pref_name_)
.Get()
->SetStringKey(kNumConsecutiveFailuresKeyName,
base::NumberToString(num_failures));
}
void NearbyShareSchedulerBase::SetHasPendingImmediateRequest(
bool has_pending_immediate_request) {
DictionaryPrefUpdateDeprecated(pref_service_, pref_name_)
.Get()
->SetBoolKey(kHasPendingImmediateRequestKeyName,
has_pending_immediate_request);
}
void NearbyShareSchedulerBase::SetIsWaitingForResult(
bool is_waiting_for_result) {
DictionaryPrefUpdateDeprecated(pref_service_, pref_name_)
.Get()
->SetBoolKey(kIsWaitingForResultKeyName, is_waiting_for_result);
}
void NearbyShareSchedulerBase::InitializePersistedRequest() {
if (IsWaitingForResult()) {
SetHasPendingImmediateRequest(true);
SetIsWaitingForResult(false);
}
}
absl::optional<base::TimeDelta> NearbyShareSchedulerBase::TimeUntilRetry(
base::Time now) const {
if (!retry_failures_)
return absl::nullopt;
size_t num_failures = GetNumConsecutiveFailures();
if (num_failures == 0)
return absl::nullopt;
// The exponential back off is
//
// base * 2^(num_failures - 1)
//
// up to a fixed maximum retry delay.
base::TimeDelta delay =
std::min(kMaxRetryDelay, kBaseRetryDelay * (1 << (num_failures - 1)));
base::TimeDelta time_elapsed_since_last_attempt = now - *GetLastAttemptTime();
return std::max(kZeroTimeDelta, delay - time_elapsed_since_last_attempt);
}
void NearbyShareSchedulerBase::OnTimerFired() {
DCHECK(is_running());
if (require_connectivity_ &&
content::GetNetworkConnectionTracker()->IsOffline()) {
return;
}
SetIsWaitingForResult(true);
SetHasPendingImmediateRequest(false);
NotifyOfRequest();
}
void NearbyShareSchedulerBase::PrintSchedulerState() const {
absl::optional<base::Time> last_attempt_time = GetLastAttemptTime();
absl::optional<base::Time> last_success_time = GetLastSuccessTime();
absl::optional<base::TimeDelta> time_until_next_request =
GetTimeUntilNextRequest();
std::stringstream ss;
ss << "State of Nearby Share scheduler \"" << pref_name_ << "\":"
<< "\n Last attempt time: ";
if (last_attempt_time) {
ss << base::TimeFormatShortDateAndTimeWithTimeZone(*last_attempt_time);
} else {
ss << "Never";
}
ss << "\n Last success time: ";
if (last_success_time) {
ss << base::TimeFormatShortDateAndTimeWithTimeZone(*last_success_time);
} else {
ss << "Never";
}
ss << "\n Time until next request: ";
if (time_until_next_request) {
std::u16string next_request_delay;
bool success = base::TimeDurationFormatWithSeconds(
*time_until_next_request,
base::DurationFormatWidth::DURATION_WIDTH_NARROW, &next_request_delay);
if (success) {
ss << next_request_delay;
}
} else {
ss << "Never";
}
ss << "\n Is waiting for result? " << (IsWaitingForResult() ? "Yes" : "No");
ss << "\n Pending immediate request? "
<< (HasPendingImmediateRequest() ? "Yes" : "No");
ss << "\n Num consecutive failures: " << GetNumConsecutiveFailures();
NS_LOG(VERBOSE) << ss.str();
}