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