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

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

Powered by Google App Engine
This is Rietveld 408576698