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

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: fdoray part deux Created 3 years, 7 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 <utility>
8
7 #include "base/bind.h" 9 #include "base/bind.h"
8 #include "base/bind_helpers.h" 10 #include "base/bind_helpers.h"
9 #include "base/command_line.h" 11 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h" 12 #include "base/files/file_util.h"
12 #include "base/files/important_file_writer.h" 13 #include "base/files/important_file_writer.h"
13 #include "base/macros.h" 14 #include "base/macros.h"
14 #include "base/memory/ptr_util.h" 15 #include "base/memory/ptr_util.h"
15 #include "base/metrics/histogram_macros.h" 16 #include "base/metrics/histogram_macros.h"
16 #include "base/profiler/scoped_tracker.h" 17 #include "base/profiler/scoped_tracker.h"
17 #include "base/sequenced_task_runner.h" 18 #include "base/sequenced_task_runner.h"
18 #include "base/strings/string_util.h" 19 #include "base/strings/string_util.h"
20 #include "base/task_runner_util.h"
19 #include "base/task_scheduler/post_task.h" 21 #include "base/task_scheduler/post_task.h"
20 #include "base/threading/sequenced_task_runner_handle.h" 22 #include "base/threading/sequenced_task_runner_handle.h"
21 #include "components/translate/core/browser/proto/ranker_model.pb.h" 23 #include "components/translate/core/browser/proto/ranker_model.pb.h"
22 #include "components/translate/core/browser/ranker_model.h" 24 #include "components/translate/core/browser/ranker_model.h"
23 #include "components/translate/core/browser/translate_url_fetcher.h" 25 #include "components/translate/core/browser/translate_url_fetcher.h"
24 #include "url/gurl.h"
25 26
26 namespace translate { 27 namespace translate {
27 namespace { 28 namespace {
28 29
29 using chrome_intelligence::RankerModel; 30 using chrome_intelligence::RankerModel;
30 using chrome_intelligence::RankerModelProto; 31 using chrome_intelligence::RankerModelProto;
31 32
32 constexpr int kUrlFetcherId = 2; 33 constexpr int kUrlFetcherId = 2;
33 34
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. 35 // The minimum duration, in minutes, between download attempts.
39 constexpr int kMinRetryDelayMins = 3; 36 constexpr int kMinRetryDelayMins = 3;
40 37
41 // Suffixes for the various histograms produced by the backend. 38 // Suffixes for the various histograms produced by the backend.
42 const char kWriteTimerHistogram[] = ".Timer.WriteModel"; 39 const char kWriteTimerHistogram[] = ".Timer.WriteModel";
43 const char kReadTimerHistogram[] = ".Timer.ReadModel"; 40 const char kReadTimerHistogram[] = ".Timer.ReadModel";
44 const char kDownloadTimerHistogram[] = ".Timer.DownloadModel"; 41 const char kDownloadTimerHistogram[] = ".Timer.DownloadModel";
45 const char kParsetimerHistogram[] = ".Timer.ParseModel"; 42 const char kParsetimerHistogram[] = ".Timer.ParseModel";
46 const char kModelStatusHistogram[] = ".Model.Status"; 43 const char kModelStatusHistogram[] = ".Model.Status";
47 44
45 // Helper function to UMA log a timer histograms.
46 void RecordTimerHistogram(const std::string& name, base::TimeDelta duration) {
47 base::HistogramBase* counter = base::Histogram::FactoryTimeGet(
48 name, base::TimeDelta::FromMilliseconds(10),
49 base::TimeDelta::FromMilliseconds(200000), 100,
50 base::HistogramBase::kUmaTargetedHistogramFlag);
51 DCHECK(counter);
52 counter->AddTime(duration);
53 }
54
48 // A helper class to produce a scoped timer histogram that supports using a 55 // A helper class to produce a scoped timer histogram that supports using a
49 // non-static-const name. 56 // non-static-const name.
50 class MyScopedHistogramTimer { 57 class MyScopedHistogramTimer {
51 public: 58 public:
52 MyScopedHistogramTimer(const base::StringPiece& name) 59 MyScopedHistogramTimer(const base::StringPiece& name)
53 : name_(name.begin(), name.end()), start_(base::TimeTicks::Now()) {} 60 : name_(name.begin(), name.end()), start_(base::TimeTicks::Now()) {}
54 61
55 ~MyScopedHistogramTimer() { 62 ~MyScopedHistogramTimer() {
56 base::TimeDelta duration = base::TimeTicks::Now() - start_; 63 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 } 64 }
64 65
65 private: 66 private:
66 const std::string name_; 67 const std::string name_;
67 const base::TimeTicks start_; 68 const base::TimeTicks start_;
68 69
69 DISALLOW_COPY_AND_ASSIGN(MyScopedHistogramTimer); 70 DISALLOW_COPY_AND_ASSIGN(MyScopedHistogramTimer);
70 }; 71 };
71 72
73 std::string LoadFromFile(const base::FilePath& model_path) {
74 DCHECK(!model_path.empty());
75 DVLOG(2) << "Reading data from: " << model_path.value();
76 std::string data;
77 if (!base::ReadFileToString(model_path, &data) || data.empty()) {
78 DVLOG(2) << "Failed to read data from: " << model_path.value();
79 data.clear();
80 }
81 return data;
82 }
83
84 void SaveToFile(const GURL& model_url,
85 const base::FilePath& model_path,
86 const std::string& model_data,
87 const std::string& uma_prefix) {
88 DVLOG(2) << "Saving model from '" << model_url << "'' to '"
89 << model_path.value() << "'.";
90 MyScopedHistogramTimer timer(uma_prefix + kWriteTimerHistogram);
91 base::ImportantFileWriter::WriteFileAtomically(model_path, model_data);
92 }
93
72 } // namespace 94 } // namespace
73 95
74 // ============================================================================= 96 RankerModelLoader::RankerModelLoader(
75 // RankerModelLoader::Backend 97 ValidateModelCallback validate_model_cb,
76 98 OnModelAvailableCallback on_model_available_cb,
77 class RankerModelLoader::Backend { 99 base::FilePath model_path,
78 public: 100 GURL model_url,
79 // An internal version of RankerModelLoader::OnModelAvailableCallback that 101 std::string uma_prefix)
80 // bundles calling the real callback with a notification of whether or not 102 : background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
81 // tha backend is finished. 103 base::TaskTraits()
82 using InternalOnModelAvailableCallback = 104 .MayBlock()
83 base::Callback<void(std::unique_ptr<RankerModel>, bool)>; 105 .WithPriority(base::TaskPriority::BACKGROUND)
84 106 .WithShutdownBehavior(
85 Backend(const ValidateModelCallback& validate_model_cb, 107 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN))),
86 const InternalOnModelAvailableCallback& on_model_available_cb, 108 validate_model_cb_(std::move(validate_model_cb)),
87 const base::FilePath& model_path, 109 on_model_available_cb_(std::move(on_model_available_cb)),
88 const GURL& model_url, 110 model_path_(std::move(model_path)),
89 const std::string& uma_prefix); 111 model_url_(std::move(model_url)),
90 ~Backend(); 112 uma_prefix_(std::move(uma_prefix)),
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,
167 const InternalOnModelAvailableCallback& internal_on_model_available_cb,
168 const base::FilePath& model_path,
169 const GURL& model_url,
170 const std::string& uma_prefix)
171 : origin_task_runner_(base::SequencedTaskRunnerHandle::Get()),
172 validate_model_cb_(validate_model_cb),
173 internal_on_model_available_cb_(internal_on_model_available_cb),
174 model_path_(model_path),
175 model_url_(model_url),
176 uma_prefix_(uma_prefix),
177 url_fetcher_(base::MakeUnique<TranslateURLFetcher>(kUrlFetcherId)), 113 url_fetcher_(base::MakeUnique<TranslateURLFetcher>(kUrlFetcherId)),
178 download_attempts_(0) { 114 weak_ptr_factory_(this) {}
179 sequence_checker_.DetachFromSequence(); 115
180 } 116 RankerModelLoader::~RankerModelLoader() {
181 117 DCHECK(sequence_checker_.CalledOnValidSequence());
182 RankerModelLoader::Backend::~Backend() {} 118 }
183 119
184 RankerModelStatus RankerModelLoader::Backend::ReportModelStatus( 120 void RankerModelLoader::NotifyOfRankerActivity() {
121 DCHECK(sequence_checker_.CalledOnValidSequence());
122 switch (state_) {
123 case LoaderState::NOT_STARTED:
124 if (!model_path_.empty()) {
125 StartLoadFromFile();
126 break;
127 }
128 // There was no configured model path. Switch the state to IDLE and
129 // fall through to consider the URL.
130 state_ = LoaderState::IDLE;
131 case LoaderState::IDLE:
132 if (model_url_.is_valid()) {
133 StartLoadFromURL();
134 break;
135 }
136 // There was no configured model URL. Switch the state to FINISHED and
137 // fall through.
138 state_ = LoaderState::FINISHED;
139 case LoaderState::FINISHED:
140 case LoaderState::LOADING_FROM_FILE:
141 case LoaderState::LOADING_FROM_URL:
142 // Nothing to do.
143 break;
144 }
145 }
146
147 void RankerModelLoader::StartLoadFromFile() {
148 DCHECK(sequence_checker_.CalledOnValidSequence());
149 DCHECK_EQ(state_, LoaderState::NOT_STARTED);
150 DCHECK(!model_path_.empty());
151 state_ = LoaderState::LOADING_FROM_FILE;
152 load_start_time_ = base::TimeTicks::Now();
153 base::PostTaskAndReplyWithResult(background_task_runner_.get(), FROM_HERE,
154 base::Bind(&LoadFromFile, model_path_),
155 base::Bind(&RankerModelLoader::OnFileLoaded,
156 weak_ptr_factory_.GetWeakPtr()));
157 }
158
159 void RankerModelLoader::OnFileLoaded(const std::string& data) {
160 DCHECK(sequence_checker_.CalledOnValidSequence());
161 DCHECK_EQ(state_, LoaderState::LOADING_FROM_FILE);
162
163 // Record the duration of the download.
164 RecordTimerHistogram(uma_prefix_ + kReadTimerHistogram,
165 base::TimeTicks::Now() - load_start_time_);
166
167 // Empty data means |model_path| wasn't successfully read. Otherwise,
168 // parse and validate the model.
169 std::unique_ptr<RankerModel> model;
170 if (data.empty()) {
171 ReportModelStatus(RankerModelStatus::LOAD_FROM_CACHE_FAILED);
172 } else {
173 model = CreateAndValidateModel(data);
174 }
175
176 // If |model| is nullptr, then data is empty or the parse failed. Transition
177 // to IDLE, from which URL download can be attempted.
178 if (!model) {
179 state_ = LoaderState::IDLE;
180 } else {
181 // The model is valid. The client is willing/able to use it. Keep track
182 // of where it originated and whether or not is has expired.
183 std::string url_spec = model->GetSourceURL();
184 bool is_expired = model->IsExpired();
185 bool is_finished = url_spec == model_url_.spec() && !is_expired;
186
187 DVLOG(2) << (is_expired ? "Expired m" : "M") << "odel in '"
188 << model_path_.value() << "' was originally downloaded from '"
189 << url_spec << "'.";
190
191 // If the cached model came from currently configured |model_url_| and has
192 // not expired, transition to FINISHED, as there is no need for a model
193 // download; otherwise, transition to IDLE.
194 state_ = is_finished ? LoaderState::FINISHED : LoaderState::IDLE;
195
196 // Transfer the model to the client.
197 on_model_available_cb_.Run(std::move(model));
198 }
199
200 // Notify the state machine. This will immediately kick off a download if
201 // one is required, instead of waiting for the next organic detection of
202 // ranker activity.
203 NotifyOfRankerActivity();
204 }
205
206 void RankerModelLoader::StartLoadFromURL() {
207 DCHECK(sequence_checker_.CalledOnValidSequence());
208 DCHECK_EQ(state_, LoaderState::IDLE);
209 DCHECK(model_url_.is_valid());
210
211 // Do nothing if the download attempts should be throttled.
212 if (base::TimeTicks::Now() < next_earliest_download_time_) {
213 DVLOG(2) << "Last download attempt was too recent.";
214 ReportModelStatus(RankerModelStatus::DOWNLOAD_THROTTLED);
215 return;
216 }
217
218 // Kick off the next download attempt and reset the time of the next earliest
219 // allowable download attempt.
220 DVLOG(2) << "Downloading model from: " << model_url_;
221 state_ = LoaderState::LOADING_FROM_URL;
222 load_start_time_ = base::TimeTicks::Now();
223 next_earliest_download_time_ =
224 load_start_time_ + base::TimeDelta::FromMinutes(kMinRetryDelayMins);
225 bool request_started = url_fetcher_->Request(
226 model_url_, base::Bind(&RankerModelLoader::OnURLFetched,
227 weak_ptr_factory_.GetWeakPtr()));
228
229 // |url_fetcher_| maintains a request retry counter. If all allowed attempts
230 // have already been exhausted, then the loader is finished and has abandoned
231 // loading the model.
232 if (!request_started) {
233 DVLOG(2) << "Model download abandoned.";
234 ReportModelStatus(RankerModelStatus::MODEL_LOADING_ABANDONED);
235 state_ = LoaderState::FINISHED;
236 }
237 }
238
239 void RankerModelLoader::OnURLFetched(int /* id */,
240 bool success,
241 const std::string& data) {
242 DCHECK(sequence_checker_.CalledOnValidSequence());
243 DCHECK_EQ(state_, LoaderState::LOADING_FROM_URL);
244
245 // Record the duration of the download.
246 RecordTimerHistogram(uma_prefix_ + kDownloadTimerHistogram,
247 base::TimeTicks::Now() - load_start_time_);
248
249 // On request failure, transition back to IDLE. The loader will retry, or
250 // enforce the max download attempts, later.
251 if (!success || data.empty()) {
252 DVLOG(2) << "Download from '" << model_url_ << "'' failed.";
253 ReportModelStatus(RankerModelStatus::DOWNLOAD_FAILED);
254 state_ = LoaderState::IDLE;
255 return;
256 }
257
258 // Attempt to loads the model. If this fails, transition back to IDLE. The
259 // loader will retry, or enfore the max download attempts, later.
260 auto model = CreateAndValidateModel(data);
261 if (!model) {
262 DVLOG(2) << "Model from '" << model_url_ << "'' not valid.";
263 state_ = LoaderState::IDLE;
264 return;
265 }
266
267 // The model is valid. Update the metadata to track the source URL and
268 // download timestamp.
269 auto* metadata = model->mutable_proto()->mutable_metadata();
270 metadata->set_source(model_url_.spec());
271 metadata->set_last_modified_sec(
272 (base::Time::Now() - base::Time()).InSeconds());
273
274 // Cache the model to model_path_, in the background.
275 if (!model_path_.empty()) {
276 background_task_runner_->PostTask(
277 FROM_HERE, base::BindOnce(&SaveToFile, model_url_, model_path_,
278 model->SerializeAsString(), uma_prefix_));
279 }
280
281 // The loader is finished. Transfer the model to the client.
282 state_ = LoaderState::FINISHED;
283 on_model_available_cb_.Run(std::move(model));
284 }
285
286 std::unique_ptr<chrome_intelligence::RankerModel>
287 RankerModelLoader::CreateAndValidateModel(const std::string& data) {
288 DCHECK(sequence_checker_.CalledOnValidSequence());
289 MyScopedHistogramTimer timer(uma_prefix_ + kParsetimerHistogram);
290 auto model = RankerModel::FromString(data);
291 if (ReportModelStatus(model ? validate_model_cb_.Run(*model)
292 : RankerModelStatus::PARSE_FAILED) !=
293 RankerModelStatus::OK) {
294 return nullptr;
295 }
296 return model;
297 }
298
299 RankerModelStatus RankerModelLoader::ReportModelStatus(
185 RankerModelStatus model_status) { 300 RankerModelStatus model_status) {
301 DCHECK(sequence_checker_.CalledOnValidSequence());
186 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( 302 base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
187 uma_prefix_ + kModelStatusHistogram, 1, 303 uma_prefix_ + kModelStatusHistogram, 1,
188 static_cast<int>(RankerModelStatus::MAX), 304 static_cast<int>(RankerModelStatus::MAX),
189 static_cast<int>(RankerModelStatus::MAX) + 1, 305 static_cast<int>(RankerModelStatus::MAX) + 1,
190 base::HistogramBase::kUmaTargetedHistogramFlag); 306 base::HistogramBase::kUmaTargetedHistogramFlag);
191 if (histogram) 307 if (histogram)
192 histogram->Add(static_cast<int>(model_status)); 308 histogram->Add(static_cast<int>(model_status));
193 return model_status; 309 return model_status;
194 } 310 }
195 311
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 312 } // namespace translate
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698