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

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

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

Powered by Google App Engine
This is Rietveld 408576698