Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(678)

Side by Side Diff: components/translate/core/browser/ranker_model_loader.cc

Issue 2839433002: [translate] Fix shutdown race for translate ranker model loader. (Closed)
Patch Set: comments from fdoray Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/translate/core/browser/ranker_model_loader.h" 5 #include "components/translate/core/browser/ranker_model_loader.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/bind_helpers.h" 8 #include "base/bind_helpers.h"
9 #include "base/command_line.h" 9 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h" 10 #include "base/files/file_util.h"
12 #include "base/files/important_file_writer.h" 11 #include "base/files/important_file_writer.h"
13 #include "base/macros.h" 12 #include "base/macros.h"
14 #include "base/memory/ptr_util.h" 13 #include "base/memory/ptr_util.h"
15 #include "base/metrics/histogram_macros.h" 14 #include "base/metrics/histogram_macros.h"
16 #include "base/profiler/scoped_tracker.h" 15 #include "base/profiler/scoped_tracker.h"
17 #include "base/sequenced_task_runner.h" 16 #include "base/sequenced_task_runner.h"
18 #include "base/strings/string_util.h" 17 #include "base/strings/string_util.h"
18 #include "base/task_runner_util.h"
19 #include "base/task_scheduler/post_task.h" 19 #include "base/task_scheduler/post_task.h"
20 #include "base/threading/sequenced_task_runner_handle.h" 20 #include "base/threading/sequenced_task_runner_handle.h"
21 #include "components/translate/core/browser/proto/ranker_model.pb.h" 21 #include "components/translate/core/browser/proto/ranker_model.pb.h"
22 #include "components/translate/core/browser/ranker_model.h" 22 #include "components/translate/core/browser/ranker_model.h"
23 #include "components/translate/core/browser/translate_url_fetcher.h" 23 #include "components/translate/core/browser/translate_url_fetcher.h"
24 #include "url/gurl.h"
25 24
26 namespace translate { 25 namespace translate {
27 namespace { 26 namespace {
28 27
29 using chrome_intelligence::RankerModel; 28 using chrome_intelligence::RankerModel;
30 using chrome_intelligence::RankerModelProto; 29 using chrome_intelligence::RankerModelProto;
31 30
32 constexpr int kUrlFetcherId = 2; 31 constexpr int kUrlFetcherId = 2;
33 32
34 // The maximum number of model download attempts to make. Download may fail
35 // due to server error or network availability issues.
36 constexpr int kMaxDownloadAttempts = 8;
37
38 // The minimum duration, in minutes, between download attempts. 33 // The minimum duration, in minutes, between download attempts.
39 constexpr int kMinRetryDelayMins = 3; 34 constexpr int kMinRetryDelayMins = 3;
40 35
41 // Suffixes for the various histograms produced by the backend. 36 // Suffixes for the various histograms produced by the backend.
42 const char kWriteTimerHistogram[] = ".Timer.WriteModel"; 37 const char kWriteTimerHistogram[] = ".Timer.WriteModel";
43 const char kReadTimerHistogram[] = ".Timer.ReadModel"; 38 const char kReadTimerHistogram[] = ".Timer.ReadModel";
44 const char kDownloadTimerHistogram[] = ".Timer.DownloadModel"; 39 const char kDownloadTimerHistogram[] = ".Timer.DownloadModel";
45 const char kParsetimerHistogram[] = ".Timer.ParseModel"; 40 const char kParsetimerHistogram[] = ".Timer.ParseModel";
46 const char kModelStatusHistogram[] = ".Model.Status"; 41 const char kModelStatusHistogram[] = ".Model.Status";
47 42
43 // Helper function to UMA log a timer histograms.
44 void RecordTimerHistogram(const std::string& name, base::TimeDelta duration) {
45 base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
46 name, base::TimeDelta::FromMilliseconds(10),
47 base::TimeDelta::FromMilliseconds(200000), 100,
48 base::HistogramBase::kUmaTargetedHistogramFlag);
49 DCHECK(counter);
50 counter->AddTime(duration);
51 }
52
48 // A helper class to produce a scoped timer histogram that supports using a 53 // A helper class to produce a scoped timer histogram that supports using a
49 // non-static-const name. 54 // non-static-const name.
50 class MyScopedHistogramTimer { 55 class MyScopedHistogramTimer {
51 public: 56 public:
52 MyScopedHistogramTimer(const base::StringPiece& name) 57 MyScopedHistogramTimer(const base::StringPiece& name)
53 : name_(name.begin(), name.end()), start_(base::TimeTicks::Now()) {} 58 : name_(name.begin(), name.end()), start_(base::TimeTicks::Now()) {}
54 59
55 ~MyScopedHistogramTimer() { 60 ~MyScopedHistogramTimer() {
56 base::TimeDelta duration = base::TimeTicks::Now() - start_; 61 RecordTimerHistogram(name_, base::TimeTicks::Now() - start_);
57 base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
58 name_, base::TimeDelta::FromMilliseconds(10),
59 base::TimeDelta::FromMilliseconds(200000), 100,
60 base::HistogramBase::kUmaTargetedHistogramFlag);
61 if (counter)
62 counter->AddTime(duration);
63 } 62 }
64 63
65 private: 64 private:
66 const std::string name_; 65 const std::string name_;
67 const base::TimeTicks start_; 66 const base::TimeTicks start_;
68 67
69 DISALLOW_COPY_AND_ASSIGN(MyScopedHistogramTimer); 68 DISALLOW_COPY_AND_ASSIGN(MyScopedHistogramTimer);
70 }; 69 };
71 70
71 std::string LoadFromFile(const base::FilePath& model_path) {
72 DCHECK(!model_path.empty());
73 DVLOG(2) << "Reading data from: " << model_path.value();
74 std::string data;
75 if (!base::ReadFileToString(model_path, &data) || data.empty()) {
76 DVLOG(2) << "Failed to read data from: " << model_path.value();
77 data.clear();
78 }
79 return data;
80 }
81
82 void SaveToFile(const GURL& model_url,
83 const base::FilePath& model_path,
84 const std::string& model_data,
85 const std::string& uma_prefix) {
86 DVLOG(2) << "Saving model from '" << model_url << "'' to '"
87 << model_path.value() << "'.";
88 MyScopedHistogramTimer timer(uma_prefix + kWriteTimerHistogram);
89 base::ImportantFileWriter::WriteFileAtomically(model_path, model_data);
90 }
91
72 } // namespace 92 } // namespace
73 93
74 // ============================================================================= 94 RankerModelLoader::RankerModelLoader(
75 // RankerModelLoader::Backend
76
77 class RankerModelLoader::Backend {
78 public:
79 // An internal version of RankerModelLoader::OnModelAvailableCallback that
80 // bundles calling the real callback with a notification of whether or not
81 // tha backend is finished.
82 using InternalOnModelAvailableCallback =
83 base::Callback<void(std::unique_ptr<RankerModel>, bool)>;
84
85 Backend(const ValidateModelCallback& validate_model_cb,
86 const InternalOnModelAvailableCallback& on_model_available_cb,
87 const base::FilePath& model_path,
88 const GURL& model_url,
89 const std::string& uma_prefix);
90 ~Backend();
91
92 // Reads the model from |model_path_|.
93 void LoadFromFile();
94
95 // Reads the model from |model_url_|.
96 void AsyncLoadFromURL();
97
98 private:
99 // Log and return the result of loading a model to UMA.
100 RankerModelStatus ReportModelStatus(RankerModelStatus model_status);
101
102 // Constructs a model from the given |data|.
103 std::unique_ptr<chrome_intelligence::RankerModel> CreateModel(
104 const std::string& data);
105
106 // Accepts downloaded model data. This signature is mandated by the callback
107 // defined by TransalteURLFetcher.
108 //
109 // id - the id given to the TranslateURLFetcher on creation
110 // success - true of the download was successful
111 // data - the body of the downloads response
112 void OnDownloadComplete(int id, bool success, const std::string& data);
113
114 // Transfers ownership of |model| to the client using the
115 // |internal_on_model_available_cb_|. |is_finished| denotes whether the
116 // backend is finished (or has given up on) loading the model.
117 void TransferModelToClient(
118 std::unique_ptr<chrome_intelligence::RankerModel> model,
119 bool is_finished);
120
121 // Validates that ranker model loader backend tasks are all performed on the
122 // same sequence.
123 base::SequenceChecker sequence_checker_;
124
125 // The TaskRunner on which |this| was constructed.
126 const scoped_refptr<base::SequencedTaskRunner> origin_task_runner_;
127
128 // Validates a ranker model on behalf of the model loader client. This may
129 // be called on any sequence and must, therefore, be thread-safe.
130 const ValidateModelCallback validate_model_cb_;
131
132 // Transfers ownership of a loaded model back to the model loader client.
133 // This will be called on the sequence on which the model loader was
134 // constructed.
135 const InternalOnModelAvailableCallback internal_on_model_available_cb_;
136
137 // The path at which the model is (or should be) cached.
138 const base::FilePath model_path_;
139
140 // The URL from which to download the model if the model is not in the cache
141 // or the cached model is invalid/expired.
142 const GURL model_url_;
143
144 // This will prefix all UMA metrics generated by the model loader.
145 const std::string uma_prefix_;
146
147 // Used to download model data from |model_url_|.
148 // TODO(rogerm): Use net::URLFetcher directly?
149 std::unique_ptr<TranslateURLFetcher> url_fetcher_;
150
151 // The number of times the backend has attempted to download the model.
152 int download_attempts_;
153
154 // The next time before which no new attempts to download the model should be
155 // attempted.
156 base::TimeTicks next_earliest_download_time_;
157
158 // Tracks the last time of the last attempt to download a model. Used for UMA
159 // reporting of download duration.
160 base::TimeTicks download_start_time_;
161
162 DISALLOW_COPY_AND_ASSIGN(Backend);
163 };
164
165 RankerModelLoader::Backend::Backend(
166 const ValidateModelCallback& validate_model_cb, 95 const ValidateModelCallback& validate_model_cb,
fdoray 2017/04/25 22:09:08 It is more efficient to take callbacks by value an
Roger McFarlane (Chromium) 2017/04/26 02:18:50 Done.
167 const InternalOnModelAvailableCallback& internal_on_model_available_cb, 96 const OnModelAvailableCallback& on_model_available_cb,
168 const base::FilePath& model_path, 97 const base::FilePath& model_path,
169 const GURL& model_url, 98 const GURL& model_url,
170 const std::string& uma_prefix) 99 const std::string& uma_prefix)
171 : origin_task_runner_(base::SequencedTaskRunnerHandle::Get()), 100 : background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
101 base::TaskTraits()
102 .MayBlock()
103 .WithPriority(base::TaskPriority::BACKGROUND)
104 .WithShutdownBehavior(
105 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN))),
172 validate_model_cb_(validate_model_cb), 106 validate_model_cb_(validate_model_cb),
173 internal_on_model_available_cb_(internal_on_model_available_cb), 107 on_model_available_cb_(on_model_available_cb),
174 model_path_(model_path), 108 model_path_(model_path),
175 model_url_(model_url), 109 model_url_(model_url),
176 uma_prefix_(uma_prefix), 110 uma_prefix_(uma_prefix),
177 url_fetcher_(base::MakeUnique<TranslateURLFetcher>(kUrlFetcherId)), 111 url_fetcher_(base::MakeUnique<TranslateURLFetcher>(kUrlFetcherId)),
178 download_attempts_(0) { 112 weak_ptr_factory_(this) {}
179 sequence_checker_.DetachFromSequence(); 113
180 } 114 RankerModelLoader::~RankerModelLoader() {
181 115 DCHECK(sequence_checker_.CalledOnValidSequence());
182 RankerModelLoader::Backend::~Backend() {} 116 }
183 117
184 RankerModelStatus RankerModelLoader::Backend::ReportModelStatus( 118 void RankerModelLoader::NotifyOfRankerActivity() {
119 DCHECK(sequence_checker_.CalledOnValidSequence());
120 switch (state_) {
121 case LoaderState::NOT_STARTED:
122 if (!model_path_.empty()) {
123 StartLoadFromFile();
124 break;
125 }
126 // There was no configured model path. Switch the state to IDLE and
127 // fall through to consider the URL.
128 state_ = LoaderState::IDLE;
129 case LoaderState::IDLE:
130 if (model_url_.is_valid()) {
131 StartLoadFromURL();
132 break;
133 }
134 // There was no configured model URL. Switch the state to FINISHED and
135 // fall through.
136 state_ = LoaderState::FINISHED;
137 case LoaderState::FINISHED:
138 case LoaderState::LOADING_FROM_FILE:
139 case LoaderState::LOADING_FROM_URL:
140 // Nothing to do.
141 break;
142 }
143 }
144
145 void RankerModelLoader::StartLoadFromFile() {
146 DCHECK(sequence_checker_.CalledOnValidSequence());
147 DCHECK_EQ(state_, LoaderState::NOT_STARTED);
148 DCHECK(!model_path_.empty());
149 state_ = LoaderState::LOADING_FROM_FILE;
150 load_start_time_ = base::TimeTicks::Now();
151 base::PostTaskAndReplyWithResult(background_task_runner_.get(), FROM_HERE,
152 base::Bind(&LoadFromFile, model_path_),
fdoray 2017/04/25 22:09:08 base::BindOnce
Roger McFarlane (Chromium) 2017/04/26 02:18:50 Alas, this fails to compile (on Linux at least) if
153 base::Bind(&RankerModelLoader::OnFileLoaded,
154 weak_ptr_factory_.GetWeakPtr()));
155 }
156
157 void RankerModelLoader::OnFileLoaded(const std::string& data) {
158 DCHECK(sequence_checker_.CalledOnValidSequence());
159 DCHECK_EQ(state_, LoaderState::LOADING_FROM_FILE);
160
161 // Record the duration of the download.
162 RecordTimerHistogram(uma_prefix_ + kReadTimerHistogram,
163 base::TimeTicks::Now() - load_start_time_);
164
165 // Empty data means |model_path| wasn't successfully read. Otherwise,
166 // parse and validate the model.
167 std::unique_ptr<RankerModel> model;
168 if (data.empty()) {
169 ReportModelStatus(RankerModelStatus::LOAD_FROM_CACHE_FAILED);
170 } else {
171 model = CreateAndValidateModel(data);
172 }
173
174 // If |model| is nullptr, then data is empty or the parse failed. Transition
175 // to IDLE, from which URL download can be attempted.
176 if (!model) {
177 state_ = LoaderState::IDLE;
178 } else {
179 // The model is valid. The client is willing/able to use it. Keep track
180 // of where it originated and whether or not is has expired.
181 std::string url_spec = model->GetSourceURL();
182 bool is_expired = model->IsExpired();
183 bool is_finished = url_spec == model_url_.spec() && !is_expired;
184
185 DVLOG(2) << (is_expired ? "Expired m" : "M") << "odel in '"
186 << model_path_.value() << "' was originally downloaded from '"
187 << url_spec << "'.";
188
189 // If the cached model came from currently configured |model_url_| and has
190 // not expired, transition to FINISHED, as there is no need for a model
191 // download; otherwise, transition to IDLE.
192 state_ = is_finished ? LoaderState::FINISHED : LoaderState::IDLE;
193
194 // Transfer the model to the client.
195 on_model_available_cb_.Run(std::move(model));
196 }
197
198 // Notify the state machine. This will immediately kick off a download if
199 // one is required, instead of waiting for the next organic detection of
200 // ranker activity.
201 NotifyOfRankerActivity();
202 }
203
204 void RankerModelLoader::StartLoadFromURL() {
205 DCHECK(sequence_checker_.CalledOnValidSequence());
206 DCHECK_EQ(state_, LoaderState::IDLE);
207 DCHECK(model_url_.is_valid());
208
209 // Do nothing if the download attempts should be throttled.
210 if (base::TimeTicks::Now() < next_earliest_download_time_) {
211 DVLOG(2) << "Last download attempt was too recent.";
212 ReportModelStatus(RankerModelStatus::DOWNLOAD_THROTTLED);
213 return;
214 }
215
216 // Kick off the next download attempt and reset the time of the next earliest
217 // allowable download attempt.
218 DVLOG(2) << "Downloading model from: " << model_url_;
219 state_ = LoaderState::LOADING_FROM_URL;
220 load_start_time_ = base::TimeTicks::Now();
221 next_earliest_download_time_ =
222 load_start_time_ + base::TimeDelta::FromMinutes(kMinRetryDelayMins);
223 bool request_started = url_fetcher_->Request(
224 model_url_, base::Bind(&RankerModelLoader::OnURLFetched,
225 weak_ptr_factory_.GetWeakPtr()));
226
227 // |url_fetcher_| maintains a request retry counter. If all allowed attempts
228 // have already been exhausted, then the loader is finished and has abandoned
229 // loading the model.
230 if (!request_started) {
231 DVLOG(2) << "Model download abandoned.";
232 ReportModelStatus(RankerModelStatus::MODEL_LOADING_ABANDONED);
233 state_ = LoaderState::FINISHED;
234 }
235 }
236
237 void RankerModelLoader::OnURLFetched(int /* id */,
238 bool success,
239 const std::string& data) {
240 DCHECK(sequence_checker_.CalledOnValidSequence());
241 DCHECK_EQ(state_, LoaderState::LOADING_FROM_URL);
242
243 // Record the duration of the download.
244 RecordTimerHistogram(uma_prefix_ + kDownloadTimerHistogram,
245 base::TimeTicks::Now() - load_start_time_);
246
247 // On request failure, transition back to IDLE. The loader will retry, or
248 // enforce the max download attempts, later.
249 if (!success || data.empty()) {
250 DVLOG(2) << "Download from '" << model_url_ << "'' failed.";
251 ReportModelStatus(RankerModelStatus::DOWNLOAD_FAILED);
252 state_ = LoaderState::IDLE;
253 return;
254 }
255
256 // Attempt to loads the model. If this fails, transition back to IDLE. The
257 // loader will retry, or enfore the max download attempts, later.
258 auto model = CreateAndValidateModel(data);
259 if (!model) {
260 DVLOG(2) << "Model from '" << model_url_ << "'' not valid.";
261 state_ = LoaderState::IDLE;
262 return;
263 }
264
265 // The model is valid. Update the metadata to track the source URL and
266 // download timestamp.
267 auto* metadata = model->mutable_proto()->mutable_metadata();
268 metadata->set_source(model_url_.spec());
269 metadata->set_last_modified_sec(
270 (base::Time::Now() - base::Time()).InSeconds());
271
272 // Cache the model to model_path_, in the background.
273 if (!model_path_.empty()) {
274 background_task_runner_->PostTask(
275 FROM_HERE, base::BindOnce(&SaveToFile, model_url_, model_path_,
276 model->SerializeAsString(), uma_prefix_));
277 }
278
279 // The loader is finished. Transfer the model to the client.
280 state_ = LoaderState::FINISHED;
281 on_model_available_cb_.Run(std::move(model));
282 }
283
284 std::unique_ptr<chrome_intelligence::RankerModel>
285 RankerModelLoader::CreateAndValidateModel(const std::string& data) {
286 DCHECK(sequence_checker_.CalledOnValidSequence());
287 MyScopedHistogramTimer timer(uma_prefix_ + kParsetimerHistogram);
288 auto model = RankerModel::FromString(data);
289 if (ReportModelStatus(model ? validate_model_cb_.Run(*model)
290 : RankerModelStatus::PARSE_FAILED) !=
291 RankerModelStatus::OK) {
292 return nullptr;
293 }
294 return model;
295 }
296
297 RankerModelStatus RankerModelLoader::ReportModelStatus(
185 RankerModelStatus model_status) { 298 RankerModelStatus model_status) {
299 DCHECK(sequence_checker_.CalledOnValidSequence());
186 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( 300 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
187 uma_prefix_ + kModelStatusHistogram, 1, 301 uma_prefix_ + kModelStatusHistogram, 1,
188 static_cast<int>(RankerModelStatus::MAX), 302 static_cast<int>(RankerModelStatus::MAX),
189 static_cast<int>(RankerModelStatus::MAX) + 1, 303 static_cast<int>(RankerModelStatus::MAX) + 1,
190 base::HistogramBase::kUmaTargetedHistogramFlag); 304 base::HistogramBase::kUmaTargetedHistogramFlag);
191 if (histogram) 305 if (histogram)
192 histogram->Add(static_cast<int>(model_status)); 306 histogram->Add(static_cast<int>(model_status));
193 return model_status; 307 return model_status;
194 } 308 }
195 309
196 std::unique_ptr<chrome_intelligence::RankerModel>
197 RankerModelLoader::Backend::CreateModel(const std::string& data) {
198 DCHECK(sequence_checker_.CalledOnValidSequence());
199 MyScopedHistogramTimer timer(uma_prefix_ + kParsetimerHistogram);
200 auto model = RankerModel::FromString(data);
201 if (ReportModelStatus(model ? validate_model_cb_.Run(*model)
202 : RankerModelStatus::PARSE_FAILED) !=
203 RankerModelStatus::OK) {
204 return nullptr;
205 }
206 return model;
207 }
208
209 void RankerModelLoader::Backend::LoadFromFile() {
210 DCHECK(sequence_checker_.CalledOnValidSequence());
211
212 // If there is not cache path set, move on to loading the model by URL.
213 if (model_path_.empty()) {
214 AsyncLoadFromURL();
215 return;
216 }
217
218 DVLOG(2) << "Attempting to load model from: " << model_path_.value();
219
220 std::string data;
221
222 {
223 MyScopedHistogramTimer timer(uma_prefix_ + kReadTimerHistogram);
224 if (!base::ReadFileToString(model_path_, &data) || data.empty()) {
225 DVLOG(2) << "Failed to read model from: " << model_path_.value();
226 data.clear();
227 }
228 }
229
230 // If model data was loaded, check if it can be parsed to a valid model.
231 if (!data.empty()) {
232 auto model = CreateModel(data);
233 if (model) {
234 // The model is valid. The client is willing/able to use it. Keep track
235 // of where it originated and whether or not is has expired.
236 std::string url_spec = model->GetSourceURL();
237 bool is_expired = model->IsExpired();
238 bool is_finished = url_spec == model_url_.spec() && !is_expired;
239
240 DVLOG(2) << (is_expired ? "Expired m" : "M") << "odel in '"
241 << model_path_.value() << "' was originally downloaded from '"
242 << url_spec << "'.";
243
244 // Transfer the model to the client. Beyond this line, |model| is invalid.
245 TransferModelToClient(std::move(model), is_finished);
246
247 // If the cached model came from currently configured |model_url_| and has
248 // not expired, there is no need schedule a model download.
249 if (is_finished)
250 return;
251
252 // Otherwise, fall out of this block to schedule a download. The client
253 // can continue to use the valid but expired model until the download
254 // completes.
255 }
256 }
257
258 // Reaching this point means that a model download is required. If there is
259 // no download URL configured, then there is nothing further to do.
260 AsyncLoadFromURL();
261 }
262
263 void RankerModelLoader::Backend::AsyncLoadFromURL() {
264 DCHECK(sequence_checker_.CalledOnValidSequence());
265
266 if (!model_url_.is_valid()) {
267 // Notify the loader that model loading has been abandoned.
268 TransferModelToClient(nullptr, true);
269 return;
270 }
271
272 // Do nothing if the download attempts should be throttled.
273 if (base::TimeTicks::Now() < next_earliest_download_time_) {
274 DVLOG(2) << "Last download attempt was too recent.";
275 return;
276 }
277
278 // If a request is already in flight, do not issue a new one.
279 if (url_fetcher_->state() == TranslateURLFetcher::REQUESTING) {
280 DVLOG(2) << "Download is in progress.";
281 return;
282 }
283
284 // If all allowed attempts have been exhausted, notify the loader that the
285 // backend has abandoned the download.
286 if (++download_attempts_ > kMaxDownloadAttempts) {
287 DVLOG(2) << "Model download abandoned.";
288 ReportModelStatus(RankerModelStatus::DOWNLOAD_FAILED);
289 TransferModelToClient(nullptr, true);
290 return;
291 }
292
293 DVLOG(2) << "Downloading model from: " << model_url_;
294
295 // Kick off the next download attempt and reset the time of the next earliest
296 // allowable download attempt.
297 download_start_time_ = base::TimeTicks::Now();
298 next_earliest_download_time_ =
299 download_start_time_ + base::TimeDelta::FromMinutes(kMinRetryDelayMins);
300 url_fetcher_->Request(
301 model_url_, base::Bind(&RankerModelLoader::Backend::OnDownloadComplete,
302 base::Unretained(this)));
303 }
304
305 void RankerModelLoader::Backend::OnDownloadComplete(int /* id */,
306 bool success,
307 const std::string& data) {
308 DCHECK(sequence_checker_.CalledOnValidSequence());
309
310 // Record the duration of the download.
311 base::TimeDelta duration = base::TimeTicks::Now() - download_start_time_;
312 base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
313 uma_prefix_ + kDownloadTimerHistogram,
314 base::TimeDelta::FromMilliseconds(10),
315 base::TimeDelta::FromMilliseconds(200000), 100,
316 base::HistogramBase::kUmaTargetedHistogramFlag);
317 if (counter)
318 counter->AddTime(duration);
319
320 // On failure, we just abort. The TranslateRanker will retry on a subsequent
321 // translation opportunity. The TranslateURLFetcher enforces a limit for
322 // retried requests.
323 if (!success || data.empty()) {
324 DVLOG(2) << "Download from '" << model_url_ << "'' failed.";
325 return;
326 }
327
328 auto model = CreateModel(data);
329 if (!model) {
330 DVLOG(2) << "Model from '" << model_url_ << "'' not valid.";
331 return;
332 }
333
334 url_fetcher_.reset();
335
336 auto* metadata = model->mutable_proto()->mutable_metadata();
337 metadata->set_source(model_url_.spec());
338 metadata->set_last_modified_sec(
339 (base::Time::Now() - base::Time()).InSeconds());
340
341 if (!model_path_.empty()) {
342 DVLOG(2) << "Saving model from '" << model_url_ << "'' to '"
343 << model_path_.value() << "'.";
344 MyScopedHistogramTimer timer(uma_prefix_ + kWriteTimerHistogram);
345 base::ImportantFileWriter::WriteFileAtomically(model_path_,
346 model->SerializeAsString());
347 }
348
349 // Notify the owner that a compatible model is available.
350 TransferModelToClient(std::move(model), true);
351 }
352
353 void RankerModelLoader::Backend::TransferModelToClient(
354 std::unique_ptr<chrome_intelligence::RankerModel> model,
355 bool is_finished) {
356 DCHECK(sequence_checker_.CalledOnValidSequence());
357 origin_task_runner_->PostTask(
358 FROM_HERE, base::Bind(internal_on_model_available_cb_,
359 base::Passed(std::move(model)), is_finished));
360 }
361
362 // =============================================================================
363 // RankerModelLoader
364
365 RankerModelLoader::RankerModelLoader(
366 const ValidateModelCallback& validate_model_cb,
367 const OnModelAvailableCallback& on_model_available_cb,
368 const base::FilePath& model_path,
369 const GURL& model_url,
370 const std::string& uma_prefix)
371 : backend_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
372 base::TaskTraits()
373 .MayBlock()
374 .WithPriority(base::TaskPriority::BACKGROUND)
375 .WithShutdownBehavior(
376 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN))),
377 weak_ptr_factory_(this) {
378 auto internal_on_model_available_cb =
379 base::Bind(&RankerModelLoader::InternalOnModelAvailable,
380 weak_ptr_factory_.GetWeakPtr(), on_model_available_cb);
381 backend_ = base::MakeUnique<Backend>(validate_model_cb,
382 internal_on_model_available_cb,
383 model_path, model_url, uma_prefix);
384 }
385
386 RankerModelLoader::~RankerModelLoader() {
387 DCHECK(sequence_checker_.CalledOnValidSequence());
388 // This is guaranteed to be sequenced after any pending backend operation.
389 backend_task_runner_->DeleteSoon(FROM_HERE, backend_.release());
390 }
391
392 void RankerModelLoader::Start() {
393 DCHECK(sequence_checker_.CalledOnValidSequence());
394 DCHECK_EQ(state_, LoaderState::NOT_STARTED);
395 state_ = LoaderState::RUNNING;
396 backend_task_runner_->PostTask(
397 FROM_HERE, base::Bind(&RankerModelLoader::Backend::LoadFromFile,
398 base::Unretained(backend_.get())));
399 }
400
401 void RankerModelLoader::NotifyOfRankerActivity() {
402 DCHECK(sequence_checker_.CalledOnValidSequence());
403 if (state_ == LoaderState::RUNNING) {
404 backend_task_runner_->PostTask(
405 FROM_HERE, base::Bind(&RankerModelLoader::Backend::AsyncLoadFromURL,
406 base::Unretained(backend_.get())));
407 }
408 }
409
410 void RankerModelLoader::InternalOnModelAvailable(
411 const OnModelAvailableCallback& callback,
412 std::unique_ptr<RankerModel> model,
413 bool finished) {
414 DCHECK(sequence_checker_.CalledOnValidSequence());
415 if (finished)
416 state_ = LoaderState::FINISHED;
417 if (model)
418 callback.Run(std::move(model));
419 }
420
421 } // namespace translate 310 } // namespace translate
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698