File: facet_manager.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (237 lines) | stat: -rw-r--r-- 9,691 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Note: Read the class comment of AffiliationService for the definition
// of the terms used below.
//
// On-demand fetching strategy
//
// A GetAffiliationsAndBranding() request concerning facet X will be served from
// the cache as long as the cache contains fresh affiliation information for
// facet X, that is, if there is an equivalence class in the cache that contains
// X and has been fetched less than |kCacheHardExpiryInHours| hours ago.
//
// Otherwise, a network request is issued against the Affiliation API as soon as
// possible, that is, immediately if there is no fetch in flight, or right after
// completion of the fetch in flight if there is one, provided that the required
// data is not incidentally returned by the first fetch.
//
//
// Proactive fetching strategy
//
// A Prefetch() request concerning facet Y can trigger an initial network fetch,
// or periodic refetches only when:
//   * The prefetch request is not already expired, i.e., its |keep_fresh_until|
//     threshold is strictly in the future (that is, prefetch intervals are open
//     from the right).
//   * Affiliation information in the cache pertaining to facet Y will get stale
//     strictly before the specified |keep_fresh_until| threshold.
//
// An initial fetch will be issued as soon as possible if, in addition to the
// two necessery conditions above, and at the time of the Prefetch() call, the
// cache contains no affiliation information regarding facet Y, or if the data
// in the cache for facet Y is near-stale, that is, it has been fetched more
// than |kCacheHardExpiryInHours| hours ago.
//
// A refetch will be issued every time the data in the cache regarding facet Y
// becomes near-stale, that is, exactly |kCacheSoftExpiry| hours after the last
// fetch, provided that the above two necessary conditions are also met.
//
// Fetches are triggered already when the data gets near-stale, as opposed to
// waiting until the data would get stale, in an effort to keep the data fresh
// even in face of temporary network errors lasting no more than the difference
// between soft and hard expiry times.
//
// The current fetch scheduling logic, however, can only deal with at most one
// such 'early' fetch between taking place between the prior fetch and the
// corresponding hard expiry time of the data, therefore it is assumed that:
//
//   kCacheSoftExpiryInHours < kCacheHardExpiryInHours, and
//   2 * kCacheSoftExpiryInHours > kCacheHardExpiryInHours.
//
//
// Cache freshness terminology
//
//
//      Fetch (t=0)              kCacheSoftExpiry        kCacheHardExpiry
//      /                        /                       /
//  ---o------------------------o-----------------------o-----------------> t
//     |                        |                       |
//     |                        [-- Cache near-stale --------------------- ..
//     [--------------- Cache is fresh ----------------)[-- Cache is stale ..
//

#include "components/affiliations/core/browser/facet_manager.h"

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/task_runner.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/affiliations/core/browser/facet_manager_host.h"

namespace affiliations {

// statics
const int FacetManager::kCacheSoftExpiryInHours = 21;
const int FacetManager::kCacheHardExpiryInHours = 24;

static_assert(
    FacetManager::kCacheSoftExpiryInHours <
        FacetManager::kCacheHardExpiryInHours,
    "Soft expiry period must be shorter than the hard expiry period.");

static_assert(
    2 * FacetManager::kCacheSoftExpiryInHours >
        FacetManager::kCacheHardExpiryInHours,
    "Soft expiry period must be longer than half of the hard expiry period.");

// Encapsulates the details of a pending GetAffiliationsAndBranding() request.
struct FacetManager::RequestInfo {
  AffiliationService::ResultCallback callback;
  scoped_refptr<base::TaskRunner> callback_task_runner;
};

FacetManager::FacetManager(const FacetURI& facet_uri,
                           FacetManagerHost* backend,
                           base::Clock* clock)
    : facet_uri_(facet_uri), backend_(backend), clock_(clock) {
  AffiliatedFacetsWithUpdateTime affiliations;
  if (backend_->ReadAffiliationsAndBrandingFromDatabase(facet_uri_,
                                                        &affiliations))
    last_update_time_ = affiliations.last_update_time;
}

FacetManager::~FacetManager() = default;

void FacetManager::GetAffiliationsAndBranding(
    AffiliationService::ResultCallback callback,
    const scoped_refptr<base::TaskRunner>& callback_task_runner) {
  RequestInfo request_info;
  request_info.callback = std::move(callback);
  request_info.callback_task_runner = callback_task_runner;
  if (IsCachedDataFresh()) {
    AffiliatedFacetsWithUpdateTime affiliation;
    if (!backend_->ReadAffiliationsAndBrandingFromDatabase(facet_uri_,
                                                           &affiliation)) {
      ServeRequestWithFailure(std::move(request_info));
      return;
    }
    DCHECK_EQ(affiliation.last_update_time, last_update_time_) << facet_uri_;
    ServeRequestWithSuccess(std::move(request_info), affiliation.facets);
  } else {
    ServeRequestWithFailure(std::move(request_info));
  }
}

void FacetManager::Prefetch(const base::Time& keep_fresh_until) {
  keep_fresh_until_thresholds_.insert(keep_fresh_until);

  // If an initial fetch if needed, trigger that (the refetch will be scheduled
  // once the initial fetch completes). Otherwise schedule the next refetch.
  base::Time next_required_fetch(GetNextRequiredFetchTimeDueToPrefetch());
  if (next_required_fetch <= clock_->Now()) {
    backend_->SignalNeedNetworkRequest();
  } else if (next_required_fetch < base::Time::Max()) {
    backend_->RequestNotificationAtTime(facet_uri_, next_required_fetch);
  }

  // For a finite |keep_fresh_until|, schedule a callback so that once the
  // prefetch expires, it can be removed from |keep_fresh_untils_|, and also the
  // manager can get a chance to be destroyed unless it is otherwise needed.
  if (keep_fresh_until > clock_->Now() && keep_fresh_until < base::Time::Max())
    backend_->RequestNotificationAtTime(facet_uri_, keep_fresh_until);
}

void FacetManager::CancelPrefetch(const base::Time& keep_fresh_until) {
  auto iter = keep_fresh_until_thresholds_.find(keep_fresh_until);
  if (iter != keep_fresh_until_thresholds_.end())
    keep_fresh_until_thresholds_.erase(iter);
}

void FacetManager::UpdateLastFetchTime(base::Time last_update_time) {
  last_update_time_ = last_update_time;
  DCHECK(IsCachedDataFresh()) << facet_uri_;
  base::Time next_required_fetch(GetNextRequiredFetchTimeDueToPrefetch());
  if (next_required_fetch < base::Time::Max())
    backend_->RequestNotificationAtTime(facet_uri_, next_required_fetch);
}

void FacetManager::NotifyAtRequestedTime() {
  base::Time next_required_fetch(GetNextRequiredFetchTimeDueToPrefetch());
  if (next_required_fetch <= clock_->Now()) {
    backend_->SignalNeedNetworkRequest();
  } else if (next_required_fetch < base::Time::Max()) {
    backend_->RequestNotificationAtTime(facet_uri_, next_required_fetch);
  }

  auto iter_first_non_expired =
      keep_fresh_until_thresholds_.upper_bound(clock_->Now());
  keep_fresh_until_thresholds_.erase(keep_fresh_until_thresholds_.begin(),
                                     iter_first_non_expired);
}

bool FacetManager::CanBeDiscarded() const {
  return GetMaximumKeepFreshUntilThreshold() <= clock_->Now();
}

bool FacetManager::CanCachedDataBeDiscarded() const {
  return GetMaximumKeepFreshUntilThreshold() <= clock_->Now() ||
         !IsCachedDataFresh();
}

bool FacetManager::DoesRequireFetch() const {
  return GetNextRequiredFetchTimeDueToPrefetch() <= clock_->Now();
}

bool FacetManager::IsCachedDataFresh() const {
  return clock_->Now() < GetCacheHardExpiryTime();
}

bool FacetManager::IsCachedDataNearStale() const {
  return GetCacheSoftExpiryTime() <= clock_->Now();
}

base::Time FacetManager::GetCacheSoftExpiryTime() const {
  return last_update_time_ + base::Hours(kCacheSoftExpiryInHours);
}

base::Time FacetManager::GetCacheHardExpiryTime() const {
  return last_update_time_ + base::Hours(kCacheHardExpiryInHours);
}

base::Time FacetManager::GetMaximumKeepFreshUntilThreshold() const {
  return !keep_fresh_until_thresholds_.empty()
             ? *keep_fresh_until_thresholds_.rbegin()
             : base::Time();
}

base::Time FacetManager::GetNextRequiredFetchTimeDueToPrefetch() const {
  // If there is at least one non-expired Prefetch() request that requires the
  // data to be kept fresh until some time later than its current hard expiry
  // time, then a fetch is needed once the cached data becomes near-stale.
  if (clock_->Now() < GetMaximumKeepFreshUntilThreshold() &&
      GetCacheHardExpiryTime() < GetMaximumKeepFreshUntilThreshold()) {
    return GetCacheSoftExpiryTime();
  }
  return base::Time::Max();
}

// static
void FacetManager::ServeRequestWithSuccess(
    RequestInfo request_info,
    const AffiliatedFacets& affiliation) {
  request_info.callback_task_runner->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(request_info.callback), affiliation, true));
}

// static
void FacetManager::ServeRequestWithFailure(RequestInfo request_info) {
  request_info.callback_task_runner->PostTask(
      FROM_HERE, base::BindOnce(std::move(request_info.callback),
                                AffiliatedFacets(), false));
}

}  // namespace affiliations