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

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

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

Powered by Google App Engine
This is Rietveld 408576698