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

Side by Side Diff: net/sdch/sdch_owner.cc

Issue 901303002: Make SDCH dictionaries persistent across browser restart. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Beef up SdchDictionaryFetcher tests Created 5 years, 10 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 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 "net/sdch/sdch_owner.h" 5 #include "net/sdch/sdch_owner.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/logging.h"
8 #include "base/metrics/histogram_macros.h" 9 #include "base/metrics/histogram_macros.h"
10 #include "base/prefs/persistent_pref_store.h"
11 #include "base/prefs/value_map_pref_store.h"
9 #include "base/time/default_clock.h" 12 #include "base/time/default_clock.h"
13 #include "base/values.h"
10 #include "net/base/sdch_manager.h" 14 #include "net/base/sdch_manager.h"
11 #include "net/base/sdch_net_log_params.h" 15 #include "net/base/sdch_net_log_params.h"
12 16
17 namespace net {
18
13 namespace { 19 namespace {
14 20
15 enum DictionaryFate { 21 enum DictionaryFate {
16 // A Get-Dictionary header wasn't acted on. 22 // A Get-Dictionary header wasn't acted on.
17 DICTIONARY_FATE_GET_IGNORED = 1, 23 DICTIONARY_FATE_GET_IGNORED = 1,
18 24
19 // A fetch was attempted, but failed. 25 // A fetch was attempted, but failed.
20 // TODO(rdsmith): Actually record this case. 26 // TODO(rdsmith): Actually record this case.
21 DICTIONARY_FATE_FETCH_FAILED = 2, 27 DICTIONARY_FATE_FETCH_FAILED = 2,
22 28
23 // A successful fetch was dropped on the floor, no space. 29 // A successful fetch was dropped on the floor, no space.
24 DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE = 3, 30 DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE = 3,
25 31
26 // A successful fetch was refused by the SdchManager. 32 // A successful fetch was refused by the SdchManager.
27 DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4, 33 DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4,
28 34
29 // A dictionary was successfully added. 35 // A dictionary was successfully added based on
30 DICTIONARY_FATE_ADDED = 5, 36 // a Get-Dictionary header in a response.
37 DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED = 5,
31 38
32 // A dictionary was evicted by an incoming dict. 39 // A dictionary was evicted by an incoming dict.
33 DICTIONARY_FATE_EVICT_FOR_DICT = 6, 40 DICTIONARY_FATE_EVICT_FOR_DICT = 6,
34 41
35 // A dictionary was evicted by memory pressure. 42 // A dictionary was evicted by memory pressure.
36 DICTIONARY_FATE_EVICT_FOR_MEMORY = 7, 43 DICTIONARY_FATE_EVICT_FOR_MEMORY = 7,
37 44
38 // A dictionary was evicted on destruction. 45 // A dictionary was evicted on destruction.
39 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8, 46 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8,
40 47
41 DICTIONARY_FATE_MAX = 9 48 // A dictionary was successfully added based on
49 // persistence from a previous browser revision.
50 DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED = 9,
51
52 // A dictionary was unloaded on destruction, but is still present on disk.
53 DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION = 10,
54
55 DICTIONARY_FATE_MAX = 11
42 }; 56 };
43 57
58 enum PersistenceFailureReason {
59 // File didn't exist; is being created.
60 PERSISTENCE_FAILURE_REASON_NO_FILE = 1,
61
62 // Error reading in information, but should be able to write.
63 PERSISTENCE_FAILURE_REASON_READ_FAILED = 2,
64
65 // Error leading to abort on attempted persistence.
66 PERSISTENCE_FAILURE_REASON_WRITE_FAILED = 3,
67
68 PERSISTENCE_FAILURE_REASON_MAX = 4
69 };
70
71 // Dictionaries that haven't been touched in 24 hours may be evicted
72 // to make room for new dictionaries.
73 const int kFreshnessLifetimeHours = 24;
74
75 // Dictionaries that have never been used only stay fresh for one hour.
76 const int kNeverUsedFreshnessLifetimeHours = 1;
77
44 void RecordDictionaryFate(enum DictionaryFate fate) { 78 void RecordDictionaryFate(enum DictionaryFate fate) {
45 UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX); 79 UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX);
46 } 80 }
47 81
82 void RecordPersistenceFailure(PersistenceFailureReason failure_reason) {
83 UMA_HISTOGRAM_ENUMERATION("Sdch3.PersistenceFailureReason", failure_reason,
84 PERSISTENCE_FAILURE_REASON_MAX);
85 }
86
48 void RecordDictionaryEviction(int use_count, DictionaryFate fate) { 87 void RecordDictionaryEviction(int use_count, DictionaryFate fate) {
49 DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT || 88 DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT ||
50 fate == DICTIONARY_FATE_EVICT_FOR_MEMORY || 89 fate == DICTIONARY_FATE_EVICT_FOR_MEMORY ||
51 fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); 90 fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION);
52 91
53 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count); 92 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count);
54 RecordDictionaryFate(fate); 93 RecordDictionaryFate(fate);
55 } 94 }
56 95
96 void RecordDictionaryUnload(int use_count) {
97 UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", 0);
Randy Smith (Not in Mondays) 2015/02/23 21:23:49 nit, suggestion: I think of it as a not-great idea
Elly Fong-Jones 2015/03/03 21:37:45 Done.
98 RecordDictionaryFate(DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION);
99 }
100
101 // Schema specifications and access routines.
102
103 // The persistent prefs store is conceptually shared with any other network
104 // stack systems that want to persist data over browser restarts, and so
105 // use of it must be namespace restricted.
106 // Schema:
107 // pref_store_->GetValue(kPreferenceName) -> Dictionary {
108 // 'version' -> 1 [int]
109 // 'dictionaries' -> Dictionary {
110 // server_hash -> {
111 // 'url' -> URL [string]
112 // 'last_used' -> seconds since unix epoch [double]
113 // 'use_count' -> use count [int]
114 // 'size' -> size [int]
115 // }
116 // }
117 const char kPreferenceName[] = "SDCH";
118 const char kVersionKey[] = "version";
119 const char kDictionariesKey[] = "dictionaries";
120 const char kDictionaryUrlKey[] = "url";
121 const char kDictionaryLastUsedKey[] = "last_used";
122 const char kDictionaryUseCountKey[] = "use_count";
123 const char kDictionarySizeKey[] = "size";
124
125 const int kVersion = 1;
126
127 // This function returns store[kPreferenceName/kDictionariesKey]. The caller
128 // is responsible for making sure any needed calls to
129 // |store->ReportValueChanged()| occur.
130 base::DictionaryValue* GetPersistentStoreDictionaryMap(
131 WriteablePrefStore* store) {
132 base::Value* result = nullptr;
133 bool success = store->GetMutableValue(kPreferenceName, &result);
134 DCHECK(success);
135
136 base::DictionaryValue* preference_dictionary = nullptr;
137 success = result->GetAsDictionary(&preference_dictionary);
138 DCHECK(success);
139 DCHECK(preference_dictionary);
140
141 base::DictionaryValue* dictionary_list_dictionary = nullptr;
142 success = preference_dictionary->GetDictionary(kDictionariesKey,
143 &dictionary_list_dictionary);
144 DCHECK(success);
145 DCHECK(dictionary_list_dictionary);
146
147 return dictionary_list_dictionary;
148 }
149
150 // This function initializes a pref store with an empty version of the
151 // above schema, removing anything previously in the store under
152 // kPreferenceName.
153 void InitializePrefStore(WriteablePrefStore* store) {
154 base::DictionaryValue* empty_store(new base::DictionaryValue);
155 empty_store->SetInteger(kVersionKey, kVersion);
156 empty_store->Set(kDictionariesKey,
157 make_scoped_ptr(new base::DictionaryValue));
158 store->SetValue(kPreferenceName, empty_store);
159 }
160
161 // A class to allow iteration over all dictionaries in the pref store, and
162 // easy lookup of the information associated with those dictionaries.
163 // Note that this is an "Iterator" in the same sense (and for the same
164 // reasons) that base::Dictionary::Iterator is an iterator--it allows
165 // iterating over all the dictionaries in the preference store, but it
166 // does not allow use as an STL iterator because the container it
167 // is iterating over does not export begin()/end() methods. This iterator can
168 // only be safely used on sanitized pref stores that are known to conform to the
169 // pref store schema.
170 class DictionaryPreferenceIterator {
171 public:
172 explicit DictionaryPreferenceIterator(WriteablePrefStore* pref_store);
173
174 bool IsAtEnd() const;
175 void Advance();
176
177 const std::string& server_hash() const { return server_hash_; }
178 const GURL url() const { return url_; }
179 base::Time last_used() const { return last_used_; }
180 int use_count() const { return use_count_; }
181 int size() const { return size_; }
182
183 private:
184 void LoadDictionaryOrDie();
185
186 std::string server_hash_;
187 GURL url_;
188 base::Time last_used_;
189 int use_count_;
190 int size_;
191
192 base::DictionaryValue::Iterator dictionary_iterator_;
193 };
194
195 DictionaryPreferenceIterator::DictionaryPreferenceIterator(
196 WriteablePrefStore* pref_store)
197 : dictionary_iterator_(*GetPersistentStoreDictionaryMap(pref_store)) {
198 if (!IsAtEnd())
199 LoadDictionaryOrDie();
200 }
201
202 bool DictionaryPreferenceIterator::IsAtEnd() const {
203 return dictionary_iterator_.IsAtEnd();
204 }
205
206 void DictionaryPreferenceIterator::Advance() {
207 dictionary_iterator_.Advance();
208 if (!IsAtEnd())
209 LoadDictionaryOrDie();
210 }
211
212 void DictionaryPreferenceIterator::LoadDictionaryOrDie() {
213 double last_used_seconds_from_epoch;
214 const base::DictionaryValue* dict = nullptr;
215 bool success =
216 dictionary_iterator_.value().GetAsDictionary(&dict);
217 DCHECK(success);
218
219 server_hash_ = dictionary_iterator_.key();
220
221 std::string url_spec;
222 success = dict->GetString(kDictionaryUrlKey, &url_spec);
223 DCHECK(success);
224 url_ = GURL(url_spec);
225
226 success = dict->GetDouble(kDictionaryLastUsedKey,
227 &last_used_seconds_from_epoch);
228 DCHECK(success);
229 last_used_ = base::Time::FromDoubleT(last_used_seconds_from_epoch);
230
231 success = dict->GetInteger(kDictionaryUseCountKey, &use_count_);
232 DCHECK(success);
233
234 success = dict->GetInteger(kDictionarySizeKey, &size_);
235 DCHECK(success);
236 }
237
238 // Triggers a ReportValueChanged() on the specified WriteablePrefStore
239 // when the object goes out of scope.
240 class ScopedPrefNotifier {
241 public:
242 // Caller must guarantee lifetime of |*pref_store| exceeds the
243 // lifetime of this object.
244 ScopedPrefNotifier(WriteablePrefStore* pref_store)
245 : pref_store_(pref_store) {}
246 ~ScopedPrefNotifier() { pref_store_->ReportValueChanged(kPreferenceName); }
247
248 private:
249 WriteablePrefStore* pref_store_;
250
251 DISALLOW_COPY_AND_ASSIGN(ScopedPrefNotifier);
252 };
253
57 } // namespace 254 } // namespace
58 255
59 namespace net {
60
61 // Adjust SDCH limits downwards for mobile. 256 // Adjust SDCH limits downwards for mobile.
62 #if defined(OS_ANDROID) || defined(OS_IOS) 257 #if defined(OS_ANDROID) || defined(OS_IOS)
63 // static 258 // static
64 const size_t SdchOwner::kMaxTotalDictionarySize = 1000 * 1000; 259 const size_t SdchOwner::kMaxTotalDictionarySize = 1000 * 1000;
65 #else 260 #else
66 // static 261 // static
67 const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000; 262 const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000;
68 #endif 263 #endif
69 264
70 // Somewhat arbitrary, but we assume a dictionary smaller than 265 // Somewhat arbitrary, but we assume a dictionary smaller than
71 // 50K isn't going to do anyone any good. Note that this still doesn't 266 // 50K isn't going to do anyone any good. Note that this still doesn't
72 // prevent download and addition unless there is less than this 267 // prevent download and addition unless there is less than this
73 // amount of space available in storage. 268 // amount of space available in storage.
74 const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000; 269 const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000;
75 270
76 SdchOwner::SdchOwner(net::SdchManager* sdch_manager, 271 SdchOwner::SdchOwner(SdchManager* sdch_manager, URLRequestContext* context)
77 net::URLRequestContext* context)
78 : manager_(sdch_manager), 272 : manager_(sdch_manager),
79 fetcher_(context, 273 fetcher_(new SdchDictionaryFetcher(context)),
80 base::Bind(&SdchOwner::OnDictionaryFetched,
81 // Because |fetcher_| is owned by SdchOwner, the
82 // SdchOwner object will be available for the lifetime
83 // of |fetcher_|.
84 base::Unretained(this))),
85 total_dictionary_bytes_(0), 274 total_dictionary_bytes_(0),
86 clock_(new base::DefaultClock), 275 clock_(new base::DefaultClock),
87 max_total_dictionary_size_(kMaxTotalDictionarySize), 276 max_total_dictionary_size_(kMaxTotalDictionarySize),
88 min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), 277 min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch),
278 in_memory_pref_store_(new ValueMapPrefStore()),
279 external_pref_store_(nullptr),
280 pref_store_(in_memory_pref_store_.get()),
89 memory_pressure_listener_( 281 memory_pressure_listener_(
90 base::Bind(&SdchOwner::OnMemoryPressure, 282 base::Bind(&SdchOwner::OnMemoryPressure,
91 // Because |memory_pressure_listener_| is owned by 283 // Because |memory_pressure_listener_| is owned by
92 // SdchOwner, the SdchOwner object will be available 284 // SdchOwner, the SdchOwner object will be available
93 // for the lifetime of |memory_pressure_listener_|. 285 // for the lifetime of |memory_pressure_listener_|.
94 base::Unretained(this))) { 286 base::Unretained(this))) {
95 manager_->AddObserver(this); 287 manager_->AddObserver(this);
288 InitializePrefStore(pref_store_);
96 } 289 }
97 290
98 SdchOwner::~SdchOwner() { 291 SdchOwner::~SdchOwner() {
99 for (auto it = local_dictionary_info_.begin(); 292 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
100 it != local_dictionary_info_.end(); ++it) { 293 it.Advance()) {
101 RecordDictionaryEviction(it->second.use_count, 294 int new_uses = it.use_count() - use_counts_at_load_[it.server_hash()];
102 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); 295 if (IsPersistingDictionaries()) {
296 RecordDictionaryUnload(new_uses);
297 } else {
298 RecordDictionaryEviction(new_uses,
299 DICTIONARY_FATE_EVICT_FOR_DESTRUCTION);
300 }
103 } 301 }
104 manager_->RemoveObserver(this); 302 manager_->RemoveObserver(this);
303
304 // This object only observes the external store during loading,
305 // i.e. before it's made the default preferences store.
306 if (external_pref_store_)
307 external_pref_store_->RemoveObserver(this);
308 }
309
310 void SdchOwner::EnablePersistentStorage(PersistentPrefStore* pref_store) {
311 DCHECK(!external_pref_store_);
312 external_pref_store_ = pref_store;
313 external_pref_store_->AddObserver(this);
314
315 if (external_pref_store_->IsInitializationComplete())
316 OnInitializationCompleted(true);
105 } 317 }
106 318
107 void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) { 319 void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) {
108 max_total_dictionary_size_ = max_total_dictionary_size; 320 max_total_dictionary_size_ = max_total_dictionary_size;
109 } 321 }
110 322
111 void SdchOwner::SetMinSpaceForDictionaryFetch( 323 void SdchOwner::SetMinSpaceForDictionaryFetch(
112 size_t min_space_for_dictionary_fetch) { 324 size_t min_space_for_dictionary_fetch) {
113 min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch; 325 min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch;
114 } 326 }
115 327
116 void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text, 328 void SdchOwner::OnDictionaryFetched(base::Time last_used,
329 int use_count,
330 const std::string& dictionary_text,
117 const GURL& dictionary_url, 331 const GURL& dictionary_url,
118 const net::BoundNetLog& net_log) { 332 const net::BoundNetLog& net_log) {
119 struct DictionaryItem { 333 struct DictionaryItem {
120 base::Time last_used; 334 base::Time last_used;
121 std::string server_hash; 335 std::string server_hash;
122 int use_count; 336 int use_count;
123 size_t dictionary_size; 337 size_t dictionary_size;
124 338
125 DictionaryItem() : use_count(0), dictionary_size(0) {} 339 DictionaryItem() : use_count(0), dictionary_size(0) {}
126 DictionaryItem(const base::Time& last_used, 340 DictionaryItem(const base::Time& last_used,
127 const std::string& server_hash, 341 const std::string& server_hash,
128 int use_count, 342 int use_count,
129 size_t dictionary_size) 343 size_t dictionary_size)
130 : last_used(last_used), 344 : last_used(last_used),
131 server_hash(server_hash), 345 server_hash(server_hash),
132 use_count(use_count), 346 use_count(use_count),
133 dictionary_size(dictionary_size) {} 347 dictionary_size(dictionary_size) {}
134 DictionaryItem(const DictionaryItem& rhs) = default; 348 DictionaryItem(const DictionaryItem& rhs) = default;
135 DictionaryItem& operator=(const DictionaryItem& rhs) = default; 349 DictionaryItem& operator=(const DictionaryItem& rhs) = default;
136 bool operator<(const DictionaryItem& rhs) const { 350 bool operator<(const DictionaryItem& rhs) const {
137 return last_used < rhs.last_used; 351 return last_used < rhs.last_used;
138 } 352 }
139 }; 353 };
140 354
355 // Figure out if there is space for the incoming dictionary; evict
356 // stale dictionaries if needed to make space.
357
141 std::vector<DictionaryItem> stale_dictionary_list; 358 std::vector<DictionaryItem> stale_dictionary_list;
142 size_t recoverable_bytes = 0; 359 size_t recoverable_bytes = 0;
143 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); 360 base::Time now(clock_->Now());
144 for (auto used_it = local_dictionary_info_.begin(); 361 // Dictionaries whose last used time is before |stale_boundary| are candidates
145 used_it != local_dictionary_info_.end(); ++used_it) { 362 // for eviction if necessary.
146 if (used_it->second.last_used < stale_boundary) { 363 base::Time stale_boundary(
147 stale_dictionary_list.push_back( 364 now - base::TimeDelta::FromHours(kFreshnessLifetimeHours));
148 DictionaryItem(used_it->second.last_used, used_it->first, 365 // Dictionaries that have never been used and are from before
149 used_it->second.use_count, used_it->second.size)); 366 // |never_used_stale_boundary| are candidates for eviction if necessary.
150 recoverable_bytes += used_it->second.size; 367 base::Time never_used_stale_boundary(
368 now - base::TimeDelta::FromHours(kNeverUsedFreshnessLifetimeHours));
369 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
370 it.Advance()) {
371 if (it.last_used() < stale_boundary ||
372 (it.use_count() == 0 && it.last_used() < never_used_stale_boundary)) {
373 stale_dictionary_list.push_back(DictionaryItem(
374 it.last_used(), it.server_hash(), it.use_count(), it.size()));
375 recoverable_bytes += it.size();
151 } 376 }
152 } 377 }
153 378
154 if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes > 379 if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes >
155 max_total_dictionary_size_) { 380 max_total_dictionary_size_) {
156 RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE); 381 RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE);
157 net::SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM); 382 net::SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM);
158 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, 383 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR,
159 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, 384 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback,
160 SDCH_DICTIONARY_NO_ROOM, dictionary_url, true)); 385 SDCH_DICTIONARY_NO_ROOM, dictionary_url, true));
161 return; 386 return;
162 } 387 }
163 388
164 // Evict from oldest to youngest until we have space. 389 // Add the new dictionary. This is done before removing the stale
165 std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end()); 390 // dictionaries so that no state change will occur if dictionary addition
166 size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_; 391 // fails.
167 auto stale_it = stale_dictionary_list.begin();
168 while (avail_bytes < dictionary_text.size() &&
169 stale_it != stale_dictionary_list.end()) {
170 manager_->RemoveSdchDictionary(stale_it->server_hash);
171 RecordDictionaryEviction(stale_it->use_count,
172 DICTIONARY_FATE_EVICT_FOR_DICT);
173 local_dictionary_info_.erase(stale_it->server_hash);
174 avail_bytes += stale_it->dictionary_size;
175 ++stale_it;
176 }
177 DCHECK(avail_bytes >= dictionary_text.size());
178
179 std::string server_hash; 392 std::string server_hash;
180 net::SdchProblemCode rv = manager_->AddSdchDictionary( 393 net::SdchProblemCode rv = manager_->AddSdchDictionary(
181 dictionary_text, dictionary_url, &server_hash); 394 dictionary_text, dictionary_url, &server_hash);
182 if (rv != net::SDCH_OK) { 395 if (rv != net::SDCH_OK) {
183 RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED); 396 RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED);
184 net::SdchManager::SdchErrorRecovery(rv); 397 net::SdchManager::SdchErrorRecovery(rv);
185 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, 398 net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR,
186 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, 399 base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback,
187 rv, dictionary_url, true)); 400 rv, dictionary_url, true));
188 return; 401 return;
189 } 402 }
190 403
191 RecordDictionaryFate(DICTIONARY_FATE_ADDED); 404 base::DictionaryValue* pref_dictionary_map =
405 GetPersistentStoreDictionaryMap(pref_store_);
406 ScopedPrefNotifier scoped_pref_notifier(pref_store_);
192 407
193 DCHECK(local_dictionary_info_.end() == 408 // Remove the old dictionaries.
194 local_dictionary_info_.find(server_hash)); 409 std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end());
410 size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_;
411 auto stale_it = stale_dictionary_list.begin();
412 while (avail_bytes < dictionary_text.size() &&
413 stale_it != stale_dictionary_list.end()) {
414 manager_->RemoveSdchDictionary(stale_it->server_hash);
415
416 DCHECK(pref_dictionary_map->HasKey(stale_it->server_hash));
417 bool success = pref_dictionary_map->RemoveWithoutPathExpansion(
418 stale_it->server_hash, nullptr);
419 DCHECK(success);
420
421 avail_bytes += stale_it->dictionary_size;
422
423 int new_uses = stale_it->use_count -
424 use_counts_at_load_[stale_it->server_hash];
425 RecordDictionaryEviction(new_uses,
426 DICTIONARY_FATE_EVICT_FOR_DICT);
427
428 ++stale_it;
429 }
430 DCHECK_GE(avail_bytes, dictionary_text.size());
431
432 RecordDictionaryFate(
433 // Distinguish between loads triggered by network responses and
434 // loads triggered by persistence.
435 last_used.is_null() ? DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED
436 : DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED);
437
438 // If a dictionary has never been used, its dictionary addition time
439 // is recorded as its last used time. Never used dictionaries are treated
440 // specially in the freshness logic.
441 if (last_used.is_null())
442 last_used = clock_->Now();
443
195 total_dictionary_bytes_ += dictionary_text.size(); 444 total_dictionary_bytes_ += dictionary_text.size();
196 local_dictionary_info_[server_hash] = DictionaryInfo( 445
197 // Set the time last used to something to avoid thrashing, but not recent, 446 // Record the addition in the pref store.
198 // to avoid taking too much time/space with useless dictionaries/one-off 447 scoped_ptr<base::DictionaryValue> dictionary_description(
199 // visits to web sites. 448 new base::DictionaryValue());
200 clock_->Now() - base::TimeDelta::FromHours(23), dictionary_text.size()); 449 dictionary_description->SetString(kDictionaryUrlKey, dictionary_url.spec());
450 dictionary_description->SetDouble(kDictionaryLastUsedKey,
451 last_used.ToDoubleT());
452 dictionary_description->SetInteger(kDictionaryUseCountKey, use_count);
453 dictionary_description->SetInteger(kDictionarySizeKey,
454 dictionary_text.size());
455 pref_dictionary_map->Set(server_hash, dictionary_description.Pass());
201 } 456 }
202 457
203 void SdchOwner::OnDictionaryUsed(SdchManager* manager, 458 void SdchOwner::OnDictionaryUsed(SdchManager* manager,
204 const std::string& server_hash) { 459 const std::string& server_hash) {
205 auto it = local_dictionary_info_.find(server_hash); 460 base::Time now(clock_->Now());
206 DCHECK(local_dictionary_info_.end() != it); 461 base::DictionaryValue* pref_dictionary_map =
462 GetPersistentStoreDictionaryMap(pref_store_);
463 ScopedPrefNotifier scoped_pref_notifier(pref_store_);
207 464
208 it->second.last_used = clock_->Now(); 465 base::Value* value = nullptr;
209 it->second.use_count++; 466 bool success = pref_dictionary_map->Get(server_hash, &value);
467 DCHECK(success);
468 base::DictionaryValue* specific_dictionary_map = nullptr;
469 success = value->GetAsDictionary(&specific_dictionary_map);
470 DCHECK(success);
471
472 double last_used_seconds_since_epoch = 0.0;
473 success = specific_dictionary_map->GetDouble(kDictionaryLastUsedKey,
474 &last_used_seconds_since_epoch);
475 DCHECK(success);
476 int use_count = 0;
477 success =
478 specific_dictionary_map->GetInteger(kDictionaryUseCountKey, &use_count);
479 DCHECK(success);
480
481 if (use_counts_at_load_.count(server_hash) == 0) {
482 use_counts_at_load_[server_hash] = use_count;
483 }
484
485 base::TimeDelta time_since_last_used(now -
486 base::Time::FromDoubleT(last_used_seconds_since_epoch));
487
488 // TODO(rdsmith): Distinguish between "Never used" and "Actually not
489 // touched for 48 hours".
490 UMA_HISTOGRAM_CUSTOM_TIMES(
491 "Sdch3.UsageInterval",
492 use_count ? time_since_last_used : base::TimeDelta::FromHours(48),
493 base::TimeDelta(), base::TimeDelta::FromHours(48), 50);
494
495 specific_dictionary_map->SetDouble(kDictionaryLastUsedKey, now.ToDoubleT());
496 specific_dictionary_map->SetInteger(kDictionaryUseCountKey, use_count + 1);
210 } 497 }
211 498
212 void SdchOwner::OnGetDictionary(net::SdchManager* manager, 499 void SdchOwner::OnGetDictionary(net::SdchManager* manager,
213 const GURL& request_url, 500 const GURL& request_url,
214 const GURL& dictionary_url) { 501 const GURL& dictionary_url) {
215 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1)); 502 base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1));
216 size_t avail_bytes = 0; 503 size_t avail_bytes = 0;
217 for (auto it = local_dictionary_info_.begin(); 504 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
218 it != local_dictionary_info_.end(); ++it) { 505 it.Advance()) {
219 if (it->second.last_used < stale_boundary) 506 if (it.last_used() < stale_boundary)
220 avail_bytes += it->second.size; 507 avail_bytes += it.size();
221 } 508 }
222 509
223 // Don't initiate the fetch if we wouldn't be able to store any 510 // Don't initiate the fetch if we wouldn't be able to store any
224 // reasonable dictionary. 511 // reasonable dictionary.
225 // TODO(rdsmith): Maybe do a HEAD request to figure out how much 512 // TODO(rdsmith): Maybe do a HEAD request to figure out how much
226 // storage we'd actually need? 513 // storage we'd actually need?
227 if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes + 514 if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes +
228 min_space_for_dictionary_fetch_)) { 515 min_space_for_dictionary_fetch_)) {
229 RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED); 516 RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED);
230 // TODO(rdsmith): Log a net-internals error. This requires 517 // TODO(rdsmith): Log a net-internals error. This requires
231 // SdchManager to forward the URLRequest that detected the 518 // SdchManager to forward the URLRequest that detected the
232 // Get-Dictionary header to its observers, which is tricky 519 // Get-Dictionary header to its observers, which is tricky
233 // because SdchManager is layered underneath URLRequest. 520 // because SdchManager is layered underneath URLRequest.
234 return; 521 return;
235 } 522 }
236 523
237 fetcher_.Schedule(dictionary_url); 524 fetcher_->Schedule(dictionary_url,
525 base::Bind(&SdchOwner::OnDictionaryFetched,
526 // SdchOwner will outlive its member variables.
527 base::Unretained(this), base::Time(), 0));
238 } 528 }
239 529
240 void SdchOwner::OnClearDictionaries(net::SdchManager* manager) { 530 void SdchOwner::OnClearDictionaries(net::SdchManager* manager) {
241 total_dictionary_bytes_ = 0; 531 total_dictionary_bytes_ = 0;
242 local_dictionary_info_.clear(); 532 fetcher_->Cancel();
243 fetcher_.Cancel(); 533
534 InitializePrefStore(pref_store_);
535 }
536
537 void SdchOwner::OnPrefValueChanged(const std::string& key) {
538 }
539
540 void SdchOwner::OnInitializationCompleted(bool succeeded) {
541 PersistentPrefStore::PrefReadError error =
542 external_pref_store_->GetReadError();
543 // Errors on load are self-correcting; if dictionaries were not
544 // persisted from the last instance of the browser, they will be
545 // faulted in by user action over time. However, if a load error
546 // means that the dictionary information won't be able to be persisted,
547 // the in memory pref store is left in place.
548 if (!succeeded) {
549 // Failure means a write failed, since read failures are recoverable.
550 DCHECK_NE(
551 error,
552 PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE);
553 DCHECK_NE(error,
554 PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM);
555
556 LOG(ERROR) << "Pref store write failed: " << error;
557 external_pref_store_->RemoveObserver(this);
558 external_pref_store_ = nullptr;
559 RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_WRITE_FAILED);
560 return;
561 }
562 switch (external_pref_store_->GetReadError()) {
563 case PersistentPrefStore::PREF_READ_ERROR_NONE:
564 break;
565
566 case PersistentPrefStore::PREF_READ_ERROR_NO_FILE:
567 // First time reading; the file will be created.
568 RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_NO_FILE);
569 break;
570
571 case PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE:
572 case PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE:
573 case PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER:
574 case PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED:
575 case PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT:
576 case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO:
577 case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY:
578 case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION:
579 RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_READ_FAILED);
580 break;
581
582 case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED:
583 case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED:
584 case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
585 case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM:
586 // Shouldn't ever happen. ACCESS_DENIED and FILE_NOT_SPECIFIED should
587 // imply !succeeded, and TASK_INCOMPLETE should never be delivered.
588 NOTREACHED();
589 break;
590 }
591
592
593 // Load in what was stored before chrome exited previously.
594 const base::Value* sdch_persistence_value = nullptr;
595 const base::DictionaryValue* sdch_persistence_dictionary = nullptr;
596
597 // The GetPersistentStore() routine above assumes data formatted
598 // according to the schema described at the top of this file. Since
599 // this data comes from disk, to avoid disk corruption resulting in
600 // persistent chrome errors this code avoids those assupmtions.
601 if (external_pref_store_->GetValue(kPreferenceName,
602 &sdch_persistence_value) &&
603 sdch_persistence_value->GetAsDictionary(&sdch_persistence_dictionary)) {
604 SchedulePersistedDictionaryLoads(*sdch_persistence_dictionary);
605 }
606
607 // Reset the persistent store and update it with the accumulated
608 // information from the local store.
609 InitializePrefStore(external_pref_store_);
610
611 ScopedPrefNotifier scoped_pref_notifier(external_pref_store_);
612 GetPersistentStoreDictionaryMap(external_pref_store_)
613 ->Swap(GetPersistentStoreDictionaryMap(in_memory_pref_store_.get()));
614
615 // This object can stop waiting on (i.e. observing) the external preference
616 // store and switch over to using it as the primary preference store.
617 pref_store_ = external_pref_store_;
618 external_pref_store_->RemoveObserver(this);
619 external_pref_store_ = nullptr;
620 in_memory_pref_store_ = nullptr;
244 } 621 }
245 622
246 void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) { 623 void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) {
247 clock_ = clock.Pass(); 624 clock_ = clock.Pass();
248 } 625 }
249 626
627 int SdchOwner::GetDictionaryCountForTesting() const {
628 int count = 0;
629 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
630 it.Advance()) {
631 count++;
632 }
633 return count;
634 }
635
636 bool SdchOwner::HasDictionaryFromURLForTesting(const GURL& url) const {
637 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
638 it.Advance()) {
639 if (it.url() == url)
640 return true;
641 }
642 return false;
643 }
644
645 void SdchOwner::SetFetcherForTesting(
646 scoped_ptr<SdchDictionaryFetcher> fetcher) {
647 fetcher_.reset(fetcher.release());
648 }
649
250 void SdchOwner::OnMemoryPressure( 650 void SdchOwner::OnMemoryPressure(
251 base::MemoryPressureListener::MemoryPressureLevel level) { 651 base::MemoryPressureListener::MemoryPressureLevel level) {
252 DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level); 652 DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level);
253 653
254 for (auto it = local_dictionary_info_.begin(); 654 for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
255 it != local_dictionary_info_.end(); ++it) { 655 it.Advance()) {
256 RecordDictionaryEviction(it->second.use_count, 656 int new_uses = it.use_count() - use_counts_at_load_[it.server_hash()];
257 DICTIONARY_FATE_EVICT_FOR_MEMORY); 657 RecordDictionaryEviction(new_uses, DICTIONARY_FATE_EVICT_FOR_MEMORY);
258 } 658 }
259 659
260 // TODO(rdsmith): Make a distinction between moderate and critical 660 // TODO(rdsmith): Make a distinction between moderate and critical
261 // memory pressure. 661 // memory pressure.
262 manager_->ClearData(); 662 manager_->ClearData();
263 } 663 }
264 664
665 bool SdchOwner::SchedulePersistedDictionaryLoads(
666 const base::DictionaryValue& persisted_info) {
667 // Any schema error will result in dropping the persisted info.
668 int version = 0;
669 if (!persisted_info.GetInteger(kVersionKey, &version))
670 return false;
671
672 // Any version mismatch will result in dropping the persisted info;
673 // it will be faulted in at small performance cost as URLs using
674 // dictionaries for encoding are visited.
675 if (version != kVersion)
676 return false;
677
678 const base::DictionaryValue* dictionary_set = nullptr;
679 if (!persisted_info.GetDictionary(kDictionariesKey, &dictionary_set))
680 return false;
681
682 // Any formatting error will result in skipping that particular
683 // dictionary.
684 for (base::DictionaryValue::Iterator dict_it(*dictionary_set);
685 !dict_it.IsAtEnd(); dict_it.Advance()) {
686 const base::DictionaryValue* dict_info = nullptr;
687 if (!dict_it.value().GetAsDictionary(&dict_info))
688 continue;
689
690 std::string url_string;
691 if (!dict_info->GetString(kDictionaryUrlKey, &url_string))
692 continue;
693 GURL dict_url(url_string);
694
695 double last_used;
696 if (!dict_info->GetDouble(kDictionaryLastUsedKey, &last_used))
697 continue;
698
699 int use_count;
700 if (!dict_info->GetInteger(kDictionaryUseCountKey, &use_count))
701 continue;
702
703 fetcher_->ScheduleReload(
704 dict_url, base::Bind(&SdchOwner::OnDictionaryFetched,
705 // SdchOwner will outlive its member variables.
706 base::Unretained(this),
707 base::Time::FromDoubleT(last_used), use_count));
708 }
709
710 return true;
711 }
712
713 bool SdchOwner::IsPersistingDictionaries() const {
714 return in_memory_pref_store_.get() != nullptr;
715 }
716
265 } // namespace net 717 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698