OLD | NEW |
---|---|
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 |
OLD | NEW |