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

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

Powered by Google App Engine
This is Rietveld 408576698